Introduction

Middleware is a software component that processes HTTP requests and responses in the ASP.NET Core pipeline.

Each request passes through a series of middleware components before reaching the final endpoint (like a controller or API). After the endpoint processes the request, the response travels back through the same middleware pipeline.

Request Flow

Client Request
     ↓
Middleware 1 (Logging)
     ↓
Middleware 2 (Authentication)
     ↓
Middleware 3 (Routing)
     ↓
Controller / API
     ↑
Response travels back through middleware

Each middleware can:

Basic Middleware Example

Let’s see a simple middleware example.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    Console.WriteLine("Request started");

    await next();

    Console.WriteLine("Response completed");
});

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

app.Run();

Execution Flow

Console Output:

Request started
Response completed

Understanding Use Middleware

Use() middleware is used when we want to perform logic before and after the next middleware.

Example

app.Use(async (context, next) =>
{
    Console.WriteLine("Before next middleware");

    await next();

    Console.WriteLine("After next middleware");
});

Key Characteristics

This is commonly used for:

Understanding Run Middleware

Run() is a terminal middleware.

Once Run() executes, the pipeline stops and no other middleware is executed after it.

Example

app.Run(async context =>
{
    await context.Response.WriteAsync("Pipeline ended here");
});

If multiple Run() methods are written:

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

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

Output:

Hello

The second Run() will never execute.

Understanding Map Middleware

Map() is used to branch the middleware pipeline based on URL paths.

Example

app.Map("/admin", adminApp =>
{
    adminApp.Run(async context =>
    {
        await context.Response.WriteAsync("Admin page");
    });
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Main page");
});

Result

URLResponse
/Main page
/adminAdmin page

This approach is useful when we want different middleware pipelines for different routes.

Creating Custom Middleware

In real applications, middleware is usually created as a separate class.

Step 1: Create Middleware Class

public class LoggingMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        Console.WriteLine("Request received");

        await _next(context);

        Console.WriteLine("Response sent");
    }
}

Step 2: Register Middleware

app.UseMiddleware<LoggingMiddleware>();

Now every request will pass through this middleware.

Built-in Middleware in ASP.NET Core

ASP.NET Core provides several built-in middleware components.

Common examples include:

MiddlewarePurpose
UseRoutingDetermines the endpoint for a request
UseAuthenticationIdentifies the user
UseAuthorizationChecks access permissions
UseStaticFilesServes static files
UseExceptionHandlerHandles global errors

Example configuration:

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

Key Takeaways

Conclusion

Middleware is one of the most powerful features in ASP.NET Core. By structuring the request pipeline effectively, developers can handle cross-cutting concerns like authentication, logging, and error handling in a clean and maintainable way.

Understanding middleware and how to create custom components is essential for building scalable and maintainable web APIs.