Middleware is the new “pipeline” for requests in asp.net core. Each piece of middleware can process part or all of the request, and then either choose to return the result or pass on down to the next piece of middleware. In the full ASP.net Framework you were able to specify “HTTP Modules” that acted somewhat like a pipeline, but it was hard to really see how the pieces fit together at times.
Anywhere you would normally write an HTTP Module in the full ASP.net Framework is where you should probably now be using middleware. Infact for most places you would normally use MVC filters, you will likely find it easier or more convenient to use middleware.
This diagram from Microsoft does a better job than I could at seeing how pipelines work. Do notice however that you can do work both before and after the pass down to the next middleware. This is important if you wish to affect results as they are going out rather than results that are coming in.
Basic Configuration
The easiest way to get started with middleware is in the Configure method of your startup.cs file. In here is where you “chain” your pipeline together. It may end up looking something like the following :
public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
Now an important thing to remember is that ordering is important. In this pipeline the static files middleware runs first, this middleware can choose to pass on the request (e.g. not process the response in its entirety) or it can choose to push a response to the client and not call the next middleware in the chain. That last part is important because it will often trip you up if your ordering is not correct (e.g. if you want to authenticate/authorize someone before hitting your MVC action).
App.Use
App.Use is going to be the most common pipeline building block you will come across. It allows you to add on something to the response and then pass to the next middleware in the pipeline, or you can force a short circuit and force a return result without passing to the next handler.
To illustrate, consider the following
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.Use((context, next) => { //Do some work here context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); //Pass the request on down to the next pipeline (Which is the MVC middleware) return next(); }); app.Use(async (context, next) => { await context.Response.WriteAsync("Hello World!"); }); app.Use((context, next) => { context.Response.Headers.Add("X-Xss-Protection", "1"); return next(); }); }
In this example, the pipeline adds a response header of X-Content-Type-Options and then passes it to the next handler. The next handler adds a response text of “Hello World!” then the pipeline ends. In this example because we are not calling Next() we actually don’t pass to the next handler at all and execution finishes. In this way we can short circuit the pipeline if something hasn’t met criteria. For example if some authorization key is not present.
App.Run
You may see App.Run appear around the place, it’s like App.Use’s little brother. App.Run is an “end of the line” middleware. It can handle generating a response, but it doesn’t have the ability to pass it down the chain. For this reason you will see App.Run middleware at the end of the pipeline and no where else.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.Use((context, next) => { //Do some work here context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); //Pass the request on down to the next pipeline (Which is the MVC middleware) return next(); }); app.Use((context, next) => { context.Response.Headers.Add("X-Xss-Protection", "1"); return next(); }); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
App.Map
App.Map is used when you want to build a mini pipeline only for a certain URL. Note that the URL given is used as a “starts with”. So commonly you might see this in middleware for locking down an admin area. The usage is pretty simple :
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.Map("/helloworld", mapApp => { mapApp.Run(async context => { await context.Response.WriteAsync("Hello World!"); }); }); }
So in this example when someone goes to /helloworld, they will be given the Hello World! message back.
App.MapWhen
This is similar to Map, but allows for much more complex conditionals. This is great if you are checking for a cookie or a particular query string, or need more powerful Regex matching on URLs. In the example below, we are checking for a query string of “helloworld”, and if it’s found we return the response of Hello World!
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.MapWhen(context => context.Request.Query.ContainsKey("helloworld"), mapApp => { mapApp.Run(async context => { await context.Response.WriteAsync("Hello World!"); }); }); }
Building A Middleware Class
And after all that we going to toss it in the bin and create a middleware class! Middleware classes use quite a different syntax to all of the above, and really it just groups them all together in a simple pattern.
Let’s say I want to build a class that will add common security headers to my site. If I right click inside my .net core web project and select “Add Item”, there is actually an option to add a middleware class template.
If you can’t find this, don’t fear because there is absolutely nothing about using the template. It just provides the basic structure of how your class should look, but you can copy and paste it from here into a plain old .cs file and it will work fine. Our security middleware looks something like the following :
public class SecurityMiddleware { private readonly RequestDelegate _next; public SecurityMiddleware(RequestDelegate next) { _next = next; } public Task Invoke(HttpContext httpContext) { httpContext.Response.Headers.Add("X-Xss-Protection", "1"); httpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); httpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); return _next(httpContext); } } public static class SecurityMiddlewareExtensions { public static IApplicationBuilder UseSecurityMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware<SecurityMiddleware>(); } }
Very simple. In our invoke method we do all the work we want to do and pass it onto the next handler. Again, we can short circuit this process by returning an empty Task instead of calling next.
Another thing to note is that this is instantiated using the service container. That means if you have a need to access a database or call in settings from an external source, you can simply add the dependency in the constructor.
Important : App middleware is constructed at startup. If you require scoped dependencies or anything other than singletons, do not inject your dependencies into the constructor. Instead you can add dependencies into the Invoke method and .net core will work it out. Again, if you need scoped dependencies add these to your Invoke method not your constructor.
The Extensions part is optional, but it does allow you to write code like this :
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseMiddleware<SecurityMiddleware>(); //If I didn't have the extension method app.UseSecurityMiddleware(); //Nifty encapsulation with the extension }
Very helpful, thank you. I noticed some typos.
1. e.g. if you with to authenticate/authorize
That should say “if you want to authenticate/authorize”
2. do not inject your dependencies into the instructure. Instead the you can add dependencies
This should say “do not inject your dependencies into the constructor. Instead you can add dependencies”
Thanks 🙂
🙂 Thanks. Fixed!