How to map routes when self-hosting for testing

Topics: Web Api
Jan 24, 2011 at 3:37 PM

My production code has a "RouteManager" class that essentially does AddServiceRoute on the RouteTable.Routes static property.

However, this fails on self-host:

Test 'Tests.WebClientSpec.WhenGettingSpecificUser_ThenReturnsIt' failed: Class Initialization method Tests.WebClientSpec.ClassInitialize threw exception. System.InvalidOperationException: System.InvalidOperationException: 'ServiceHostingEnvironment.EnsureServiceAvailable' cannot be invoked within the current hosting environment. This API requires that the calling application be hosted in IIS or WAS..
	at System.ServiceModel.ServiceHostingEnvironment.EnsureInitialized()
	at System.ServiceModel.Activation.ServiceRoute.CheckAndCreateRouteString(String routePrefix)
	at System.ServiceModel.Activation.ServiceRoute..ctor(String routePrefix, ServiceHostFactoryBase serviceHostFactory, Type serviceType)
	at Microsoft.ServiceModel.Http.RouteCollectionExtensions.AddServiceRoute[TService](RouteCollection routes, String routePrefix, HostConfiguration configuration)
	Web\RouteManager.cs(15,0): at WebHttp.Web.RouteManager.RegisterRoutes(RouteCollection routes)

Any ideas how to pass the routes to the service host somehow? Obviously, I don't want to go IIS or WAS for tests :)

Coordinator
Jan 24, 2011 at 3:46 PM
won't work though we are aware of this and have a bug filed to fix it.
Jan 24, 2011 at 3:51 PM

is there a place in the source code where I can hack this to work? Looks like self-hosting for tests is not an option for now otherwise :(

Coordinator
Jan 24, 2011 at 4:57 PM
No as this is part of wcf 4. What you can do (or should be able to do) is create a new route class which lazily instantiates an inner service route when the actual request occurs.

Sent from my Windows Phone

From: dcazzulino
Sent: Monday, January 24, 2011 7:52 AM
To: Glenn Block
Subject: Re: How to map routes when self-hosting for testing [wcf:243062]

From: dcazzulino

is there a place in the source code where I can hack this to work? Looks like self-hosting for tests is not an option for now otherwise :(

Jan 24, 2011 at 7:03 PM

Got this to work :)))

The key is NOT to use ServiceRoute or any routing for that matter. This helper class gets me there easily:

 

	/// <summary>
	/// Instantiates the host for the given service.
	/// </summary>
	/// <remarks>
	/// If VS is run without elevated permissions, you need to run the 
	/// following command from an elevated command prompt ONCE:
	/// <code>
	/// netsh http add urlacl http://+:[port]/ user=[DOMAIN\USER]
	/// </code>
	/// </remarks>
	[TestClass]
	public abstract class GivenAWebHttpService<TService>
	{
		private WebHttpServiceHost serviceHost;
		private HostConfiguration configuration;
		private string serviceRoute;

		public GivenAWebHttpService(string serviceBaseUrl, string serviceRoute, HostConfiguration configuration)
		{
			this.BaseUri = new Uri(serviceBaseUrl);
			this.serviceRoute = serviceRoute;
			this.configuration = configuration;
		}

		protected Uri BaseUri { get; private set; }

		/// <summary>
		/// Opens up the service host for the service.
		/// </summary>
		[TestInitialize]
		public virtual void Initialize()
		{
			this.serviceHost = new WebHttpServiceHost(typeof(TService), this.configuration, new Uri(this.BaseUri, this.serviceRoute));

			serviceHost.Open();
		}

		/// <summary>
		/// Closes the service host.
		/// </summary>
		[TestCleanup]
		public void Cleanup()
		{
			serviceHost.Close();
		}
	}

Usage:

    [TestClass]
    public class WebClientSpec : GivenAWebService<Server.WebHttp.Web.UserResourceService>
    {
		public WebClientSpec()
			: base("http://localhost:20000", "users", new Server.Clarius.JsonNetHostConfiguration())
		{
		}

        [TestInitialize]
        public override void Initialize()
        {
			// Initialize DB, etc.
			base.Initialize();
			// At this point the host is open.
        }

        [TestMethod]
        public void WhenGettingSpecificUser_ThenReturnsIt()
        {
            // Note that you have to use the BaseUri, not the full uri.
            var client = new HttpClient(base.BaseUri);

            // Do your request & asserts, i.e. client.Get("users/1")
        }
    }

Note: you use the BaseUri, but your client.Get calls need to include the route path just like your regular client code would. The routing is replaced with specific hosting of the service at the full uri that client requests will have.

Jan 24, 2011 at 7:20 PM

I should also note that you MUST use the WebHttpServiceHost constructor receiving the baseAddress, as that's the one that adds the HttpEndpointBehavior to the service endpoint being created.

If you don't, you have to manually add it after the call to host.AddServiceEndpoint()