ASP.Net Web API Self Hosting: Really useful for testing Web API responses

Hackered
Sunday, September 21, 2014
by Sean McAlinden

I recently wrote a library for using Data Annotations with ASP.Net Web API called ASP.Net Web API Request Validator and wanted a set of acceptance tests to verify the response. Whilst I could have spun up multiple sites for the different configuration options, it made much more sense to create a little self host class which I could point at my different start up options. Using SpecFlow I created a hook to start and stop the host:

[BeforeFeature("SelfHostDefault")]
public static void BeforeComplexHostFeature()
{
    Host.StartHost<DefaultStartUp>(getBaseAddress: SetBaseAddress);
}
 
[AfterFeature("SelfHostDefault")]
public static void AfterComplexHostFeature()
{
    Host.StopHost();
}

What's going on?

The Host.StartHost method accepts a generic argument which represents the WebApiConfig for example:

public class DefaultStartUp
{
    public void Configuration(IAppBuilder appBuilder)
    {
        var config = new HttpConfiguration();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
 
        RequestValidator.Create()
            .MapPropertyName(x => x.Property)
            .MapPropertyErrorMessagesCollection(x => x.ErrorMessages)
            .Map(x => x.Message).ToErrorMessage()
            .Init(config);
 
        appBuilder.UseWebApi(config);
    }
}

The getBaseAddress parameter is setting up a callback method to grab the generating host address and store it for use:

private static void SetBaseAddress(string baseAddress)
{
    FeatureContextService.SaveValue("baseAddress", baseAddress);
}

The base address will be localhost plus an available port, you can however pass in the port if required.

The Host

Here is the host code. Essentially I spin up the host on a separate thread on StartHost. The stop host sends a cancellation request on the hosts thread to shut down. If you want to see it in action, download my RequestValidator project and run the acceptance tests.  

/// <summary>
/// Simple Self Host Utility for ASP.Net Web API.
/// </summary>
public static class Host
{
    private const string LocalHostAddress = "http://localhost:";
    private static string baseAddress = "";
    private static CancellationTokenSource cancellationTokenSource;
 
    /// <summary>
    /// Starts the host.
    /// </summary>
    /// <typeparam name="TStartup">The startup class.</typeparam>
    /// <param name="port">Enter a specific port or leave null for the host to utilise one that is available.</param>
    /// <param name="getBaseAddress">Action for returning the base address.</param>
    public static void StartHost<TStartup>(int? port = null, Action<string> getBaseAddress = null)
    {
        baseAddress = GetBaseAddress(port);
        if (getBaseAddress != null)
        {
            getBaseAddress(baseAddress);
        }
        cancellationTokenSource = new CancellationTokenSource();
        var cancellationToken = cancellationTokenSource.Token;
        Task.Factory.StartNew(() => WebApp.Start<TStartup>(baseAddress), cancellationToken);
    }
 
    /// <summary>
    /// Stops the host.
    /// </summary>
    public static void StopHost()
    {
        cancellationTokenSource.Cancel();
    }
 
    private static string GetBaseAddress(int? port)
    {
        return string.Format("{0}{1}/", LocalHostAddress, port.HasValue ? port.Value : GetAvailableTcpPort());
    }
 
    private static int GetAvailableTcpPort()
    {
        var tcpListener = new TcpListener(IPAddress.Loopback, 0);
        tcpListener.Start();
        var port = ((IPEndPoint)tcpListener.LocalEndpoint).Port;
        tcpListener.Stop();
        return port;
    }
}