Using Message Handlers in ASP.NET Web API

December 24, 2016

Message handlers are a very powerful yet underutilized part of the ASP.NET Web API framework. In this post I’ll demonstrate their usefulness in keeping your API clean, maintainable, and testable. Before I get into how to write a message handler you need to know where they fit into the Web API request pipeline and how they work.

How a request is processed in Web API

The Web API request pipeline is quite simple. It is made up of three layers: the hosting layer, the message handling pipeline, and the controller.

The ASP.NET Web API Pipeline

The raw HTTP request comes to the hosting layer (either IIS or self-hosted) and is parsed into an HttpRequestMessage. That message is then passed into the message handler pipeline. The message handler pipeline is composed of a chain of pluggable handlers that accept an HttpRequestMessage and return an HttpResponseMessage. The request is passed down the chain of handlers until it gets to your controller. When you return a value from your controller it is converted to an HttpResponseMessage which then bubbles back up the handler chain.

Another way to visualize this is to think of the layers of an onion. Your controller is at the core. An HttpRequestMessage must traverse through each layer before it reaches the core. The core generates an HttpResponseMessage which then must pass back through the layers before it’s sent to the API consumer.

Web API's Onion Architecture

APIs typically include things like request/response logging, authentication, rate limiting, validation, etc. Putting all of this logic in your controllers will quickly clutter them up and make the project more difficult to maintain. If they are moved into message handlers then the controllers stay clean and focused on business logic. It also makes it easy to add that kind of functionality later without affecting the business logic in your controller.

Handlers can perform both pre- and post-processing logic on the message. For example, you could write an authentication message handler that checks for a valid API token. If the token is invalid it will immediately return the appropriate message to the consumer without passing the request through the rest of the pipeline.

Message Handlers Short Cicruit

Creating Message Handlers

Plugging in a custom message handler is very easy. Simply create a class that inherits from DelegatingHandler and override the SendAsync method. I’ll demonstrate by creating a message handler that adds rate limiting to an API. Please note that this demo does not represent a complete rate limiting example; this is just the message handler. If you want to use this handler then you will have to implement the rate limiting service on your own.

To add rate limiting we’ll pull the API token from the request and call into a service that returns the number of remaining requests that can be made with that token. If the token has already made the maximum number of requests then the API returns an HTTP 429 Too Many Requests response. If the caller is still within the limit then the request is processed and a custom header designating how many requests are remaining is appended to the response.

using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace MyApi.Infrastructure
{
public class RateLimitingHandler : DelegatingHandler
{
private const string RateLimitHeaderName = "X-MyApi-Rate-Limit-Remaining";
// The service that handles tracking rate limits for API consumers.
RateLimitingService _service = new RateLimitingService();
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response;
// Get the token that is making this request.
string consumerToken = request.Headers.Authorization.Parameter;
// Get the number of calls that this token is authorized to make.
int remainingCalls = _service.GetRemainingCalls(consumerToken);
// If the number of calls < 1 then the consumer has hit their rate limit.
// Return an appropriate response.
if(remainingCalls < 1)
{
response = request.CreateResponse(
(HttpStatusCode)429, new { Message = "Rate limit exceeded!" });
}
else // Otherwise return the response from the controller.
{
response = await base.SendAsync(request, cancellationToken);
// Append a custom header so the API consumer can see the remaining request limit.
response.Headers.Add(RateLimitHeaderName, (--remainingCalls).ToString());
}
return response;
}
}
}

The handler must be registered in the WebApiConfig.Register() method. It can be registered globally so that every request is processed through it or it can be registered for a single route. If you are registering more than one message handler then pay careful attention to the order you register them in. Message handlers execute from the top down.

using MyApi.Infrastructure;
using System.Web.Http;
namespace MyApi
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
// Register as a global handler - all Web API requests will go through
// RateLimitingHandler.
config.MessageHandlers.Add(new RateLimitingHandler());
// -- OR --
// Register the handler for a specific route - only Web API requests
// that match this route will go through RateLimitingHandler.
config.Routes.MapHttpRoute(
name: "TasksApi",
routeTemplate: "api/Tasks/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: null,
handler: new RateLimitingHandler()
);
}
}
}
view raw WebApiConfig.cs hosted with ❤ by GitHub

Conclusion

Message handlers can simplify a lot of an API’s “administrative” functionality like logging and authentication and make it easy to add more functionality in the future. What creative uses have you found for them? Let me know in the comments.


© 2020 Jesse Barocio. Built with Gatsby