Middleware in ASP.Net Core

Middleware in ASP.Net Core is software components that are assembled into the HTTP pipeline to handle requests and response. A middleware component can either choose to pass the request to the next component in the pipeline. Or it may choose to end the request. Middleware can perform tasks both before as well as after the next component.

The following diagram demonstrates how the HTTP request pipeline consists of a sequence of middleware (or request delegate). Each of them calls one after the other in a sequence.

middleware in asp.net core
Pictorial representation of middleware in ASP.Net Core

Middleware is nothing but requests delegates which builds the request pipeline. In other words, we can chain requests delegates one after another to build the HTTP pipeline. And each of these requests delegates works with HTTP requests.

Compare with IHttpHandler and IHttpModule

In legacy .Net we have a couple of concepts that are similar to middleware in ASP.Net Core. So it is worth mentioning them.

The first one is IHttpModule, which is similar to a middleware. It sits in the pipeline of every request and can process or react to the request. It does not rely on any file extension. IHttpModule can stop the further process of the request and return response back.

The second one is the IHttpHandler. It is associated to a specific file extension. For example, if we want to do some specific processing for a .txt file extension, we will use IHttpHandler to achieve this. IHttpHandler is the last stop, the request does not go beyond an IHttpHandler. And we can have only one IHttpHandler for a file extension. Whereas we can have multiple IHttpModule on the pipeline.

Both IHttpHandler and IHttpModule configuration is in the Web.config file.

IHttpHandler/IHttpModule
Pictorial representation of legacy ASP.Net HTTP pipeline

How to configure middleware in ASP.Net Core

We can configure middleware using Use, Run or the Map extension method of the IApplicationBuilder instance. We can declare the middleware in-line using an in-line anonymous delegate. Or we can define it as a reusable class.

Each middleware component is responsible for calling the next middleware in the pipeline. Or it can just return the response hence short-circuiting the call. We call a middleware a terminal middleware if it short-circuits a request pipeline.

Simplest ASP.Net Core application with Run extension method

We will create the simplest ASP.Net Core application using a middleware. Here we will use the Run extension method on the IApplicationBuilder instance to achieve this.

Firstly, I will create a new ASP.Net Core Web API application using Visual Studio 2019 (You can use IDE of your choice). I will enable the Docker container for the application.

Secondly, once the project is ready, I will update the Startup class and remove all the auto-generated code. And put the below code there.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.Run(async context =>
            {
                await context.Response.WriteAsync("Hello, World!");
            });
        }
    }
}

In the above example, I have a single request delegate or a middleware. And it is writing “Hello, World!” for every single HTTP request that this application receives.

Finally, I will run the application, and I can see the expected output.

simple middleware in asp.net core
Simplest middleware output

Run extension method of the IApplicationBuilder instance always terminates request pipeline. Meaning, if we add another pipeline after the Run extension method, it will never be executed.

For example, if I update my Startup class to add two Run statements as below. After I compile and run the application, I will still see one response in the browser. And it will print output from the first Run statement, which is “Hello, World!”.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.Run(async context =>
            {
                await context.Response.WriteAsync("Hello, World!");
            });

            app.Run(async context =>
            {
                await context.Response.WriteAsync("Another Hello, World!");
            });
        }
    }
}

Chaining requests with Use extension method

If we have to execute multiple middlewares or request delegates, we will need to use the Use extension method. To demonstrate that, I will update the code block above. I will change the first request delegate to use the Use extension method.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("Hello, World!");

                await next();
            });

            app.Run(async context =>
            {
                await context.Response.WriteAsync("Another Hello, World!");
            });
        }
    }
}

In the above code, the next parameter is the handle to the next middleware or request delegate. I will call it to execute the next middleware in the pipeline.

Now, I will run the application, and I will see both the sentences are available in the browser.

Chained middleware with Use

Here, I can perform tasks both before and after calling the next middleware. In the above code, I am just doing it before. Now I will change it to do it both before and after calling the next middleware.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("Hello, World!");

                await next();

                await context.Response.WriteAsync("<br>This is after another Hello, World!");
            });

            app.Run(async context =>
            {
                await context.Response.WriteAsync("<br>Another Hello, World!");
            });
        }
    }
}

I added a break statement before the two statements to make the output look clean. But this is not necessary.

Now if I run the application, I will see all the statements are printing in order as expected.

middleware chaining with Use
Middleware with both before and after the statement

In the above example, if we want to make the first middleware as a terminate middleware, we will just stop the call to the next delegate. In a real-life scenario, we will do that only for specific file extensions where we do not want other middleware to process the request.

I will update the code to demonstrate this.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("Hello, World!");

                await context.Response.WriteAsync("<br>This is after another Hello, World!");
            });

            app.Run(async context =>
            {
                await context.Response.WriteAsync("<br>Another Hello, World!");
            });
        }
    }
}

If I run the above code, I will not see the text “Another Hello, World!” in the output as expected.

terminal middleware
Terminal middleware

Conditional middleware with UseWhen extension method

There are scenarios when we want to execute a request based on a condition. For example, based on a query string or HTTP header value. In those cases, we will use UseWhen extension method.

Inside the UseWhen method, the code execution path behaves exactly like Use. It gets a handle to the next middleware which we will use to call the next middleware in the pipeline. Or we may decide to make this middleware a terminal middleware and not call the next middleware in the pipeline.

In the below code example, I will demonstrate a scenario where we will execute a middleware based on the query string. If the query contains a key “role”, it will execute a middleware to handle the query string.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using System;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.UseWhen(context => context.Request.Query.ContainsKey("role"),
                a => a.Use(async (c, next) => {
                    var role = c.Request.Query["role"];
                    Console.WriteLine($"Role passed {role}");
                    // Do work that doesn't write to the Response.
                    await next();
                }));

            app.Use(async (context, next) =>
            {
                Console.WriteLine("Inside second middleware");
                await context.Response.WriteAsync("Hello, World!");

                await context.Response.WriteAsync("<br>This is after another Hello, World!");
            });

            app.Run(async context =>
            {
                await context.Response.WriteAsync("<br>Another Hello, World!");
            });
        }
    }
}

Now when we run the application with a query string of “role=test”, we will see that is getting print in the console. And immediately after that, we will see the print statement from the next middleware as expected.

Note: Unlike previous examples, we cannot use Write statement to test this.

UseWhen output in Console

Specific request path with Map extension method

As the name suggests, the Map extension method on the IApplicationBuilder maps a specific path to a middleware. It creates a new branch for the request. Meaning the new branch will use the Run or Use extension method to execute the request.

In the below code example, I will create a new branch of request execution for a “/newbranch” request path. When we make a request to this path, only the branch associated with this path will execute the code.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.Map("/newbranch", a => a.Run(c => c.Response.WriteAsync("Running from the newbranch branch!")));

            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("Hello, World!");

                await context.Response.WriteAsync("<br>This is after another Hello, World!");
            });

            app.Run(async context =>
            {
                await context.Response.WriteAsync("<br>Another Hello, World!");
            });
        }
    }
}

If I run the above code and request path “https://localhost:32768/newbranch”, I will see only the text “Running from the newbranch branch!” as expected. Whereas for any other path, I will see the result as in previous output.

Below table demonstrates the scenarios:

RequestOutput
https://localhost:32768/weatherforecastHello, World!
This is after another Hello, World!
https://localhost:32768/ Hello, World!
This is after another Hello, World!
https://localhost:32768/newbranchRunning from the newbranch branch!

Below is the output from the application when we access the “/newbranch” URL.

Middleware in ASP.Net Core with Map
Middleware with Map
Nesting with Map extension method

The Map extension method supports nesting. If we want to branch out different segments of a request we can do so with nested mapping.

For example, if we want to execute /newbranch/branch1 in a separate branch compared to /newbranch/branch2, we can do so using nested Map.

The code below demonstrates the nest Map feature.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.Map("/newbranch", a => {
                a.Map("/branch1", brancha => brancha
                    .Run(c => c.Response.WriteAsync("Running from the newbranch/branch1 branch!")));
                a.Map("/branch2", brancha => brancha
                    .Run(c => c.Response.WriteAsync("Running from the newbranch/branch2 branch!")));

                a.Run(c => c.Response.WriteAsync("Running from the newbranch branch!"));
        });

            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("Hello, World!");

                await context.Response.WriteAsync("<br>This is after another Hello, World!");
            });

            app.Run(async context =>
            {
                await context.Response.WriteAsync("<br>Another Hello, World!");
            });
        }
    }
}

When we run the above code and browse to “https://localhost:32768/newbranch/branch1”, we will see the text “Running from the newbranch/branch1 branch!” in the output. Whereas if we browse to “https://localhost:32768/newbranch/branch2”, we will see the output “Running from the newbranch/branch2 branch!”. Finally, if we browse to “https://localhost:32768/newbranch/”, we will see the output “Running from the newbranch branch!” as before.

middleware in asp.net core with multiple branch
Multiple branches with Map

The Map extension method can also match multiple segments of the URL. Meaning if we do not have branch1 and branch2 in the previous example. And just have one branch with /newbranch/branch1, we can use Map to branch out this URL.

The below code demonstrates this example. To preserve the existing code, instead of using newbranch, I will use a new segment named newsegment and segment1, URL /newsegment/segment1.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.Map("/newsegment/segment1", a => 
                a.Run(c => c.Response.WriteAsync("Running from the /newsegment/segment1 branch!")));

            app.Map("/newbranch", a => {
                a.Map("/branch1", brancha => brancha
                    .Run(c => c.Response.WriteAsync("Running from the newbranch/branch1 branch!")));
                a.Map("/branch2", brancha => brancha
                    .Run(c => c.Response.WriteAsync("Running from the newbranch/branch2 branch!")));

                a.Run(c => c.Response.WriteAsync("Running from the newbranch branch!"));
            });

            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("Hello, World!");

                await context.Response.WriteAsync("<br>This is after another Hello, World!");
            });

            app.Run(async context =>
            {
                await context.Response.WriteAsync("<br>Another Hello, World!");
            });
        }
    }
}

When I run the application and browse the URL “https://localhost:32768/newsegment/segment1”, I will see the text “Running from the /newsegment/segment1 branch!” in output as expected.

multi segment middleware
Middleware with multiple segments branch

In the previous example, if we just browse to “https://localhost:32768/newsegment” we will see output from the default middleware in our code. Which will print the following message in the browser output “Hello, World! This is after another Hello, World!”.

Conditional branching with MapWhen extension method

There are scenarios when we want to branch out based on a condition. For example, based on a query string or HTTP header value. In those cases, we will use MapWhen extension method.

Let us consider a scenario, if the query string of a request contains “role”, we want to branch it out differently, compared to other requests. The below code demonstrates how we can achieve this.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.MapWhen(c => c.Request.Query.ContainsKey("role"), a =>
                a.Run(async context =>
                {
                    var role = context.Request.Query["role"];
                    await context.Response.WriteAsync($"Role received {role}");
                }));

            app.Map("/newsegment/segment1", a => 
                a.Run(c => c.Response.WriteAsync("Running from the /newsegment/segment1 branch!")));

            app.Map("/newbranch", a => {
                a.Map("/branch1", brancha => brancha
                    .Run(c => c.Response.WriteAsync("Running from the newbranch/branch1 branch!")));
                a.Map("/branch2", brancha => brancha
                    .Run(c => c.Response.WriteAsync("Running from the newbranch/branch2 branch!")));

                a.Run(c => c.Response.WriteAsync("Running from the newbranch branch!"));
            });

            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("Hello, World!");

                await context.Response.WriteAsync("<br>This is after another Hello, World!");
            });

            app.Run(async context =>
            {
                await context.Response.WriteAsync("<br>Another Hello, World!");
            });
        }
    }
}

When we execute the above code, no not matter which URL we navigate to. If the query string contains key “role” then the new middleware will execute.

middleware with MapWhen
MapWhen with query string

Order of middlewares

The order of middleware is simple, it is as we add them in the Startup class’s Configure method. Ordering of the middleware matters for some of the middleware components. For example, authentication and authorization middleware must go in the order of authentication first and then authorization.

Middlewares available out of box

There are many middlewares provided out of the box. For example Authentication, Authorization, CORS, etc. The complete is list is available in the reference section at the end of this post.

All the out of the box middlewares comes with their own extension methods. Let us try to create an extension method similar to the out of box middlewares.

For example, if we need a middleware which will process our custom logic, we can create a new class with name MiddlewareExtension with an extension method UseCustomExtension. And use it inside of the Startup class.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

namespace Middleware.Demo
{
    public static class MiddlewareExtension
    {
        public static void UseCustomExtension(this IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("Custom logic before next middleware");
                await next();
                await context.Response.WriteAsync("<br>Custom logic after next middleware");
            });
        }
    }
}

Below code contains the change to the Startup.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace Middleware.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.UseCustomExtension();

            app.Run(async context =>
            {
                await context.Response.WriteAsync("<br>Another Hello, World!");
            });
        }
    }
}

When we run this application, we will get the output from the newly created middleware as expected.

custom extension method
The output from the custom extension method middleware

Custom middleware in ASP.Net Core

To add a custom middleware, we will use the UseMiddleware extension method of the IApplicationBuilder instance.

To create a new custom middleware, all we have to do is to create a class with a constructor which needs RequestDelegate as a parameter. And a public method with return type Task and name as Invoke with parameter HttpContext.

using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace Middleware.Demo
{
    public class CustomMiddleware
    {
        private readonly RequestDelegate next;

        public CustomMiddleware(RequestDelegate next)
        {
            this.next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            await httpContext.Response.WriteAsync("Inside of new custom middleware");
            await next(httpContext);
        }
    }
}

Once I create this new CustomMiddleware class, I will update the UseCustomExtension method I created before to use this custom middleware.

using Microsoft.AspNetCore.Builder;

namespace Middleware.Demo
{
    public static class MiddlewareExtension
    {
        public static void UseCustomExtension(this IApplicationBuilder app)
        {
            app.UseMiddleware<CustomMiddleware>();
        }
    }
}

Finally, I will run this application and will see the print from the new custom middleware in the output.

custom middleware in asp.net core
Custom middleware output

Conclusion

In conclusion, middleware in ASP.Net Core is an extremely powerful concept. In my previous blogs about Authorization and Authentication, I have used built-in middleware.

Middleware in ASP.Net Core makes horizontal responsibility implementation very clean. Making separation of concern for orthogonal responsibilities very easy to implement.

Reference:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1