Skip to content

What is Middleware in Dotnet

The initial release of what became known as Dotnet Core and what is now just known as Dotnet, introduced many new concepts and patterns that developers could leverage to develop modern web-based applications. One of the most interesting concepts, in my opinion is Middleware, which enables developers to run a series of components during web request and response processing.

What is Middleware

Middleware is a component or a module of code that handles incoming requests and outgoing responses. These components are chained together in such a way that each component in the pipeline gets the chance to run some logic or process requests before passing the request to the next Middleware in the pipeline. Each middleware also gets the chance to process the outgoing responses in reverse order. Which is very similar to Mediatr Pipeline behaviours, I discussed in How to use MediatR Pipeline Behaviours

Middleware are software components that are assembled into an application pipeline to handle requests and responses. Each component chooses whether to pass the request on to the next component in the pipeline, and can perform certain actions before and after the next component is invoked in the pipeline. Request delegates are used to build the request pipeline. The request delegates handle each HTTP request.

At its core ASP.net Core Request/Response pipeline is fundamentally an example of the Middleware in action, because its a pipeline that consists of a sequence of request delegates , all called after each other. Each delegate can perform operations before and after the next delegate. Exception-handling delegates should be called early in the pipeline, so they can catch exceptions that occur in later stages of the pipeline.

This diagram taken directly from Microsoft Documentation explains this concept.

ASP.net Core is a modular framework, which means it enables developers to either add or remove features and middleware as they require.

The core concept for dotnet developers to keep in mind about middle ware is that they are simply C# classes that can handle HTTP Requests and/or Responses. The key concepts they enable dotnet developers to implement are:

  • Handle incoming HTTP Requests by generating an HTTP Response
  • Process an incoming HTTP Request, modify it, and pass it onto the next piece of middle-ware in the pipeline
  • Process an outgoing HTTP Response, modify it, pass it on to either to the next middle-ware in the pipeline or the ASP.net core web server.

What are use cases of Middleware

There are a number of common and not so common use cases for Middleware, but all a commonly associated with common code that needs to be executed on every request or response irrespective of what resource has been requested or desired. Examples of such as the following:

  • Associating requests and responses with relevant users
  • Setting Culture, Environment or other variables for each request
  • Logging each request
  • Determining the type of request i.e. REST, Websocket or gRPC etc
  • Auditing and analytic gathering of requests

One of the most common use-cases for middleware is in the Authentication & Authorisation flow of a dotnet based Web API. In this contrived example we may separated out our Authentication and Authorisation middleware into separate classes each handling a specific functionality associated with the actions.

A request is made to access a resource, the Authentication middleware in the pipeline will validate the user credentials and if they are able to use the API then the request is forwarded to the Authorisation Middleware which will then determine whether the specified user account has sufficient authorisation to access the resource. If the account does not have the required authorisation then the request is not forwarded to the endpoint resource and the requisite response is generated.

A key concept to absorb from this contrived example use case is that the pipeline is bi-directional. The request passes through the pipeline in one direction until a middleware component generates a response, at which point the response passes back through the pipeline in the other direction, passing through each middleware component for a second time, until it gets back to the first middleware component. Finally, this first/last piece of middleware passes the response back to the ASP.NET Core web server.

How to develop Middleware in dotnet

Dotnet provides two ways to start developing a middleware component, which are commonly known as:

  • Convention based middleware approach - Requires the implementation of two conventions that must be applied that declare a constructor that takes a RequestDelegate as a parameter.
  • Strongly typed middleware approach - based on implementing the IMiddleware interface, which was introduced in  .NET Core 3.0.

Strongly typed middleware approach

The Dotnet makes developing new custom middleware using the strongly typed middleware components really simple. For the most part it may be as simple as creating a new class which implements the IMiddleware interface. Sticking with our contrived example as above, so we wanted to create our Authentication Middleware class, we could simply create a Authentication Middleware class and inherit the IMiddleware interface and implement the required methods of the interface as follows.

public class AuthenticationMiddleware : IMiddleware
{
    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        throw new NotImplementedException();
    }
}

The InvokeAsync method takes two arguments which are important when it comes to implementing Middleware components.

  • HttpContext - Stores data which is specific the request and the subsequent response associated with it.
  • RequestDelegate - represents the next piece of middleware in the pipeline, allowing requests to be passed along after one piece of middleware has completed its operations.

Conventions based approach

The convention based approach is the generally the approach used by a number of the existing Middleware components within the framework, only by virtue that it was the first and oldest approach defined in earlier versions of dotnet.

The two conventions that must be applied to a conventions-based middleware class are to declare a constructor that takes a RequestDelegate as a parameter representing the next middleware in the pipeline, and a method named Invoke or InvokeAsync that returns a Task and has at least one parameter, the first being an HttpContext.

public class AuthenticationMiddleware
{
    private readonly RequestDelegate _next;

    public AuthenticationMiddleware(RequestDelegate next) => _next = next;
    

    public async Task InvokeAsync(HttpContext context)
    {
        throw new NotImplementedException();
    }
}

The HttpContext Object

The HttpContext is automatically generated by the ASP.net Core webserver on the initial request and contains details of the original HTTP request and other configuration details, and passes it on to the rest of the application.

 if the request contained any user credentials, what resource the request was attempting to access, and to fetch any posted data. The middleware can use these details to determine how to handle the request.

We can expand on our example middleware component by injecting our Logger and implementing some really basic logic regarding the authentication. Obviously more logic is required for this method, but in its current state it has just enough logic required to illustrate the key concepts of middleware

public class AuthenticationMiddleware : IMiddleware
{
    private readonly ILogger<AuthenticationMiddleware> _logger;

    public AuthenticationMiddleware(ILogger<AuthenticationMiddleware> logger)
    {
     
        _logger = logger;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="context"></param>
    /// <param name="next"></param>
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        const string anonymous = "Anonymous";
        var user = context.User;
        var name = user.Identity?.Name ?? anonymous;

        if (string.IsNullOrEmpty(name) || name.Equals(anonymous, StringComparison.OrdinalIgnoreCase))
        {
            _logger.LogInformation("Invalid user attempt to access: {Name}", name);
            // Check to see if the response has already started
            if (context.Response.HasStarted) return;
            
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            await  context.Response.WriteAsync("Invalid user");

            return;
        }
        // TODO:  Add authentication logic here
        
        _logger.LogInformation("User {Name} is authenticated", name);
        await next.Invoke(context);
    }
}

Avoid

The code sample does not contain any actual functional code but is purely meant for illustrative purposes to provide introductory examples of the use of objects and other concepts available in Middleware

We have access to the constructor so we can inject any additional objects we may require for our middleware component to function. In this example we want to make use of the Logger that has been registered with in the DI container to log any important messages.

We make use of the HttpContext object to check if a user has been associated with this request. Obviously in a proper Authentication middleware this won't be the case, because we are actually handling the authentication process, but I just wanted to illustrate how you can access various properties associated with the request.

If the request does not have an Username associated with or we have an anonymous user request then we simply then we check to see if a Response object has already been started. This is important because if a Response object has already been started and we try to modify we could cause errors. If the response object has not already been started we set the Http Status code to 401 and simply write out invalid user to the response. Either way we then simply exit out the pipeline and do not pass the request onto the next component in the pipeline.

If we have a valid user and they can be authenticated then pass the request onto whichever middleware component has been defined in the pipeline.

The RequestDelegate

The RequestDelegate type represents any Task returning method, which has a single parameter of type HttpContext. A RequestDelegate can therefore operate on the HttpContext asynchronously.

ASP.NET Core request handling is achieved by chaining one or more RequestDelegate instances together. Each delegate may handle the request by writing to the response on the HttpContext. If the RequestDelegate is considered have fully completed the response, it can simply return. In cases, where it has not entirely handled the request, it may pass the HttpContext onto the next RequestDelegate in the chain.

Configuring the middleware pipeline

Once you have developed your Middleware component you can configure your application to make use of it. In the case of the example you want to register our component as a singleton class in our Dependency Container , so there will only be one instance of our class used.

It's important to remember that although using the Strongly Typed or the Conventions based approach enable you to achieve the same result when developing Middleware there are some subtle differences to be aware of when it comes to registering and configuring your middleware components.

Then we instruct our application to make use of the Middleware by using the UseMiddleware method.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

builder.Services.AddSingleton<AuthenticationMiddleware>();

var app = builder.Build();
app.UseMiddleware<AuthenticationMiddleware>();
// Configure the HTTP request pipeline.


app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Dependency Injection

Principles, Practices, and Patterns

teaches you to use DI to reduce hard-coded dependencies between application components. 

Conclusion

Middleware enables developers to write reusable code components that can be executed on every request within a pipeline. We briefly examined some key concepts to be familiar with when starting to develop Middleware and even implemented our first piece of middleware.

The key points to bare in mind regarding middleware are:

  • Middleware is defined as a collection of of classes which form a pipeline for handling request and responses in an ASP.net core application.

Middleware is one of the major reasons behind the success of ASP.NET core, providing numerous advantages to the developers, such as – real-time information flow, linear development process, information integrity, more control over the HTTP request/response, and improved software architecture.

Taking a deeper analyses of the Asp.net server code base you'll literally find hundreds of example of Middleware implemented within the framework itself.

Gary Woodfine
Latest posts by Gary Woodfine (see all)