Last update:

Non-linear middleware chains in ASP.NET Core

In one of my previous posts I described how middleware works in ASP.NET Core. Simply put, each middleware can modify the response and either invoke the next one in the chain, or decide that the request is handled and it’s time to return the response. This simple scenario will be sufficient in many situations. However it’s good to know that in ASP.NET Core we can create a non-linear middleware chain.

This post has been updated to cover ASP.NET Core 1.0

Purpose of middleware branching

First of all, why would anyone need a branched middleware chain? Let’s suppose we’ve got an application with both UI and API (think MVC and WebAPI in the old days). For the UI part, we’d like to have an logging middleware, error page, authentication, MVC and perhaps static files. The API part, on the other hand, will look somewhat different. We’d still use a logger but an error page does not make a lot of sense in the API. If clients expect XML or JSON, an HTML error page won’t do any good. Thus, we need a different error handler. Fortunately, IApplicationBuilder has the AddErrorHandler extension method that does what we need. Of course we could write a custom middleware that returns either a HTML page or empty response with an appropriate status code, but we don’t have to, thanks to the possibilities of the framework. Instead, our middleware chain will split after the logging part. The correct branch can be selected based on the URL of the request (there’s also a possibility to implement a custom predicate).

IApplicationBuilder.Map method

The IApplicationBuilder.Map method does exactly what we need. It allows to specify another configuration method for requests targeted at a given URL (or URL pattern). To be clear – it doesn’t invoke just a single middleware. A new IApplicationBuilder is used that can have several middlewares defined.

Let’s see how could it look like:

The Map method accept two arguments. In the first, we can specify the URL pattern for which the new middleware chain will be used. The second one is the Action<IApplicationBuilder delegate, which is exactly what our Configure method is. I created another configuration method, ConfigureApi and passed it to the Map method. In the example above, all requests to \api (or subresources, such as \api\thing, \api\some\other\thing) will be handled by the ConfigureApi method. Thus, for these URLs, a “Hello World from API!” string will be returned in the response.

IApplicationBuilder.MapWhen method

Sometimes branching based on URL will not be enough. Let’s suppose we need to handle AJAX requests in a different way. Fortunately, this could not be easier. The framework provides the IApplicationBuilder.MapWhen method, which behaves similarily to the aforementioned Map method, but instead of accepting a URL pattern, it requires a predicate (specifically, a Func<HttpContext, bool>, that is a function that accepts a HttpContext instance and returns true or false). This allows us to write some custom logic to determine if the configuration method should be executed.

In this example I’m checking if the request contain the X-Requested-With header. If this predicate is true, the ConfigureAjax method is used to set up further middlewares.

The MapWhen method also has its async brother, MapWhenAsync, in which the predicate is asynchronous (it accepts Func<HttpContext, Task<bool>> as its first parameter). If the middleware choosing logic is long-running (e.g. involves a database call), the MapWhenAsync method should be chosen over MapWhen.

Conclusion

To sum up, middleware chains in ASP.NET 5 don’t have to be “proper” (linear) chains. Authors of the framework gave us the possibility to order them as we see fit, so let’s take advantage of that.

2 comments

  1. Great post!
    Thanks for helping me understand this.

    Romko on 10 August 2015, 22:56 +01:00 Reply
  2. I don’t ordinarily comment but I gotta tell thank you for the post on this one :D.

    IT management on 21 October 2020, 19:39 +01:00 Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Michał Dudak