Guide to ASP.NET Core 8 Middleware and its Types

What is ASP.NET Core Middleware?

ASP.NET Core Middleware is a series of modular components that work together in a pipeline. These components act as intermediaries between incoming requests and the final response, allowing you to extend your application’s functionality in a flexible and organized way. Each middleware component can perform specific tasks like authentication, logging, or adding custom headers.

How to add Middleware?

Here are the different ways to add middleware in ASP.NET Core using C#:

Request Delegates

The simplest possible ASP.NET Core app sets up a single request delegate using app.Run or app.Use or app.Map directly within the Configure method of your Startup.cs file. Each middleware component in the pipeline can choose to short-circuit the pipeline (i.e. terminate the request-response cycle) or pass it on to the next component. When a middleware short-circuits, it’s called a terminal middleware because it prevents further middleware from processing the request.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.Use(async (context, next) =>
    {
      // Do work that can write to the Response.
      await next(context);
      // Do logging or other work that doesn't write to the Response.
    });

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

    app.Map("/map1", HandleMapTest1);
}

static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}
MethodDescription
app.UseAdds a middleware component to the pipeline. Each middleware component in the pipeline can choose to terminate the request-response cycle or pass it on to the next component.
app.RunAdds a terminal middleware, stopping the pipeline and directly handling the request.
app.MapMap extensions are used as a convention for branching the pipeline. Map branches the request pipeline based on matches of the given request path. If the request path starts with the given path, the branch is executed.

Inline Delegate

Another approach is defining the middleware logic using app.Use, directly within the Configure method of your Startup.cs file. app.Use adds a middleware component to the pipeline, allowing further processing by subsequent middleware or the endpoint.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  app.Use(async (context, next) =>
  {
    // Do work that can write to the Response.
    await next(context);
    // Do logging or other work that doesn't write to the Response.
  });
  // ... rest of your application configuration
}

Custom Middleware

For more complex middleware with reusable functionality, it’s recommended to create a separate class. This class should have:

  • A public constructor with a parameter of type RequestDelegate.
  • A public method named Invoke or InvokeAsync. This method must:
    • Return a Task.
    • Accept a first parameter of type HttpContext.

Additional parameters for the constructor and Invoke/InvokeAsync are populated by Dependency injection (DI).

using System.Globalization;

namespace Middleware.Example;

public class RequestCultureMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var cultureQuery = context.Request.Query["culture"];
        if (!string.IsNullOrWhiteSpace(cultureQuery))
        {
            var culture = new CultureInfo(cultureQuery);

            CultureInfo.CurrentCulture = culture;
            CultureInfo.CurrentUICulture = culture;
        }

        // Call the next delegate/middleware in the pipeline.
        await _next(context);
    }
}

Unlike the previous options, we need additional code to add this middleware to the ASP.NET Core pipeline. There are two main ways to to this:

Using app.UseMiddleware

This is the most common approach for adding middleware components to the pipeline. The app.UseMiddleware extension method takes a reference to a class that satisfies the conditions to be a Middleware or a class implementing the IMiddleware interface. Here’s an example:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ 

  // Add middleware using a class
  app.UseMiddleware<RequestCultureMiddleware>();

  // ... rest of your application configuration
}

Using Extension methods

For reusable middleware components, you can create an extension method that simplifies registration in the Configure method. Here’s an example:

public static class MiddlewareExtensions
{
  public static IApplicationBuilder UseLogging(this IApplicationBuilder builder)
  {
    builder.UseMiddleware<LoggingMiddleware>();
    return builder;
  }
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  app.UseLogging();
  // ... rest of your application configuration
}

Factory-Based Custom Middleware

A class that implements IMiddleware interface is called as Factory-Based middleware. A Middleware class that doesn’t implement this interface (discussed in the previous section) is called as Convention-Based Middleware. It utilizes IMiddlewareFactory instance during request processing and hence called as Factory-Based middleware. It leverages constructor injection within the factory context. This allows access to any service registered in the dependency injection container (including singletons, transients, and scoped services)

Factory-based middleware provides more flexibility and control over the middleware instantiation process. It allows you to create middleware instances with different constructors or dependencies, and it also enables you to share middleware instances across multiple requests if desired.

public class FactoryActivatedMiddleware : IMiddleware
{
    private readonly SampleDbContext _dbContext;

    public FactoryActivatedMiddleware(SampleDbContext dbContext)
        => _dbContext = dbContext;

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var keyValue = context.Request.Query["key"];

        if (!string.IsNullOrWhiteSpace(keyValue))
        {
            _dbContext.Requests.Add(new Request("Factory", keyValue));

            await _dbContext.SaveChangesAsync();
        }

        await next(context);
    }
}

We need additional code to add this middleware to the ASP.NET Core pipeline. The code is similar to Class based middleware except for additional code in the Configure method.

public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseFactoryActivatedMiddleware(
        this IApplicationBuilder app)
        => app.UseMiddleware<FactoryActivatedMiddleware>();
}    

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<FactoryActivatedMiddleware>();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    services.AddTransient<FactoryActivatedMiddleware>();
  // ... rest of your application configuration
}

Difference between Middleware and Endpoint

In ASP.NET Core, Middleware acts as a series of modular components forming a processing pipeline for incoming requests. These components can perform various tasks like authentication, authorization, logging, or request manipulation before passing the request on to the next middleware in the chain. Middleware can also terminate the pipeline (e.g. return 401 Unauthorized if user is not logged in). This allows for a flexible and extensible approach to handling requests with specific functionalities at each stage. A single instance of a middleware class might be used for multiple requests throughout the application’s lifetime, depending on the middleware type.

Endpoints represent the final destinations for requests within the ASP.NET Core application. They are mapped to specific URL patterns and HTTP methods (GET, POST, etc.) and define how the application responds to these requests. Endpoints handle the core application logic, which could involve database interaction, data manipulation, and ultimately generating the response sent back to the client. The endpoint logic is typically executed for each incoming request that matches its URL pattern and HTTP method.

Built-in Middlewares

ASP.NET Core provides numerous built-in middleware. Following are some of the commonly used middleware:

UseStaticFilesThe UseStaticFiles middleware allows you to serve static files, such as images, JavaScript, and CSS, from a specific directory within your ASP.NET application. This middleware component is typically used to serve files that don’t need to be processed by ASP.NET, such as client-side scripts and static images.
UseRoutingThe UseRouting middleware is responsible for extracting the route template and parameters from the incoming request. The route data is then used by later middleware components, such as the MVC middleware, to handle the request.
UseEndpointsThe UseEndpoints middleware is used to define the endpoints in your ASP.NET application. Endpoints are the entry points for handling HTTP requests, and they are typically mapped to controllers and methods in your application.
UseAuthenticationThe UseAuthentication middleware is responsible for authenticating the incoming requests. It uses the authentication schemes that you have configured in your ASP.NET application, such as cookies or bearer tokens, to validate the user’s identity.
UseAuthorizationThe UseAuthorization middleware is used to authorize the incoming requests. It checks the user’s identity and determines whether they have the necessary permissions to access the requested resource.
UseSessionThe UseSession middleware allows you to store and retrieve data from the current session, which is maintained on the server. This middleware is useful when you need to store data that is specific to the current user, such as their shopping cart or preferences.
UseCorsThe UseCors middleware is used to enable Cross-Origin Resource Sharing (CORS) in your ASP.NET application. This allows you to make requests from a different domain than the one hosting your application, which is useful in scenarios where you have a separate front-end application that needs to communicate with a back-end API.
UseResponseCompressionThe UseResponseCompression middleware allows you to compress the HTTP response that is sent to the client. This can help reduce the amount of data that needs to be transferred over the network, improving the performance of your ASP.NET application.

Difference between Middleware and Filter

Filters focus on a more granular level within the framework. They are specifically designed to work with controllers and actions, allowing developers to inject logic before or after the execution of specific controller actions. This can be useful for tasks like validation, authorization within specific actions, or handling exceptions. Filters provide a more targeted approach to enhancing application behavior.

Benefits of Middleware

  • Flexibility: With middleware, you can easily customize the request and response pipeline according to your application’s specific needs. Whether you want to authenticate users, handle errors, or log requests, middleware allows you to insert your custom logic at any point in the pipeline. This flexibility empowers developers to build applications that are tailored to the unique requirements of their projects.
  • Reusability: ASP.NET Core Middleware components can be easily shared across different applications or even between different teams working on the same codebase. This reduces development time and effort, as developers can leverage pre-built middleware to handle common tasks. Additionally, middleware can be configured to run in a specific order, allowing you to chain multiple middleware components together to form a seamless request processing flow.
  • Code separation and modularity: By encapsulating specific functionalities into middleware components, developers can maintain cleaner and more organized codebases. This separation of concerns allows for better code maintainability and promotes code reusability across different parts of the application.
  • Testability: Individual middleware components are easier to isolate and test, leading to more robust applications. Extensibility: The built-in middleware framework allows you to create custom middleware to cater to specific needs.
  • Extensibility: The built-in middleware framework allows you to create custom middleware to cater to specific needs

Summary

  • Modular Magic: Break down complex tasks into smaller, reusable pieces that process requests step-by-step. Think of an assembly line for your web app.
  • Extend Your App: Go beyond routing. Add features like authentication, logging, or request tweaks using middleware.
  • Choose Your Approach: Keep it simple with convention-based middleware for basic needs, or use factory-based for more control over dependencies.
  • Easy Registration: Keep your code clean when using reusable middleware components.
  • Middleware vs. Endpoints: Middleware prepares the ingredients (request data), while endpoints cook the final dish (response) based on the recipe (endpoint logic).

I hope this blog post gives you a starting point for understanding and utilizing middleware in your ASP.NET Core applications. Feel free to experiment and create your own custom middleware to extend the functionalities of your ASP.NET Core applications. Explore the official Microsoft documentation on ASP.NET Core middleware for a comprehensive reference.

Scroll to Top