The Middleware Pipeline is one of the most important concepts in ASP.NET Core. It defines how HTTP requests and responses are handled inside an application. Every request flows through a sequence of components called middleware, and each middleware can inspect, modify, or terminate the request before passing it to the next component in the pipeline.

Understanding middleware is essential for building secure, scalable, and high-performance web applications. Features such as authentication, logging, error handling, routing, and compression are all implemented using middleware.

Middleware-Pipeline

What is Middleware?

Middleware is a software component that:

Each middleware:

  1. Executes code before calling the next middleware.

  2. Calls the next delegate (optional).

  3. Executes code after the next middleware finishes.

How the Middleware Pipeline Works

When a request arrives:

  1. It enters the first middleware.

  2. The middleware decides to:

    • Process the request and pass it further, OR

    • Short-circuit the request and return a response directly.

  3. The response travels backward through the middleware chain.

This forms a pipeline or chain of execution.

Basic Pipeline Example

In Program.cs:

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

app.Use(async (context, next) =>
{
    Console.WriteLine("Middleware 1: Before");
    await next();
    Console.WriteLine("Middleware 1: After");
});

app.Use(async (context, next) =>
{
    Console.WriteLine("Middleware 2: Before");
    await next();
    Console.WriteLine("Middleware 2: After");
});

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

app.Run();

Output Order

Code
Middleware 1: Before
Middleware 2: Before
Hello from endpoint
Middleware 2: After
Middleware 1: After

This shows request going forward and response coming backward.

Types of Middleware

There are three main types:

1. Built-in Middleware

ASP.NET Core includes many built-in middlewares:

Example

Code
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseRouting();

2. Inline (Anonymous) Middleware

Used directly with app.Use():

Code
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Before Next\n");
    await next();
    await context.Response.WriteAsync("After Next\n");
});

Useful for logging, validation, and debugging.

3. Custom Middleware Class

Create a middleware class:

Code
public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        Console.WriteLine($"Request: {context.Request.Path}");
        await _next(context);
    }
}

Register it:

Code
app.UseMiddleware<RequestLoggingMiddleware>();

Use, Run, and Map

ASP.NET Core provides three ways to add middleware.

Use()

Calls the next middleware:

Code
app.Use(async (context, next) =>
{
    Console.WriteLine("Before");
    await next();
    Console.WriteLine("After");
});

Run()

Terminates pipeline and does not call next:

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

Map()

Branches pipeline based on path:

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

Ordering Matters

Middleware order significantly affects how the application behaves:

Incorrect Order:

Code
app.UseAuthorization();
app.UseAuthentication(); // WRONG

Correct Order:

Code
app.UseAuthentication();
app.UseAuthorization();

Authentication must come before Authorization.

Another example:

Code
app.UseStaticFiles();
app.UseRouting();

If static files were after routing, they may never execute correctly.

Short-Circuiting the Pipeline

A middleware can stop further execution:

Code
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/blocked")
    {
        context.Response.StatusCode = 403;
        return;
    }

    await next();
});

Error Handling Middleware

Global exception handling:

Code
app.UseExceptionHandler("/error");

Custom global error middleware:

Code
app.Use(async (context, next) =>
{
    try
    {
        await next();
    }
    catch(Exception ex)
    {
        context.Response.StatusCode = 500;
        await context.Response.WriteAsync("Internal Server Error");
    }
});

Adding Middleware via Extensions

Better reusable pattern:

Code
public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder app)
    {
        return app.UseMiddleware<RequestLoggingMiddleware>();
    }
}

Use:

Code
app.UseRequestLogger();

Dependency Injection in Middleware

Middleware fully supports DI:

Code
public class AuditMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<AuditMiddleware> _logger;

    public AuditMiddleware(RequestDelegate next, ILogger<AuditMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        _logger.LogInformation("Request incoming...");
        await _next(context);
    }
}

Middleware vs Filters

MiddlewareFilters
Applies to all requestsMVC-only
Cross-cutting concernsController logic
Runs outside MVCRuns inside MVC
Used for security/loggingUsed for API behavior

Common Use-Cases

Middleware is used for:

Best Practices

✅ Keep middleware simple
✅ Order pipeline carefully
✅ Avoid heavy logic in middleware
✅ Handle exceptions globally
✅ Use custom middleware for cross-cutting concerns
✅ Use Map() for path branching
✅ Prefer extension methods for reusability

When to Use Middleware

Use middleware when the logic:

✔ Applies globally
✔ Is request/response based
✔ Is infrastructure-related
✔ Should run before controllers
✔ Is reusable across APIs or services

Conclusion

The middleware pipeline is the backbone of ASP.NET Core request processing. Every feature you rely on beneath the surface—security, logging, routing, error handling—depends on middleware working together efficiently.

By mastering middleware, you take control over how requests enter, travel through, and exit your application. Whether you are building APIs, microservices, SaaS platforms, or enterprise solutions, middleware design directly impacts performance, maintainability, and security.

A well-ordered middleware pipeline leads to predictable behavior, cleaner code, and better system reliability.