Click here to Skip to main content
Click here to Skip to main content

ASP.NET WebAPI Hosting Techniques

, 11 Sep 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
We shall see different WebAPI hosting techniques by creating a WebAPI library and referencing it in different hosting projects. Also perform unit tests to confirm the functionality of our WebAPI logic.

Introduction

In this article we shall see different methods of WebAPI hosting. We will be creating a class library for an API Controller and reference it in various hosting projects. If you have started working with WebAPI, soon you will start copy pasting the WebAPI controllers and related things over and over in each project. Say you have planned to host one in a Console or wish to perform unit testing, or host it in IIS as a Web Application.

When you are hosting your WebAPI in IIS, all your API controllers get compiled and will be available in the application domain so you can make an HTTP Web request to your API controller. But this isn't the case when you are hosting in a Windows process (self hosting) by creating a class library and referencing the same. You will be surprised and wonder why it isn't working as expected.

There is currently a bug associated with the WebAPI framework. When you are hosting your WebAPI in a console or Windows service using HttpSelfHostServer, the HttpControllerDispatcher is unable to map the incoming URL request to the API controller if you are trying to reference the WebAPI as a class library. So you see, most of the WebAPI implementations hosted in a console have the API controllers within the same project. In fact, it is also the case with integration or unit testing of API Controllers.

Here we will see how to overcome the above problem when we are creating a WebAPI library and trying to reference it when you are hosting the WebAPI in ASP.NET, Console, or Window Service.

There are primarily two types of hosting available:

  1. Web Hosting - In this case the Web API will be hosted on top of the classic ASP.NET hosting infrastructure, supported by the IIS (Internet Information Services) server.
  2. Self Hosting - The WebAPI will be hosted on a Windows process. We can either go with console or Windows services.

We can also use in-memory hosting for unit testing the WebAPI libraries. Below are two hosting adapters used for web and self hosting. They are also available as independent Nuget packages.

  • Microsoft.AspNet.WebAPI.WebHost
  • Microsoft.AspNet.WebApi.SelfHost

Before understanding WebAPI hosting, let's first glance through the below diagram and understand the overall picture of the different types of hosting to how requests are dispatched to the API controller.

Background  

Understanding of ASP.NET WebAPI. You can also refer to my beginner's article: http://www.codeproject.com/Articles/549152/Introduction-to-ASP-NET-Web-API

Using the code 

Step 1: Creating a new WebAPI Controller class and its associated Entity in a class library project.

Create a new class library project, say "ProductsLibrary", and then create a new ProductsController class inheriting ApiController. Implement WebAPI methods to get all products, products by ID, etc.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Category { get; set; }
    public decimal Price { get; set; }
}

public class ProductsController : ApiController
{
    public ProductsController()
    {
    }

    Product[] products = new Product[]  
    {  
        new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },  
        new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },  
        new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }  
    };
 
    public IEnumerable<product> GetAllProducts()
    {
        return products;
    }

    public Product GetProductById(int id)
    {
        var product = products.FirstOrDefault((p) => p.Id == id);
        if (product == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
        return product;
    }

    public IEnumerable<product> GetProductsByCategory(string category)
    {
        return products.Where(p => string.Equals(p.Category, category,
                StringComparison.OrdinalIgnoreCase));
    }
} 

Create a CustomAssemblyResolver class implementing the IAssembliesResolver interface. The GetAssemblies method implementation loads all the assemblies based on the path. We will be re-using this class in various other projects.

public class CustomAssemblyResolver : IAssembliesResolver
{
    private string _path;
    public CustomAssemblyResolver(string path)
    {
        _path = path;
    }

    ICollection<assembly> IAssembliesResolver.GetAssemblies()
    {
        List<assembly> assemblies = new List<assembly>();
        assemblies.Add(Assembly.LoadFrom(_path));
        return assemblies;
    }
} 

Let us see how to host the WebAPI in a console application. Before hosting WebAPI, we will have to follow the below mentioned step to reserve the URL.

Add an HTTP URL Namespace Reservation

This application listens to http://localhost:8081/. By default, listening at a particular HTTP address requires administrator privileges. Run Visual Studio with elevated administrator permissions, or use Netsh.exe to give your account permissions to reserve the URL.

To use Netsh.exe, open a command prompt with administrator privileges and enter the following command:

netsh http add urlacl url=http://+:8081/ user=machine\username

Where machine\username is your user account.

When you are finished self-hosting, be sure to delete the reservation:

netsh http delete urlacl url=http://+:8081/ 

Hosting the WebAPI in a Console App

  1. Create a new console application and reference ProductsLibrary.dll (the WebAPI class library project that we created).
  2. Create an instance of HttpSelfHostConfiguration, set the Route and Services. It defines the self hosting configuration. Below you can see we are replacing the services by dynamically loading the ProductsLibrary assembly. So there comes the use of CustomAssemblyResolver.
  3. Create an instance of HttpSelfHostServer, make use of the above host configuration.
  4. Open connection to listen for the incoming HttpRequest to process.
static void Main(string[] args)
{
    Uri _baseAddress = new Uri("http://localhost:8081/");

    // Get the executable assembly location
    var assembly = System.Reflection.Assembly.GetExecutingAssembly().Location;

    // The Assembly to load
    string path = assembly.Substring(0, assembly.LastIndexOf("\\")) + "\\ProductsLibrary.dll";

    HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(_baseAddress);
    config.Services.Replace(typeof(IAssembliesResolver), new CustomAssemblyResolver(path));
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    HttpSelfHostServer server = null;
    // Create server
    server = new HttpSelfHostServer(config);
    Console.WriteLine("Waiting for clients");
    // Start listening
    server.OpenAsync().Wait();
    // Get All Products
    GetAllProducts();
    
    Console.ReadLine();          
}

We will now try to make a HttpRequest to the WebAPI Controller and see whether we can get all the products. For that we need to create an instance of HttpClient and then perform GetAsync.

private static async void GetAllProducts()
{
    try
    {
        HttpClient client = new HttpClient();
        HttpResponseMessage response = await client.GetAsync(
           new Uri("http://localhost:8081/api/Products"));
        response.EnsureSuccessStatusCode();
        string responseBody = await response.Content.ReadAsStringAsync();
        Console.WriteLine("********* Get request to get all products *******");
        Console.WriteLine(responseBody);
        Console.WriteLine("\n");
    }
    catch (HttpRequestException e)
    {
        Console.WriteLine("\nException Caught!");
        Console.WriteLine("Message :{0} ", e.Message);
    }
} 

The below picture depicts the Self Hosting of WebAPI.

Hosting the WebAPI in IIS

  1. Create an ASP.NET MVC4 Web application.
  2. Reference ProductsLibrary, the WebAPI library that we had created.
  3. You will notice the WebAPI Route registration all done by default when you are creating the project:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
  4. In order to host this web application you can create a virtual directory in IIS and choose the physical path, i.e., your Web project folder.

There is nothing special about this type of hosting. But the only difference you can see here is the ApiControllers are being referenced and it's not within the web application.

Below you can see how we are requesting for all products by specifying the URL to the Products API Controller.

In-Memory Hosting of WebAPI 

If you are planning to perform unit or integration testing then this approach would be the best one. Two different things can be done:

  1. Create an instance of HttpClient and HttpServer and make a WebAPI Request and assert the response.
  2. Create a custom DelegateHandler and then make use of HttpServer and HttpClient or HttpMessageInvoker to initiate a WebAPI request.

We will see both of these approaches.

Below is the code snippet that creates an instance of HttpConfiguration and sets the Routes:

HttpConfiguration config;
 
[SetUp]
public void Setup()
{
    config = new HttpConfiguration();
    config.Routes.MapHttpRoute("Default", 
      "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
    config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;        
}

Below is the code block for testing the GetAllProducts WebAPI functionality. Note we are replacing the HttpConfiguration's Services dynamically with the ProductsLibrary assembly.

  1. Create an HttpServer instance with HttpConfiguration.
  2. Create an HttpClient instance with HttpServer.
  3. Make a WebAPI request to get all products.
  4. Assert the Products count and make sure the count is as expected.
[Test]
public void GetAllProductsWithOutMessageHandler()
{
    var assembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
    string path = assembly.Substring(0, 
      assembly.LastIndexOf("\\")) + "\\ProductsLibrary.dll";
    config.Services.Replace(typeof(IAssembliesResolver), new CustomAssemblyResolver(path));

    HttpServer server = new HttpServer(config);
    HttpClient client = new HttpClient(server);
    HttpResponseMessage response = client.GetAsync(
      "http://can.be.anything/api/Products/GetAllProducts").Result;
 
    var responseProducts = response.Content.ReadAsAsync<product[]>().Result;
    Assert.AreEqual(responseProducts.Count(), 3);
}  

The second method, we will make use of a custom delegate handler and verify the same.

Note: here we are using a InMemoryHttpContentSerialization handler. It's used to send the WebAPI request and format the response appropriately and send it across to the client. Frankly speaking, InMemoryHttpContentSerialization just overrides the SendAsync method.

The InMemoryHttpContentSerialization delegate handler does the below:

  1. Processes the request message.
  2. Calls base.SendAsync to send the request to the inner handler.
  3. The inner handler returns a response message. (This step is asynchronous.)
  4. Processes the response and returns it to the caller.

Note: Here the InnerHandler itself is an HttpServer. So it is actually responsible for sending the WebAPI Requests.

You might find a little difference in the way we are performing the below unit tests by creating a HttpRequestMessage and initiating a WebAPI request using HttpMessageInvoker. We are still using HttpConfiguration, HttpServer instances, however the MessageInvoker's SendAsync can make any type of WebAPI Request. The request in-turn is purely dependent upon the request method that we are setting.

The biggest advantage of using this approach is we can do some pre and post processing of HTTP Web Request and Response.

[Test]
public void GetProductsWithMessageHandler()
{
    Uri uri = new Uri(baseAddress, "api/Products/GetAllProducts");
  
    Product requestProduct = new Product()
    {
        Id = 1,
        Name = "Tomato Soup",
        Category = "Groceries",
        Price = 1
    };

    HttpServer server = new HttpServer(config);
    HttpMessageInvoker messageInvoker = new HttpMessageInvoker(
                 new InMemoryHttpContentSerializationHandler(server));
    HttpRequestMessage request = new HttpRequestMessage();

    request.RequestUri = uri;
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
    request.Method = HttpMethod.Get;

    CancellationTokenSource cts = new CancellationTokenSource();

    using (HttpResponseMessage response = messageInvoker.SendAsync(request, cts.Token).Result)
    {
        var responseProducts = response.Content.ReadAsAsync<product[]>().Result;

        Assert.NotNull(response.Content);
        Assert.NotNull(response.Content.Headers.ContentType);
        Assert.AreEqual("application/xml; charset=utf-8", 
                  response.Content.Headers.ContentType.ToString());
        Assert.AreEqual(responseProducts.Count(), 3);
    }
}

Below is the code snippet for InMemoryHttpContentSerializationHandler:

public class InMemoryHttpContentSerializationHandler : DelegatingHandler
{
    public InMemoryHttpContentSerializationHandler()
    {
    }

    public InMemoryHttpContentSerializationHandler(HttpMessageHandler innerHandler)
        : base(innerHandler)
    {
    }

    protected override Task<httpresponsemessage> SendAsync(
         HttpRequestMessage request, CancellationToken cancellationToken)
    {   
        return base.SendAsync(request, cancellationToken).ContinueWith<httpresponsemessage>((responseTask) =>
        {
            HttpResponseMessage response = responseTask.Result;
            response.Content = ConvertToStreamContent(response.Content);
            return response;
        });    
    }

    private StreamContent ConvertToStreamContent(HttpContent originalContent)
    {
        if (originalContent == null)
        {
            return null;
        }

        StreamContent streamContent = originalContent as StreamContent;

        if (streamContent != null)
        {
            return streamContent;
        }

        MemoryStream ms = new MemoryStream();

        // **** NOTE: ideally you should NOT be doing calling Wait() as its going to block this thread ****
        // if the original content is an ObjectContent, then this particular
        // CopyToAsync() call would cause the MediaTypeFormatters to 
        // take part in Serialization of the ObjectContent and the result
        // of this serialization is stored in the provided target memory stream.
        originalContent.CopyToAsync(ms).Wait();

        // Reset the stream position back to 0 as in the previous CopyToAsync() call,
        // a formatter for example, could have made
        // the position to be at the end after serialization
        ms.Position = 0;

        streamContent = new StreamContent(ms);

        // copy headers from the original content
        foreach (KeyValuePair<string,>> header in originalContent.Headers)
        {
            streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }
 
        return streamContent;
    }
} 

Below you can see  when we are hosting the WebAPI in-memory we are directly dealing with the HttpClient and HttpServer.

References  

Points of Interest 

It was really a good time in learning something new and playing around once again with WebAPI to understand the hosting tactics.

If you have any suggestions, comments, or anything you want to say please post a comment. Also If you like the article, please do vote for me.

History   

  • Version 1.0 - 3/3/2013 - Initial version with a sample application to host the Web API in Console, IIS, and in-memory hosting to unit test.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ranjan.D
Web Developer
United States United States
Profile
 
Around 10 years of professional software development experience in analysis, design, development, testing and implementation of enterprise web applications for healthcare domain with good exposure to object-oriented design, software architectures, design patterns, test-driven development and agile practices.
 
In Brief
 
Analyse and create High Level , Detailed Design documents.
Use UML Modelling and create Use Cases , Class Diagram , Component Model , Deployment Diagram, Sequence Diagram in HLD.
 
Area of Working : Dedicated to Microsoft .NET Technologies
Experience with : C# , J2EE , J2ME, Windows Phone 8, Windows Store App
Proficient in: C# , XML , XHTML, XML, HTML5, Javascript, Jquery, CSS, SQL, LINQ, EF
 
Software Development
 
Database: Microsoft SQL Server, FoxPro
Development Frameworks: Microsoft .NET 1.1, 2.0, 3.5, 4.5
UI: Windows Forms, Windows Presentation Foundation, ASP.NET Web Forms and ASP.NET MVC3, MVC4
Coding: WinForm , Web Development, Windows Phone, WinRT Programming, WCF, WebAPI
 
Healthcare Domain Experience
 
CCD, CCR, QRDA, HIE, HL7 V3, Healthcare Interoperability
 
Others:
 
TTD, BDD
 
Education
 
B.E (Computer Science)
 
CodeProject Contest So Far:
 
1. Windows Azure Developer Contest - HealthReunion - A Windows Azure based healthcare product , link - http://www.codeproject.com/Articles/582535/HealthReunion-A-Windows-Azure-based-healthcare-pro
 
2. DnB Developer Contest - DNB Business Lookup and Analytics , link - http://www.codeproject.com/Articles/618344/DNB-Business-Lookup-and-Analytics
 
3. Intel Ultrabook Contest - Journey from development, code signing to publishing my App to Intel AppUp , link - http://www.codeproject.com/Articles/517482/Journey-from-development-code-signing-to-publishin
 
4. Intel App Innovation Contest 2013 - eHealthCare - http://www.codeproject.com/Articles/635815/eHealthCare
 
5. Grand Prize Winner of CodeProject HTML5 &CSS3 Article Contest 2014
 
6. Grand Prize Winner of CodeProject Android Article Contest 2014

Comments and Discussions

 
QuestionThere is currently a bug associated with the WebAPI framework. Pinmemberdudu43513-May-14 8:29 
AnswerRe: There is currently a bug associated with the WebAPI framework. PinmvpRanjan.D13-May-14 9:55 
QuestionMy vote of 5 Pinmemberdudu43513-May-14 6:09 
GeneralRe: My vote of 5 PinmvpRanjan.D13-May-14 6:33 
GeneralMy vote of 5 Pinmemberrhck5-Mar-13 2:37 
Thank you for this useful introduction.
GeneralRe: My vote of 5 PinmemberRanjan.D5-Mar-13 6:08 
GeneralMy vote of 5 PinmemberMonjurul Habib4-Mar-13 20:25 
GeneralRe: My vote of 5 PinmemberRanjan.D5-Mar-13 6:08 
GeneralMy vote of 5 Pinmembersraoh4-Mar-13 9:36 
GeneralRe: My vote of 5 PinmemberRanjan.D4-Mar-13 11:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141216.1 | Last Updated 11 Sep 2013
Article Copyright 2013 by Ranjan.D
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid