By Andrew Lock
The word middleware is used in a variety of contexts in software development and IT, but it’s not a particularly descriptive word – so, what is middleware? This article discusses the definition of middleware in ASP.NET Core and how they can be used.
Save 37% off ASP.NET Core in Action with code lockaspdotnet.
In ASP.NET Core, middleware are C# classes that can handle an HTTP request or response. Middleware can either:
Handle an incoming HTTP request by generating an HTTP response.
Process an incoming HTTP request, modify it, and pass it on to another piece of middleware.
Process an outgoing HTTP response, modify it, and pass it on to either another piece of middleware, or the ASP.NET Core web server.
For example, a piece of logging middleware might note down when a request arrived and then pass it on to another middleware. Meanwhile, an image resizing middleware component might spot an incoming request for an image with a specified size, generate the requested image, and send it back to the user without passing it on.
The most important piece of middleware in most ASP.NET Core applications is the
MvcMiddleware. This normally generates your HTML pages and API responses. Like the image resizing middleware, it typically receives a request, generates a response, and then sends it back to the user, as shown in figure 1.
Figure 1 Example of a middleware pipeline. Each middleware handles the request and passes it on to the next middleware in the pipeline. After a middleware generates a response, it passes it back through the pipeline. When it reaches the ASP.NET Core web server, the response is sent to the user’s browser.
This arrangement, where a piece of middleware can call another piece of middleware, which in turn can call another, is referred to as a pipeline. You can think of each piece of middleware as a section of pipe – when you connect all the sections, requests flow through one piece into the next.
One of the most common use cases for middleware is for “crosscutting concerns” of your application. These aspects of your application need to occur with every request, regardless of the specific path in the request or the resource requested. These include things like:
Logging each request
Adding standard security headers to the response
Associating a request with the relevant user
Setting the language for the current request
In each of these examples, the middleware receives a request, modifies it, and then passes the request on to the next piece of middleware in the pipeline. Subsequent middleware could use the details added by the earlier middleware to handle the request. For example, in figure 2, the authentication middleware associates the request with a user. The authorization middleware uses this detail to verify whether the user has permission to make that specific request to the application.
Figure 2 Example of a middleware component modifying the request for use later in the pipeline. Middleware can also short-circuit the pipeline, returning a response before the request reaches later middleware.
If the user has permission, the authorization middleware passes the request on to the MVC middleware, to allow it to generate a response. If, on the other hand, the user doesn’t have permission, the authorization middleware can short-circuit the pipeline, generating a response directly. It returns the response back to the previous middleware, before the MVC middleware has even seen the request.
A key point to glean from this is that the pipeline is bi-directional. The request passes through the pipeline in one direction until a piece of middleware generates a response, at which point the response passes back through the pipeline in the other direction, passing through each piece of middleware for a second time, until it gets back to the first piece of middleware. Finally, this first/last piece of middleware passes the response back to the ASP.NET Core web server.
The HttpContext object
HttpContext sits behind the scenes. The ASP.NET Core web server constructs an
HttpContext, which the ASP.NET Core application uses as a sort of “storage box” for a single request. Anything which is specific to this request and the subsequent response can be associated with and stored in it. This could include properties of the request, request-specific services, data which has been loaded, or errors which have occurred. The web server fills the initial
HttpContext with details of the original HTTP request and other configuration details, and passes it on to the rest of the application.
All middleware has access to the
HttpContext for a request. It can use this to determine, for example, if the request contained any user credentials, what page the request was attempting to access, and to fetch any posted data. It can then use these details to determine how to handle the request.
Once the application has finished processing the request, it’ll update the
HttpContext with an appropriate response and return it back through the middleware pipeline to the web server. The ASP.NET Core web server converts the representation into a raw HTTP response and sends it back to the reverse proxy, which will forward it to the user’s browser.
You define the middleware pipeline in code as part of your initial application configuration in
Startup. You can tailor the middleware pipeline specifically to your needs –simple apps may need only a short pipeline, and large apps with a variety of features may use many more middleware. Middleware is the fundamental source of behavior in your application – ultimately the middleware pipeline is responsible for responding to any HTTP request it receives.
The request is passed to the middleware pipeline as an
HttpContext object. The ASP.NET Core web server builds an
HttpContext object from the incoming request, which passes up and down the middleware pipeline. When you’re using existing middleware to build a pipeline, this is a detail you’ll rarely deal with. Its presence behind the scenes provides a route to exerting extra control over your middleware pipeline.
Middleware vs HTTP Modules and HTTP Handlers
In the previous version of ASP.NET, the concept of a middleware pipeline isn’t used. Instead, there are HTTP modules and HTTP handlers.
An HTTP handler is a process that runs in response to a request and generates the response. For example, the ASP.NET page handler runs in response to requests for .aspx pages. Alternatively, you could, for example, write a custom handler that returns resized images when an image is requested.
HTTP modules handle cross cutting concerns of applications, such as security, logging, or session management. They run in response to life-cycle events that a request progresses through when it’s received at the server. For example, there are events such as BeginRequest, AcquireRequestState, or PostAcquireRequestState.
This approach works, but it’s sometimes tricky to reason about which modules will run at which points. Implementing a module requires a relatively detailed understanding of the state of the request at each individual life-cycle event.
The middleware pipeline makes understanding your application far simpler. The pipeline is completely defined in code, specifying which components should run, and in which order.
This is all there is to the concept of middleware.