WCF Web API–Message Handlers

This is the tenth post on a series about the Preview 4 of WCF Web API. The sixth post, entitled WCF Web API–Processing Architecture, presented Web API’s runtime architecture, introducing several key architectural elements, namely message handlers and operation handlers.

In this post, I describe message handlers in more detail, presenting some examples and a set of external resources on this subject.

Message handlers

Message handlers are a WCF Web API extensibility point located after a HttpRequestMessage is retrieved from the transport channel but before this message is dispatched to an operation, as shown in the following diagram

Message handlers are also located before the operation handlers. This has several consequences:

  • The same message handler instance is used for all the operations, i.e., all request URIs and HTTP methods. Namely, the handler doesn’t have access to the dispatching result – the final operation. However, there are different message handler instances per endpoint.
  • The message handler only has access to the request message and not to the operation’s parameters.

Message handlers use a “russian-doll” model, also used by other frameworks:

  • On initialization, each message handler receives a reference to a inner handler.
  • On request processing, each message handler receives the request and is responsible to produce a response, eventually delegating that task to the inner handler (in Preview 4 message handlers are still called delegating handlers).

However, message handlers don’t use a synchronous model. Instead, they use an asynchronous model, based on the Task<T> class: instead of returning a HttpResponseMessage, a message handler must return a Task<HttpResponseMessage>. This means that message handlers are an adequate extension point to insert lengthy I/O requests.

On Preview 4, a message handler is a class that must derive from DelegatingChannel and overrides the SendAsync method.

Example: Adding an header with the elapsed time

The following code excerpt show a simple message channel that records the request’s processing time and adds an header to the response with it. Note how, after creating the Stopwatch, the request is simply delegated to the inner handler (base.SendAsync)

class ElapsedTimeMessageHandler : DelegatingChannel
{
    public ElapsedTimeMessageHandler(HttpMessageChannel innerChannel)
        : base(innerChannel)
    {
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var sw = new Stopwatch();
        sw.Start();
        return
            base.SendAsync(request, cancellationToken)
                .ContinueWith(task =>
                    {
                        sw.Stop();
                        task.Result.Headers.Add("X-Elapsed-Time", 
						    sw.ElapsedMilliseconds.ToString());
                        return task.Result;
                    },TaskContinuationOptions.ExecuteSynchronously);
    }
}

 

Example: HTTP intermediary

A message handler doesn’t even need to call the inner handler. Namely, it can completely bypass the upper WCF runtime stack.

The following code excerpt shows a message handler prototype that simply forwards all the HTTP requests to the origin server, using an HttpClient object. It illustrates a simplistic way of implementing HTTP  intermediaries (e.g. proxies).

class ProxyMessageHandler : DelegatingChannel
{
    private readonly HttpClient _client = new HttpClient();
    public ProxyMessageHandler(HttpMessageChannel innerChannel)
        : base(innerChannel)
    {
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            var req = new HttpRequestMessage
                    {
                        RequestUri =
                            new Uri("http://" + request.Headers.Host + request.RequestUri.PathAndQuery),
                        Method = request.Method
                    };
            if (req.Method == HttpMethod.Post || req.Method == HttpMethod.Put)
            {
                req.Content = request.Content;
            }
            foreach (var h in request.Headers.Where(h => !h.Key.StartsWith("Proxy-")))
            {
                req.Headers.Add(h.Key, h.Value);
            }
            var resp = _client.SendAsync(req,HttpCompletionOption.ResponseHeadersRead);
            resp.ContinueWith(t => TraceResponse(req,t));
            return resp.ContinueWith(t =>
                {
                    t.Result.Headers.Remove("Transfer-Encoding");
                    return t.Result;
                });
        }
        catch (Exception e)
        {
           TraceException(e);
            throw;
        }
    }
}

 

Configuration

Message handlers can be configured via a HttpHostConfiguration class and the AddMessageHandlers method. This is illustrated in the following code excerpt. Notice that ProxyMessage handler is the inner handler of ElapseTimeMessageHandlers.

var cfg = HttpHostConfiguration.Create()
			.AddMessageHandlers(typeof(ProxyMessageHandler), typeof(ElapsedTimeMessageHandler));
	
using (var host = new HttpConfigurableServiceHost(typeof(TheService), cfg, new Uri(...)))
...

External resources

Finally, here are some interesting references about message handlers:

Advertisements

2 thoughts on “WCF Web API–Message Handlers

  1. saya

    How do we write Response Message Handlers. should it Inherit from DelegateChannel? I want to modify every response on the way out.

    Reply
    1. pedrofelix Post author

      There is no distinction between request and response message handlers. You just have to write a message handler (the name of the base class changed on preview 5) that:
      1) Simply forwards the request to the inner handler
      2) Get the response from the inner handler and modifies it.

      HTH
      Pedro

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s