Understanding Middleware in .NET Core

What is Middleware in ASP.NET Core?

If you're starting to learn ASP.NET Core, you've probably heard the word "middleware" quite a few times. It might sound complicated at first, but don’t worry — in this post, we’ll break it down into simple terms.

You’ll learn:

  • What middleware is

  • Why it's important

  • How it works

  • How to create your own

  • Best practices (and things to avoid)


What is Middleware in .NET Core?

Middleware is a piece of code that sits between the incoming HTTP request and the outgoing HTTP response in an ASP.NET Core app. It can:

  • Inspect or modify the request

  • Handle authentication or logging

  • Pass the request to the next step

  • Or even stop the request entirely

In simple words: Middleware is like a checkpoint that performs some logic on every request before it reaches your controller or API.


Real-Life Analogy: The Airport

Imagine you're at an airport going through various checks:

  • Security Check (Middleware 1): Checks your ID

  • Baggage Scan (Middleware 2): Scans your luggage

  • Customs (Middleware 3): Verifies documents

Only if you clear all these checks are you allowed to board the plane (the final endpoint).


How Middleware Works: The Request-Response Lifecycle

    

Here’s a simplified flow of how middleware works:

  1. Request enters the ASP.NET Core app.

  2. The first middleware runs its logic.

  3. It can:

    • Modify the request

    • Perform logic (like logging or authentication)

    • Pass it to the next middleware using await next()

  4. Eventually, a response is returned.

  5. Each middleware gets a chance to:

    • Modify the response

    • Log something

    • Handle any errors


Why Use Middleware?

Middleware helps you:

  • Handle authentication and authorization

  • Add logging and monitoring

  • Catch exceptions

  • Add caching or compression

  • Transform requests or responses

  • Keep your app modular and testable


Built-in Middleware in ASP.NET Core

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

Middleware
Description
UseRouting
    Matches request to endpoints
UseAuthentication
    Authenticates users
UseAuthorization
    Checks user permissions
UseStaticFiles                
    Serves static files
UseExceptionHandler
    Handles unhandled exceptions
UseCors
    Enables CORS
UseSession
    Enables session state
UseEndpoints
    Executes matched endpoint


Creating Custom Middleware

public class CustomMiddleware

{

    private readonly RequestDelegate _next;  

    public CustomMiddleware(RequestDelegate next)

    {

        _next = next;

    }

    public async Task InvokeAsync(HttpContext context)

    {

        // Custom logic before next middleware

        Console.WriteLine("Request handled by custom middleware");

        await _next(context); // Call next

        // Custom logic after next middleware

        Console.WriteLine("Response handled by custom middleware");

    }

}


Advantage & Disadvantage of Middleware

                    Advantage       Description
        Modular                    Each piece is self-contained and reusable
        Lightweight                    Less overhead compared to full frameworks
        Highly configurable                    Order of execution gives total control
        Testable                    Each middleware can be unit tested
        Cross-cutting logic                    Centralized logging, auth, error handling


Disadvantage
Description
        Order-dependentWrong order can break functionality
        Hard to debugComplex chains are harder to trace
        No built-in DI scope per middlewareUnless configured, can lead to DI issues
        Unintentional short-circuitingMissing await next() ends the pipeline early
        Verbose for complex logicFor complex logic, can become hard to maintain

What You Might Not Know About Middleware

1. Middleware Runs for Every Request

Even static file requests go through middleware. Optimize heavy middleware logic with condition checks (e.g., if (context.Request.Path.StartsWithSegments("/api"))).


2. Short-Circuiting the Pipeline

A middleware that doesn’t call await next() will stop the request from reaching subsequent components.

app.Use(async (context, next) =>
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized");
// next() not called - ends the pipeline
});

3. Middleware Order Matters a LOT

For example, calling UseAuthorization() before UseAuthentication() results in broken security behavior. Correct order:

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

4. You Can Branch Middleware with Map() and MapWhen()

Create conditional middleware branches for specific paths or conditions:

app.Map("/admin", adminApp =>
{
adminApp.Use(async (context, next) =>
{
// Only for /admin
});
});
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/api"), appBuilder =>
{
// Middleware only for API requests
});


5. Middleware Can Modify Request and Response on the Fly

You can read/replace the request body, inject headers, or intercept the response before it's sent.


6. Middleware Runs Outside MVC/Minimal API

Middleware is framework-independent. It runs before minimal APIs or MVC controllers get invoked.


7. Streaming and Buffering in Middleware

To modify the response body, you might need to buffer it:

var originalBodyStream = context.Response.Body;
using var newBodyStream = new MemoryStream();
context.Response.Body = newBodyStream;
await _next(context); // Response is written here
// Now you can inspect or modify the response
newBodyStream.Seek(0, SeekOrigin.Begin);
var responseBody = new StreamReader(newBodyStream).ReadToEnd();
// Reset body and write modified content
context.Response.Body = originalBodyStream;
await context.Response.WriteAsync(responseBody);


Middleware Order in .NET 6+


var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();


// 1. Global exception handler

app.UseExceptionHandler(/* ... or IExceptionHandler configured */);


// 2. HTTPS redirection / HSTS

app.UseHttpsRedirection();

if (!app.Environment.IsDevelopment())

{

    app.UseHsts();

}


// 3. Static files

app.UseStaticFiles();


// 4. Routing (this & short-circuit routing possibilities)

app.UseRouting();


// 5. CORS (if used)

app.UseCors("MyCorsPolicy");


// 6. Authentication

app.UseAuthentication();


// 7. Authorization

app.UseAuthorization();


// 8. HTTP Logging (or earlier, depending on what you want to log)

app.UseHttpLogging();


// 9. Custom / user middleware(s) (logging, metrics, etc.)


// 10. Output Caching (if you want to cache responses)

app.UseOutputCache();   // or similar


// 11. Map endpoints (controllers, minimal APIs etc.)

app.MapControllers();

app.MapGet(...);

app.MapRazorPages();


// 12. Fallbacks / terminal middleware, e.g. return 404 or custom


Conclusion

Middleware is a core concept in ASP.NET Core and plays a critical role in how HTTP requests are handled in your application. By understanding how middleware works, the request pipeline, and the importance of middleware order, you’ll gain full control over how your app processes incoming requests and outgoing responses.

Whether you're using built-in middleware like UseAuthentication, or writing your own custom logic, middleware helps you keep your code modular, clean, and easy to maintain.

As you continue learning ASP.NET Core, start experimenting with custom middleware and observe how changing the order affects your app — that’s the best way to truly understand the pipeline in action.



 

Comments

Popular posts from this blog

Database Approaches in .NET Core: Code-First vs Database-First Explained for Beginners

Dev tunnels