How to create a Custom Middleware in ASP.NET Core

Introduction

When you are developing an ASP.NET Core application, chances are that you have already been using middleware components.

You want to add authentication to secure your resources, authorize access, implement CORS policies, enforce HTTPS, or even cache responses – you will add the respective built-in libraries to the web application.

These are all exposed by the ASP.NET Core framework as Middlewares – pieces of code blocks that are responsible for a single functionality.

In this article, let us learn what is a Middleware and the Middleware architecture at a glance. We will then discover in how many ways we can create our own custom middleware components using the .NET library.

What is a Middleware Component in ASP.NET Core?

A middleware is a piece of code that runs on the request before it is handled. This piece of code can do anything – log the request details, modify the request, authenticate or authorize the request, or even break its lifecycle and return an early response back.

A middleware can do two things –

  1. Can decide whether to pass the request to the next component in the pipeline or break it
  2. Can read and modify a request before and after it is passed on to the next component

Middleware components are like Lego pieces for an ASP.NET Core application. They work together to create what we call the Middleware architecture of ASP.NET Core.

Middleware Architecture of ASP.NET Core

A typical .NET Core application consists of a series of middleware blocks that are chained together in order. Every functionality is built and added in the form of a middleware.

You add a middleware for Authentication, Authorization, Caching, CORS handling, Static Resources, etc. Even the Web API routing works in the form of an Endpoint middleware that maps an incoming request to its matching controller action.

Internally, each middleware component calls the next component in line using a RequestDelegate object that is given to each middleware.

What is a Request Pipeline?

These middleware components are linked together to create the request flow – the route that every request must follow before reaching its designated controller action. This chain of middlewares that are invoked before and after a request is processed is called a Request Pipeline.

The order of execution of middlewares

The middleware blocks are run in the order they are added to the WebApplicationBuilder object when a request is made. However, they are run in the opposite order for the response.

Order of Execution of Middlewares. Credits: MS Documentation

Implementing Custom Middleware components in ASP.NET Core

A custom middleware is a component delegate that runs our own business logic for every incoming request.

Based on how a middleware component behaves, we can classify them into 3 types –

  1. delegates which can branch request into a new request pipeline
  2. delegates which can terminate a request flow and not call the next components
  3. delegates which can run a piece of business logic and rejoin to the original pipeline

You can create a custom piece of code and chain that to the request pipeline in 3 different ways – Map, Run and Use extension methods on the WebApplicationBuilder object.

Map and MapWhen extension methods

You can use the Map extension method to branch out the request pipeline based on the request path and return a response as required.

When the request satisfies a branch, it gets into that branch pipeline and doesn’t come back to the original pipeline.

app.Map("/testbranchone", (applicationBuilder) =>
{
    // runs a separate branch pipeline and executes all the middlewares
    // that are added to this pipeline
    // original pipeline is not picked anymore

    applicationBuilder.Run(async context =>
    {
        await context.Response.WriteAsync("This is a response generated from test branch pipeline one");
    });
});

app.Map("/testbranchtwo", (applicationBuilder) =>
{
    // runs a separate branch pipeline and executes all the middlewares
    // that are added to this pipeline
    // original pipeline is not picked anymore

    applicationBuilder.Run(async context =>
    {
        await context.Response.WriteAsync("This is a response generated from test branch pipeline two");
    });
});

app.Run(async context =>
{
    await context.Response.WriteAsync("This is a response from the original pipeline");
});

app.Run();

You can use the MapWhen extension method to create a middleware code that runs conditionally based on any Boolean logic. For example, when you want to run a middleware code when the request path contains a particular parameter or matches a criteria.

app.MapWhen(context => context.Request.Query.ContainsKey("branch"), (applicationBuilder) =>
{
    // runs a separate branch pipeline and executes all the middlewares
    // that are added to this pipeline
    // original pipeline is not picked anymore

    applicationBuilder.Run(async context =>
    {
        await context.Response.WriteAsync("This is a response generated from branched pipeline");
    });
});

By using a Map or Run method, you can create an inline middleware – a simple middleware block that runs a piece of logic.

Run extension method

Run method creates a circuit-breaker middleware. It returns a request from within and doesn’t call the next request delegate. Hence It is called a terminal middleware.

Any middleware that is added to the chain after a Run method is not called.

applicationBuilder.Run(async context => {
    // Run method doesn't call the next Delegate
    // Hence request terminates here and response is generated
    await context.Response.WriteAsync("This is a response generated from Run");
});

// no request reaches till here
// no controller route will be called
applicationBuilder.MapDefaultControllerRoute();
Use and UseWhen extension methods

You can create an inline middleware code with the Use() extension method. This is a shorthand notation, where you can add a piece of code that must run on every request and should be attached to the pipeline.

The feature here is that you can run a delegate and rejoin to the original pipeline, unlike the Map or Run methods discussed above.

app.Use(async (httpContext, nextRequestDelegate) => {
    // middleware block
    Console.WriteLine("Inline Middleware Code block executed.");
    Console.WriteLine("Use() method continues in the same pipeline and calls the next Middleware in the chain.");

    await nextRequestDelegate.Invoke();
});

Similar to MapWhen, you can create a conditional middleware using the UseWhen extension method. It creates a middleware that is executed when a certain criteria is satisfied.

For example, you can run a middleware only when the request path contains a certain string. The difference is that while the MapWhen method branches out the pipeline into a separate one, the UseWhen method can rejoin to the original pipeline when there is no terminal middleware.

app.UseWhen(context => context.Request.Path.Contains("branch"), async (httpContext, nextRequestDelegate) => {

    // middleware block
    Console.WriteLine("Request Path contains the string 'branch'");
    Console.WriteLine("Hence, Inline Middleware Code block executed.");
    Console.WriteLine("UseWhen() method continues in the same pipeline and calls the next Middleware in the chain.");

    await nextRequestDelegate.Invoke();
});

We can use Map, Run or Use extension methods to add simple functionalities as delegates to the request pipelines. To create components that need to handle complex functionalities, it is recommended to create a custom reusable class that can be added to the request pipeline as a Middleware component.

How to create an ASP.NET Core Custom Middleware Class

A custom middleware is a simple concrete class with a public constructor. It must contain the following two pieces to be added to the request pipeline –

  • A Public constructor that has RequestDelegate as a parameter
  • A public InvokeAsync method that has HttpContext as an argument

You will inject a RequestDelegate instance inside the constructor and then give an implementation of the InvokeAsync() method where you will call the delegate.

The InvokeAsync() method is called for every request and it contains the functionality that the middleware is expected to run.

You will then register this class as a middleware by calling the IApplicationBuilder.UseMiddleware() method where you will pass the custom middleware as a type parameter.

To demonstrate, the following is a simple asp.net core custom middleware example that checks the request headers for a Key and then adds the Value to the Context Dictionary.

public static class KeyConstants
{
    public const string CUSTOM_REQUEST_HEADER_KEY = "X-My-Request-ID";
    public const string CUSTOM_REQUEST_CONTEXT_ITEMS_KEY = "xMyRequestId";
}

public class AddRequestHeaderValueToContextItems
{
    private readonly RequestDelegate _nextRequestDelegate;

    public AddRequestHeaderValueToContextItems(RequestDelegate nextRequestDelegate)
    {
        _nextRequestDelegate = nextRequestDelegate;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        Console.WriteLine("AddRequestHeaderValueToContextItems is now Invoked");

        var requestHeaders = httpContext.Request.Headers.ToDictionary(
            x => x.Key,
            x => x.Value.ToString()
        );

        if (requestHeaders.ContainsKey(KeyConstants.CUSTOM_REQUEST_HEADER_KEY))
        {
            var myCustomRequestHeaderValue = requestHeaders[KeyConstants.CUSTOM_REQUEST_HEADER_KEY];
            httpContext.Items.Add(KeyConstants.CUSTOM_REQUEST_CONTEXT_ITEMS_KEY, myCustomRequestHeaderValue);
        }

        await _nextRequestDelegate.Invoke(httpContext);
    }
}

In the above code snippet, for every incoming request the pipeline executes the InvokeAsync() method and here we are checking for the existence of a custom Header Key.

If present we will place that in the Context.Items Dictionary. As with other middleware, we will call the RequestDelegate.Invoke() method that calls the next delegate in the chain.

We will register this class in the request pipeline as below –

// middleware added to the pipeline
app.UseMiddleware<AddRequestHeaderValueToContextItems>();
app.Run();

Buy Me A Coffee

Enjoyed my content and found it useful?
Consider buying me a coffee to support my work and keep the content flowing!

Conclusion

Middleware architecture is introduced in the later versions of .NET Framework and is a standard in .NET Core.

We can use middlewares to examine and modify request data or can short-circuit requests without having to be executed by their respective handlers.

In ASP.NET Core we can use the built-in middlewares to add several basic and important functionalities to the request handling pipeline such as Authentication, Authorization, CORS, Static Files, Response Caching etc.

We can also create custom delegates that run on every request in the pipeline in different ways using extension methods – Use, Map and Run.

We can use these extension methods to create inline middlewares that run a piece of functionality.

For complex logic and dependency injection we can create concrete classes and then register them to the pipeline using the UseMiddleware extension method.

Checkout my new ebook Exploring ASP.NET Core Middlewares – A Complete Guide for Developers – I will explain everything you need to know about working with Middleware components in ASP.NET Core!!😁

Sriram Mannava
Sriram Mannava

A software Engineer from Hyderabad, India with close to 9 years experience as a full stack developer in Angular and .NET space.

Articles: 5

One comment

Comments are closed.