Message handlers scoped per route in ASP.NET Web API

Β· 495 words Β· 3 minutes to read

A lot of Web API functionalities are based around message handlers. The real power of them is that they run for every request and provide an easy mechanism of globally addressing some of the application concerns (such as i.e. security). However, there are situations where you’d like to use a handler to apply specific behavior to only a selected portion of an application.

Let’s have a look at how you’d do it with route-specific message handlers

Suppose we have a need to log all traffic against the routes /api/[something] but not /api/public/[something]. As a result we need to have a message handler to apply only for a specific route, rather than globally to every request.

Route specific handlers πŸ”—

It is possible to configure a message handler (DelegatingHandler) on a per-route basis, rather than registering globally in a HttpConfigration object.

To begin with, let’s take a simple API request logging handler:

public class UsageHandler : DelegatingHandler  
{  
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)  
{  
//see extension method  
///2013/05/retrieving-the-clients-ip-address-in-asp-net-web-api/  
var ip = request.GetClientIpAddress();  
//log IP

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

The actual implementation of the request/response logging is not important here. We are really interested only in the mechanism that will allow us to apply this handler to specific routes.

We do that in the Web API RouteConfig class:

{  
config.Routes.MapHttpRoute(  
name: "PublicDefaultApi",  
routeTemplate: "api/public/{controller}/{id}",  
defaults: new { id = RouteParameter.Optional }  
);

config.Routes.MapHttpRoute(  
name: "PrivateDefaultApi",  
routeTemplate: "api/{controller}/{id}",  
defaults: new { id = RouteParameter.Optional },  
constraints: null,  
handler: new UsageHandler() { InnerHandler = new HttpControllerDispatcher(config) }  
);  
}  

There is an overload on the MapHttpRoute, in the HttpRouteCollectionExtensions that allows us to do that:

public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints, HttpMessageHandler handler);  

Notice that we need to specify the HttpControllerDispatcher as the inner handler for our handler - this means it will be chained as the next one to execute, and that’s the class responsible for creating controllers and continuing with the Web API execution pipeline. Using this technique, we could chain as many handlers on this route as we want, as long as we plug the HttpControllerDispatcher at the end.

How does it work? πŸ”—

In the Web API pipeline, the additional per route handler kicks after all global message handlers run and just prior to HttpControllerDispatcher. An HttpRoutingDispatcher is responsible solely for recognizing whether a given route has a specific handler attached to it, and if so, it will be run; otherwise the processing continues normally towards dispatching of controllers.

If you now run your application:

    • all /public/api/[something] routes do not log their requests/responses
    • all /api/[something] routes log their requests/responses - because the handler executes only for these routes

You can only imagine the amount of flexibility this gives the developers, especially in authentication and logging-related situations, where only specific routes have to be secured by authentication mechanisms (which are normally handled in Web API by message handlers) or should be subject to quotas or usage limits.

About


Hi! I'm Filip W., a software architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP