In this episode we take a look at ASP.NET Core’s middlewares, how to create them and use them in the request handling pipeline. For the walk-through you can check the next video, but if you prefer a quick read, skip to the written synthesis.
The playlist for the whole series is here.
We had a brief introduction to middlewares in the fourth episode, when we talked about the
Startup classes. In this post we’ll be revisiting the middleware topic, and going through the options we have to build middlewares.
As we’ve seen, the basic idea of middlewares is that they work like in a chain - a middleware is invoked, it can do something, then pass the request along to the next middleware and finally do something else when the next middleware is done. There can be cases where the next middleware isn’t invoked, for instance in a authorization middleware, if the user isn’t authenticated, it can immediately stop the request, returning a response by itself.
The following is a good image to illustrate the behavior, from the official docs.
Middlewares can range from simple things just to enrich the request handling pipeline - adding some headers, filter unauthorized access, collect some metrics, etc - to implementing a complete request handling logic - an endpoint for health checks, serving static files, MVC 🙂, etc.
To create middlewares, we have a couple of options to get started:
- Implement the logic in an inline anonymous function (like the example we saw in episode 4)
- Implement the logic in a class dedicated to it
In this post, we’ll take a look at some simple examples of implementing middlewares with both of these options.
Given we already saw a simple middleware implemented and registered in the pipeline with
app.Use, won’t be going into much more detail than adding the code here for reference 🙂
As we can see, this registers an anonymous function that receives the
HttpContext and a
Func to invoke the next middleware in the chain. Then the implementation, well, we do whatever we want, in this case it’s just adding a response header, while letting the rest of the pipeline work as normal, by invoking the next middleware.
Note that the order in which we register the middleware (during the
Configure method), defines the order in which they’ll be run when handling a request, so order is not irrelevant.
app.Use, we register a lambda based middleware. To register a class based middleware we can use
app.UseMiddleware<T> extension method.
When creating class based middlewares, we have a couple of options:
- Convention-based activated - where we follow a convention to implement the class, and all will work kinda automagically
- Factory-based activated - the middleware class implements
IMiddleware, so we get a more “normal” experience, like when we implement a controller inheriting from the
Controllerclass (which, by the way, is also not mandatory, we can also implement controllers by following some conventions)
Convention-based activated middleware
For a convention-based activated middleware, we should follow a couple of rules and we’re good to go. A basic implementation would be something like the following.
The constructor receives a delegate to invoke the next middleware in the chain, and we’re storing it to use it when handling the requests.
Then we have an
InvokeAsync method that gets the
HttpContext as an argument. This might already tells us something, that’s probably not completely clear at first look - the instance of the middleware used in the pipeline is basically a singleton, as it’s constructed at application startup.
Let’s take a look at a middleware with a little more going on.
As we saw in the previous example, the middleware instance is a singleton, so if we have some dependencies, instead of getting them in the constructor as usual, we declare what we need in the
InvokeAsync method, so they can be resolved per request.
With all this in mind, what happens in this case is the
_requestCounter field will have a count of all the requests that went through the middleware during the application lifetime, because it’s a singleton. Additionally, we get a logger as dependency injected on
Sample log output:
11:38:09 CodingMilitia.PlayBall.GroupManagement.Web.Demo.Middlewares.RequestTimingAdHocMiddleware Warn Request 1 took 1ms 11:38:15 CodingMilitia.PlayBall.GroupManagement.Web.Demo.Middlewares.RequestTimingAdHocMiddleware Warn Request 2 took 0ms 11:38:19 CodingMilitia.PlayBall.GroupManagement.Web.Demo.Middlewares.RequestTimingAdHocMiddleware Warn Request 3 took 0ms
For all of this to work, of course we need to register the middleware in the request handling pipeline:
Factory-based activated middleware
Another option for class based middlewares is to implement the
IMiddleware interface. Continuing with the example we used for the convention-based approach, we get:
Right off the bat, we can see a more traditional class layout, with a dependency injected in the constructor and the
InvokeAsync method to implement the interface.
In this type of middleware, the lifetime is up to us, as we need to register it in DI, and not only in the request handling pipeline like we did for the convention-based middleware.
In this case, because the class is being registered as transient, the request counter will not persist across requests, so we get the following log output:
11:52:06 CodingMilitia.PlayBall.GroupManagement.Web.Demo.Middlewares.RequestTimingFactoryMiddleware Warn Request 1 took 0ms 11:52:07 CodingMilitia.PlayBall.GroupManagement.Web.Demo.Middlewares.RequestTimingFactoryMiddleware Warn Request 1 took 0ms 11:52:08 CodingMilitia.PlayBall.GroupManagement.Web.Demo.Middlewares.RequestTimingFactoryMiddleware Warn Request 1 took 0ms
Back to using anonymous functions as middlewares, besides the
app.Use extension method, we have
app.Run. It is basically the same, but to register a function as a terminal middleware, which means it won’t flow the request through to any middleware that comes after it in the chain. It’s basically the same as
app.Use without ever calling the
next argument 😛
Just for examples sake, in the request handling pipeline configuration, we can add something at the end:
In this case, if no middleware is capable of completely handle the request, particularly the MVC middleware, we send out a (pretty barebones) response informing of this fact.
app.Map and app.MapWhen
Another interesting couple of extension methods we have available are
MapWhen. These methods allow us to create branches in the request handling pipeline given some conditions.
Let’s take a look at some examples.
Map allows us to branch the pipeline for a given path.
Taking the example above, for any request to
/ping, instead of following the usual pipeline, the middlewares configured in the lambda argument will be used, do we get the previously developed
RequestTimingFactoryMiddleware executing, plus a terminal middleware responding with a simple message.
MapWhen works in a very similar fashion to
Map, but instead of using forcibly a path, we can define other conditions. To define the condition, we provide a function that gets an
HttpContext argument and returns a boolean, indicating if the branch should be used.
This example is pretty similar to the previous one, but using as condition a check to see if there’s a request header named
ping. If the header exists, than the branch is used and, in this case, the previously implemented
RequestTimingAdHocMiddleware is used, plus another terminal middleware to output a simple response message.
That does it for a quick look at implementing middlewares. It’s simple enough, and I think there isn’t much more to it. To build more complex middlewares, we just need to add more logic upon these initial steps we followed to set them up.
Like I said in other posts in this series, the official docs will most likely have much more information when compared to these rather quick walk-throughs I’m putting out 😉.
The source code for this post is here.
Please send any feedback so I can improve and adjust the next episodes.
Thanks for stopping by, cyaz!