For the next installment in our ASP.NET Core Demystified series, we will discuss of feature of ASP.NET Core that doesn't really have an equivalent feature in ASP.NET Framework: Middleware.

What Are Middleware?

Middleware are classes in ASP.NET Core which form a "pipeline" through which requests to the system and responses from the system flow. Each piece of middleware can handle requests or responses or both. Each piece of middleware can also control whether or not the request or response will "flow" to the next piece in the pipeline. In this way, we can create middleware to handle situations that shouldn't be handled by the core site, or situations (such as authorization) which need to be handled on every request.

Microsoft has an excellent image that demonstrates this behavior:

A flow diagram for middleware

Each piece of middleware can do something to the request or response, or even "short-circuit" the pipeline by immediately returning a response. For example, if you wanted an image-serving middleware, that would probably need to short-circuit the rest of the pipeline once it returned the requested image.

A (Very) Simple Pipeline

To get started creating some custom middleware, we must first create a "pipeline" which consists of pieces of middleware that are in use for our app.

A water pipeline running through a field.

We create said pipeline in our ASP.NET Core application by calling app.Run() and app.Use() in our Startup file. For example, let's see a very simple middleware setup:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Here's the last call in the pipeline.");
    });
}

The middleware pipeline ends at the first call to app.Run(). This particular example is rather silly, because we will never reach our MVC site's controllers. In fact, if we run this app, here's what we see in a browser:

A screenshot of the browser running the simple middleware example, showing no styling, nothing other than the sentence "Here's the last call in the pipeline."

Obviously this isn't what we're going to need in most situations. But to demonstrate what we will need, we first need to create some middleware.

Creating Middleware

Let's create two very simple middleware classes:

  1. The first piece will disallow POST actions.
  2. The second piece will add a custom header to the response.

What follows is the code for the first piece of middleware. Be sure to read the comments in the code!

public class AuthorizedPostMiddleware
{
    //The RequestDelegate represents the next middleware in the pipeline.
    private RequestDelegate _next;

    //Said delegate must be passed in from the previous middleware.
    public AuthorizedPostMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    //The Invoke method is called by the previous middleware 
    //to kick off the current one.
    public Task Invoke(HttpContext context)
    {
        //In this middleware, if the user is not authenticated 
        //and the request is a POST, we return 401 Unauthorized
        if(!context.User.Identity.IsAuthenticated && context.Request.Method == "POST")
        {
            context.Response.StatusCode = 401;
            return context.Response.WriteAsync("You are not permitted to perform POST actions.");
        }
  
        //Finally, we call Invoke() on the next middleware in the pipeline.
        return _next.Invoke(context);
    }
}

A pipeline isn't really a pipeline if it's only got one segment. Here's the code for the second piece of middleware:

public class CustomHeaderMiddleware
{
    private RequestDelegate _next;

    public CustomHeaderMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        //In this middleware, we will always return 
        // a response header called "Custom-Middleware-Value"
        await _next.Invoke(context);
        context.Response.Headers.Add("Custom-Middleware-Value", DateTime.Now.ToString());
    }
}

Now that we've got our custom middleware created, let's create the pipeline!

Setting Up the Pipeline

The middleware pipeline is actually created in the Startup.cs file's Configure() method. Here's a simplified Configure() method from the sample project:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...
    //If you want to invoke a piece of middleware but not end the pipeline, use app.Use()
    //This method chains middleware so that you can modify the response without immediately returning it.
    app.Use(async (context, next) =>
    {
        await next.Invoke();
        context.Response.Headers.Add("AppUseRan", "yes");
    });

    //Now let's add our custom middleware!
    app.UseMiddleware<AuthorizedPostMiddleware>();
    app.UseMiddleware<CustomHeaderMiddleware>();

    //Finally, the MVC Routing middleware establishes the routes needed to access our MVC application.
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Let's draw attention to that call to app.Use(). Remember how a call to app.Run() marks the end of the pipeline? If you want to insert middleware but don't want a full C# class and don't want to end the pipeline, you can use app.Use() to insert such middleware. The particular middleware we are using modifies the response to add another custom header called "AppUseRan".

So, we now have a three-part pipeline: AppUseRan -> AuthorizedPost -> CustomHeader.

Please note that the middleware pieces are executed in the order they are added to the pipeline for requests, and in reverse order for responses (just like the Microsoft diagram from earlier shows).

Tip: Method Extensions

Let's see a piece of the Startup file from earlier again:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...
    //Session is an example of built-in middleware.  In ASP.NET Core, unlike in ASP.NET Framework, you must
    //explicity enable Session in order to use it in your apps.
    app.UseSession();

    //Static File handlers are another example of built-in middleware.  They enable the serving of static files
    //(e.g. JS, CSS, images), and by default those files are served from the wwwroot folder.
    app.UseStaticFiles();
    ...
}

Both Session and Static File Handlers are built-in middleware, but the calls to include them in the pipeline are not using the default app.Run() or app.Use() calls. This is because these two packages instead use extension methods to enable including these middleware pieces.

Let's quickly create some extensions for our two middleware pieces.

public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseAuthorizedPost(this IApplicationBuilder app)
    {
        return app.UseMiddleware<AuthorizedPostMiddleware>();
    }

    public static IApplicationBuilder UseCustomHeaders(this IApplicationBuilder app)
    {
        return app.UseMiddleware<CustomHeaderMiddleware>();
    }
}

NOTE: IApplicationBuilder is an interface which creates the ASP.NET Core application being run, hence why our extensions need to use it to include middleware, just like the Startup file does.

The idea behind these extension methods is that having them slightly cleans up the Startup file, which can be quite a good thing for complex or complicated apps. However, they are optional, and adding layers of abstraction is not always the best solution, so use your best judgment.

Check Out the Sample Project!

As with all my tutorials, there is a sample project over on GitHub which shows all our custom middleware and a few tips and tricks (and even some addition items not mentioned in this post!). Go check it out!

Summary

Here's a few things to remember about middleware in ASP.NET Core.

  1. Middleware are classes that form a "pipeline" which handles requests and responses.
  2. The pipeline ends at the first call to app.Run().
  3. If you want to add pieces of middleware to the pipeline, use app.Use().
  4. You can (optionally) create extension methods to include your middleware into an instance of IApplicationBuilder.

As always, if you noticed something about middleware that you think would be useful to others, share in the comments!

Happy Coding!