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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; namespace MyApplication { public class Startup { public void Configure(IApplicationBuilder app) { app.Map("/api", ConfigureApi); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } private void ConfigureApi(IApplicationBuilder app) { app.Run(async (context) => { await context.Response.WriteAsync("Hello World from API!"); }); } } } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; namespace MyApplication { public class Startup { public void Configure(IApplicationBuilder app) { app.MapWhen(ctx => ctx.Request.Headers["X-Requested-With"] == "XMLHttpRequest", ConfigureAjax); app.Run(async (ctx) => { await ctx.Response.WriteAsync("Hello World!"); }); } private void ConfigureAjax(IApplicationBuilder app) { app.Run(async (ctx) => { await ctx.Response.WriteAsync("Hello World from AJAX request!"); }); } } } |
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.
Great post!
Thanks for helping me understand this.
I don’t ordinarily comment but I gotta tell thank you for the post on this one :D.