ASP.NET Core in Action - What is middleware?

In February 2017, the Manning Early Access Program (MEAP) started for the ASP.NET Core book I am currently writing - ASP.NET Core in Action. This post gives you a sample of what you can find in the book. If you like what you see, please take a look - for now you can even get a 37% discount with the code lockaspdotnet!

The Manning Early Access Program provides you full access to books as they are written, You get the chapters as they are produced, plus the finished eBook as soon as it’s ready, and the paper book long before it's in bookstores. You can also interact with the author (me!) on the forums to provide feedback as the book is being written.

There are currently 18 of the 20 chapters available in the MEAP, so now is the time to act if you're interested! Thanks 🙂

What is middleware?

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.

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.

In practice, you often don't have a dedicated authorization middleware, instead allowing the MvcMiddleware to handle the authorization requirements.

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.