Middleware in ASP.NET Core – Handling requests
Middleware is software that application assembles into the pipeline to handle requests and responses. Each part chooses whether to pass the request on to the next part in the pipeline, and can do certain actions before and after application invokes the next part in the pipeline. Request delegates usage is to build the request pipeline.
In ASP.NET we had modules, handlers, web.config and other. In ASP.NET Core we have middleware.
We set up middleware inside of our Startup class. One could say that ASP.NET Core steals heavily from the Node.js world (hint: Express.js). However, ASP.NET Core copied some of the good concepts from Node.js. Middleware is one of those. Another one is granular packages for everything and anything (MVC, Filters, Configurations, etc.).
Middleware controls how the application responds to HTTP requests. What response will it output? How does it act in case of an error? Is the user authenticated? Is the user allowed to use a specific resource?
To get a better understanding of middleware and where it fits into let’s talk about application startup process.
Application startup process
ASP.NET Core application starts inside of Program.cs. That is where everything gets started. After initial configuration is complete, Startup.cs is initiated, and we can use its constructor to inject some of the internal services and configure if needed. Then goes ConfigureServices method inside of Startup class. That is where we define services that our application will use, including MVC itself. After that comes Configure method. This is where we typically use IApplicationBuilder to set up middleware.
After initialization is complete, our app is ready to process requests. Once a request hits our server, it is middleware that takes over and handles it.
As we said, we define pieces of middleware inside of a Configure method of Startup class. Every piece of middleware does something with the request and has a specific role. Usually, we need many pieces of middleware for our application to work as we intended it to.
Middleware handles a request in order that we specified in Configure method. Therefore, in the example above the logger factory is the first middleware in the pipeline and it can read all data from the incoming HTTP request (path, query string, headers, cookies, etc.). Hence, it can log any information about the request. After that, it passes the request to next piece of middleware. In the example, it is a simple piece of middleware that will output the same message for every request.
When dealing with a request we use IApplicationBuilder. And we have four methods available to interact with a request:
Adds a middleware to the application pipeline and it can either pass the request to next delegate or it can end the request (short-circuit request pipeline). It is the most commonly used method to interact with middleware.
We use Map to connect a request path with another middleware. That middleware can use any of the other mentioned request delegates.
MapWhen behaves almost the same as Map except that we can specify the detailed condition by using a HttpContext object. We could check for URL, headers, query strings, cookies, etc).
In the example above HandleMapWhen method will execute only when we have query string category and when its value has more than 5 characters. And once HandleMapWhen method executes the app.Run() method inside of Configure will not execute. Since Run delegate ends the request pipeline.
Run is different when compared to others. Once application executes this middleware it will reach the end of the request pipeline. Consequently, after that response pipeline starts executing in reverse order.
If we set up the code like this:
First line of code that will execute is “In USE. Before RUN. …”. After that, application continues with next piece of middleware. That happens due to executing next.Invoke() and that means Run inside of Configure will get called. After application executes Run method it continues inside of Use and outputs “In USE. After RUN…”.
We can see this clearly in the console and in the browser:
Creating a simple middleware class
Lets move the code from the Use to a separate class:
For us to be able to use this middleware in our application we will have to make an extension method:
Now we can use this UseSampleMiddleware extension method inside of our code:
That looks much cleaner. We extracted specific code to separate method/class, which is always nice thing to do.
- Middleware forms the request handling pipeline
- Application configuration occurs inside of Program and Startup classes
- We can have as many middleware components as we deem necessary
- Order of the registered middleware components matters
- Middleware components can short-circuit the request and generate the request immediately
- Three ways of configuring the middleware pipeline:
- Run – Generate a response and short-circuit the request
- Use – Perform logic and send request to next part
- Map – Conditionally send the request to other middleware
- Middleware helps us to write lightweight and modular apps