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

Implementing a Custom DelegatingHandler in ASP.NET WebAPI

, 6 Mar 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
We will see how to Implement a custom WebAPI Delegating handler with two real world examples.

Introduction

In this article we will see how to implement custom WebAPI delegating handlers with two real world examples. You have to understand what is a delegating handler before implementing one. When you are making a HTTP WebAPI request, it goes through a series of HTTP handlers in a pipeline. What each of these handlers does is, they receive the request, does some processing, and then sends the request to the next handler in the chain. At some point, i.e., when the last handler processes the request, it will send the response and further it will be passed back in the chain of HTTP handlers and this pattern is called a delegating handler.

You must be wondering why we need custom handlers or why we have to implement our own custom HTTP delegating handlers. The answer to this question is, there are several reasons. Each of these handlers is responsible to perform designated tasks. We will see in detail about the custom delegating handlers so that you will understand how to implement one.

Before implementing the custom delegating handlers we will see the existing WebAPI framework handlers which receive and process the requests.

  • HttpServer gets the request from the host.
  • HttpRoutingDispatcher dispatches the request based on the route.
  • HttpControllerDispatcher sends the request to a Web API controller.

Each of the custom delegating handlers can be implemented to do something like below:

  1. Apply compression to responses.
  2. Read or modify request headers.
  3. Add a response header to responses.
  4. Validate requests before they reach the controller. 
  5. Do some authentication.
  6. Trace or log the request and responses.

In this article we will see how we can implement custom Tracing and Compression delegating handlers. Then host the WebAPI making use of custom delegating handlers.

Below is the pictorial representation of how we are using the delegating handlers.

Background

Understanding of ASP.NET WebAPI. Please refer to my beginner articles on WebAPI. It is very important to glance at my WebAPI article: http://www.codeproject.com/Articles/549152/Introduction-to-ASP-NET-Web-API

We are reusing the ProductsLibrary WebAPI class library project that is already built.

Using the code

We will see how to implement a custom gzip or deflate compression delegating handler. The picture below shows a high level communication between the client and the server supporting the compression of HTTP responses.

Steps required

  1. Create a class and extend the DelegatingHandler abstract class.
  2. Override the SendAsync method to handle the compression.

You will notice the custom handler calls the base SendAsync i.e., HttpRoutingDispatcher in this case. When it gets the response, it will just check whether it has the AcceptEncoding. If so, it will get the first accept encoding and compress the content based on it.

This handler makes use of the CompressedContent class for handling GZip or deflate compression. It inherits the HttpContent class and overrides SerializeToStreamAsync to return the compressed HTTP Response. We are primarily using GzipStream or DeflateStream to compress the content based on the encoding type.

public class CompressionDelegateHandler : DelegatingHandler
{
    protected override Task<httpresponsemessage> SendAsync(
               HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, 
          cancellationToken).ContinueWith<httpresponsemessage>((
          responseToCompleteTask) =>
        {
            HttpResponseMessage response = responseToCompleteTask.Result;

            if (response.RequestMessage.Headers.AcceptEncoding != null)
            {
                string encodingType = response.RequestMessage.Headers.AcceptEncoding.First().Value;                    
                response.Content = new CompressedContent(response.Content, encodingType);
            }

            return response;
        },
        TaskContinuationOptions.OnlyOnRanToCompletion);
    }
}

public class CompressedContent : HttpContent
{
    private HttpContent originalContent;
    private string encodingType;

    public CompressedContent(HttpContent content, string encodingType)
    {
        if (content == null)
        {
            throw new ArgumentNullException("content");
        }

        if (encodingType == null)
        {
            throw new ArgumentNullException("encodingType");
        }

        originalContent = content;
        this.encodingType = encodingType.ToLowerInvariant();

        if (this.encodingType != "gzip" && this.encodingType != "deflate")
        {
            throw new InvalidOperationException(string.Format(
              "Encoding '{0}' is not supported. Only supports gzip or deflate encoding.", 
              this.encodingType));
        }

        foreach (KeyValuePair<string,>> header in originalContent.Headers)
        {
            this.Headers.Add(header.Key, header.Value);
        }

        this.Headers.ContentEncoding.Add(encodingType);
    }
 
    protected override bool TryComputeLength(out long length)
    {
        length = -1;

        return false;
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        Stream compressedStream = null;

        if (encodingType == "gzip")
        {
            compressedStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
        }
        else if (encodingType == "deflate")
        {
            compressedStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true);
        }

        return originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
        {
            if (compressedStream != null)
            {
                compressedStream.Dispose();
            }
        });
    }
} 

We will now see how we can implement a custom tracing delegating handler.

Code snippet for Trace Delegating handler

public class TraceMessageHandler : DelegatingHandler
{
    protected override Task<httpresponsemessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request.Method == HttpMethod.Trace)
        {
            return Task<httpresponsemessage>.Factory.StartNew(() =>  {
                    var response = new HttpResponseMessage(HttpStatusCode.OK);
                    response.Content = new StringContent(request.ToString(), Encoding.UTF8, "message/http");
                    return response;
                });
        }

        return base.SendAsync(request, cancellationToken);
    }
} 

The tracing logic is pretty straightforward. It will check for the request method, if it's an HttpTrace then it will return the trace message, else it will forward the request to the next handler, i.e., the CompressionDelegateHandler.

We will see the hosting part of the WebAPI with the above mentioned custom delegating handlers

Code snippet for hosting the WebAPI

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.MessageHandlers.Add(new TraceMessageHandler());
    config.MessageHandlers.Add(new CompressionDelegateHandler());
 
    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();

    Console.ReadLine();          
}

It's normal console hosting of WebAPI. Where we are using HttpSelfHostConfiguration to configure the HTTP Route and host it by making use of HttpSelfHostServer. You can notice how the custom delegating
handlers are attached here by adding it to the MesssageHandlers collection.

When a client makes an HTTP Web Request to get all the products, the request method will be 'GET', the request will go through:

HttpServer -> TraceMessageHandler -> CompressionDelegateHandler -> HttpRoutingDispatcher -> HttpControllerDispatcher -> ApiController

Similarly the HTTP Response will be forward from ApiController back to the HttpServer and it will finally send it across to the client.

Code snippet for trace request

  1. Instantiate an HttpClient instance and set the base address, i.e., the URI where the WebAPI is hosted.
  2. Instantiate an HttpRequestMessage and set the RequestUri and HTTP method to 'Trace'.
  3. Make a WebAPI Request and read the response.

You should be able to see the below mentioned Trace message.

private async static void TraceRequest()
{
    try
    {
        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri("http://localhost:8081/");

        HttpRequestMessage request = new HttpRequestMessage()
        {
            RequestUri = new Uri("http://localhost:8081/api/Products"),
            Method = HttpMethod.Trace
        };

        Console.WriteLine("********* Send Request to get all products *******");

        HttpResponseMessage httpReponse = client.SendAsync(request).Result;
        var text = await httpReponse.Content.ReadAsStringAsync();
        Console.WriteLine(text);
        Console.WriteLine("\n");
    }
    catch (HttpRequestException e)
    {
        Console.WriteLine("\nException Caught!");
        Console.WriteLine("Message :{0} ", e.Message);
    }
}

Code snippet for HTTP GET compression test

  1. Instantiate an HttpClient instance and set the base address, i.e., the URI where the WebAPI is hosted.
  2. Set the Accept encoding to 'gzip'.
  3. Instantiate a HttpRequestMessage and set the RequestUri and HTTP method to 'GET'.
  4. Make a WebAPI Request and read the response in bytes.
  5. Decompress the bytes to get the actual content.
private async static void SendRequest()
{
     try
     {
       HttpClient client = new HttpClient();
       client.BaseAddress = new Uri("http://localhost:8081/");
       client.DefaultRequestHeaders.Clear();
       client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));

       HttpRequestMessage request = new HttpRequestMessage()
       {
           RequestUri = new Uri("http://localhost:8081/api/Products"),
           Method = HttpMethod.Get
       };

       Console.WriteLine("********* Send Request to get all products *******");
     
       HttpResponseMessage response = client.SendAsync(request).Result;
       var compressedResponseInBytes = await response.Content.ReadAsByteArrayAsync();
       var decompressedResponseInBytes = Decompress(compressedResponseInBytes);

       string text = System.Text.ASCIIEncoding.ASCII.GetString(decompressedResponseInBytes);
       Console.WriteLine(text);
       Console.WriteLine("\n");
     }
     catch (HttpRequestException e)
     {
        Console.WriteLine("\nException Caught!");
        Console.WriteLine("Message :{0} ", e.Message);
     }
}

Points of Interest

It was really interesting to learn something new and implement. I loved implementing custom delegating handlers as the WebAPI framework by itself did not provide the compression of HTTP responses , I happened to write one.

Please let me know if you have any questions, comments or any concerns. Also if you like this article please do vote for me.

History

  • Version 1.0 - 3/05/2013 - Initial version with two sample delegating handlers

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 Content 2014

Comments and Discussions

 
GeneralJob Opportunity Pinmemberniteshsshah3-Mar-14 16:26 
News[My vote of 2] You should quote your sources PinmemberThaRudeDude10-Oct-13 0:15 
GeneralExcellant PinmemberGopikrishna G9-May-13 5:37 
GeneralMy vote of 5 PinmemberRudolf Grauberger6-Mar-13 11:20 
GeneralRe: My vote of 5 PinmemberRanjan.D6-Mar-13 11:45 
GeneralMy vote of 5 PinmemberJ. Wijaya5-Mar-13 15:43 
GeneralRe: My vote of 5 PinmemberRanjan.D5-Mar-13 16: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 | Mobile
Web04 | 2.8.141015.1 | Last Updated 6 Mar 2013
Article Copyright 2013 by Ranjan.D
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid