Understanding Middleware in ASP.NET Core — The Core Building Block of the Request Pipeline

In ASP.NET Core, middleware is more than just a feature — it’s the foundation of how every request and response flows through your application. If you’re building web APIs or full-stack applications with ASP.NET Core, understanding middleware is essential to writing clean, efficient, and extensible code.

In this article, we’ll break down what middleware is, how the request pipeline works, and why middleware plays such a crucial role in ASP.NET Core.

What is Middleware in ASP.NET Core?

Middleware is software that’s assembled into an application pipeline to handle requests and responses. In ASP.NET Core, every incoming HTTP request passes through a sequence of middleware components — and each one can:

  • Inspect the request
  • Modify the request
  • Short-circuit the pipeline (i.e., end it early)
  • Pass the request on to the next middleware
  • Modify the response on the way back

Each middleware has a specific purpose. Think of them as processing layers: logging, authentication, routing, static file handling, error handling — these are all implemented as middleware.

How the ASP.NET Core Request Pipeline Works

The request pipeline in ASP.NET Core is built by chaining middleware components together in the Program.cs file using extension methods like UseRouting, UseAuthorization, UseEndpoints, and custom middleware using app.Use.

Here’s a simplified example of what a request pipeline looks like:

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

app.UseRouting(); // determines which endpoint the request matches.

app.UseAuthorization(); // applies any authorization policies.

app.MapControllers(); // maps the request to a controller action.

app.Run();

In this case:

  • UseRouting() determines which endpoint the request matches.
  • UseAuthorization() applies any authorization policies.
  • MapControllers() maps the request to a controller action.

Middleware Execution Flow (Order Matters!)

In ASP.NET Core, middleware components run in the exact order they’re registered in the pipeline — and that order matters.

When a request arrives, it flows through each middleware in the order registered — and each one has a chance to run logic before and after calling the next() delegate. Once the request reaches the last middleware (or an earlier one short-circuits the flow), the response travels back up through the middleware stack in reverse.

Here’s a simple diagram to help visualize this flow:

ASP.NET Core middleware request-response pipeline diagram

Each middleware processes the request, passes control using next(), and then continues processing as the response returns.

In this example:

  1. Middleware 1 runs some logic, then calls next().
  2. The request continues to Middleware 2, which does the same.
  3. Middleware 3 handles the request and starts sending the response back.
  4. On the way back, Middleware 2 and Middleware 1 can perform additional work, such as modifying the response or logging.

This flow is why middleware ordering is critical — the earlier it’s added, the earlier it sees the request and the later it sees the response.

Built-in Middleware in ASP.NET Core

ASP.NET Core comes with a rich set of built-in middleware components that help you handle common web application needs out of the box — from authentication and logging to response compression and static file serving.

Each middleware has a specific purpose and should be placed at the correct position in the request pipeline to function properly. Some middleware components are terminal, meaning they can short-circuit the pipeline and generate a response without passing the request further.

Here’s a breakdown of commonly used built-in middleware and where they typically belong in the pipeline:

  • UseAntiforgery – Adds CSRF protection to your app. Should be placed after authentication and authorization, and before endpoint execution.
  • UseAuthentication – Enables user authentication. Register early in the pipeline, before anything that needs HttpContext.User. Acts as terminal for OAuth callbacks.
  • UseAuthorization – Handles permission checks based on user identity. Should come immediately after UseAuthentication.
  • UseCookiePolicy – Manages cookie consent and security policies like SameSite. Should be placed before any middleware that sets cookies, such as session or authentication.
  • UseCors – Enables cross-origin requests. Place before anything that consumes CORS headers. Must come before UseResponseCaching.
  • UseDeveloperExceptionPage – Shows detailed error pages during development. Typically the first middleware in the pipeline for development environments.
  • UseExceptionHandler / UseStatusCodePages – Handles errors and generates user-friendly status responses. Should be placed early, before middleware that may generate exceptions.
  • UseForwardedHeaders – Preserves original client IP, scheme, and headers from reverse proxies. Place before middleware that uses this information.
  • UseHealthChecks – Provides health status endpoints for your app and its dependencies. Acts as terminal middleware when a request matches a health check endpoint.
  • UseHeaderPropagation – Forwards selected headers from incoming requests to outgoing HTTP clients.
  • UseHttpLogging – Logs HTTP requests and responses. Should be registered early to capture full request/response information.
  • UseHttpMethodOverride – Allows overriding HTTP methods (e.g., simulate PUT via POST). Register before anything that uses the request method.
  • UseHttpsRedirection – Redirects HTTP requests to HTTPS. Place before anything that relies on the request URL.
  • UseHsts – Adds Strict-Transport-Security headers to responses. Should be registered after UseHttpsRedirection, before responses are sent.
  • UseRouting – Matches requests to routes. Required before UseAuthorization, UseEndpoints, and most endpoint-related middleware.
  • UseEndpoints – Executes matched endpoint (controller, Razor Page, etc.). Acts as terminal middleware for matched routes.
  • UseOutputCaching – Caches responses based on configuration. Register after routing and before components that depend on caching. Must come after UseRouting and before UseCors.
  • UseResponseCaching – Enables HTTP caching. Requires client support. Should come after UseCors and before MVC/static file middleware.
  • UseRequestDecompression – Decompresses request bodies. Place before middleware that reads the request body.
  • UseResponseCompression – Compresses outgoing responses. Place before middleware that returns responses.
  • UseRequestLocalization – Applies culture and language settings based on the request. Must be registered after UseRouting when using route-based providers.
  • UseRequestTimeouts – Allows global and per-endpoint request timeout settings. Must come after error handlers and routing middleware.
  • UseSpa – Handles fallback for Single Page Applications (SPAs). Should be added late in the pipeline, after static file and MVC handling.
  • UseSession – Enables session state management. Place before middleware that relies on session data.
  • UseStaticFiles – Serves static assets like HTML, CSS, JS, and images. Acts as terminal middleware when a file is matched.
  • UseRewriter – Rewrites URLs and handles redirects. Place early, before anything that depends on request paths or URLs.
  • UseW3CLogging – Generates logs in W3C format. Register at the start of the pipeline to capture complete logging info.
  • UseWebSockets – Enables WebSocket support. Register before middleware that accepts WebSocket requests.

Custom Middleware — When and Why to Create Your Own

Built-in middleware is great, but there are times when your app needs to perform custom logic — like:

  • Logging custom headers
  • Adding tenant information to the request
  • Transforming the response
  • Validating custom tokens

That’s when writing your own middleware makes sense. It gives you full control over the request and response flow.

Want to go a step further and build your own middleware? Check out my full guide: How to Create Middleware in ASP.NET Core

Middleware vs Filters vs Delegates

It’s worth clarifying: middleware is not the same as filters or request delegates.

FeatureScopeBehavior
MiddlewareApplication-wideHandles the entire HTTP pipeline. Registered in Program.cs with app.UseXyz() methods. ex: Logging, authentication
FiltersController/Action-levelWork inside the MVC pipeline. Used for cross-cutting concerns in MVC like validation or authorization.
Request DelegateLightweight, inlineRepresents a single step in the request pipeline (e.g., app.Run(context => …)). Often used for quick middleware.

Middleware Best Practices

Here are a few tips to keep your middleware efficient and clean:

  1. Order Matters – Always be intentional about how you order middleware.
  2. Short-Circuit Carefully – If you don’t call await next(), no further middleware or endpoints will run.
  3. Keep Logic Focused – Middleware should do one job. Avoid bloated middleware that handles multiple responsibilities.
  4. Use Dependency Injection – Middleware supports constructor injection of services.

Summary

Middleware is a fundamental part of how ASP.NET Core handles web requests. Every incoming request passes through a chain of middleware components, each with the opportunity to inspect, modify, or even short-circuit the request. ASP.NET Core provides a rich set of built-in middleware for handling common tasks like authentication, logging, error handling, and static file serving — but you can also create your own when you need custom logic.

The order in which you register middleware matters significantly, as it determines how requests are processed and how responses are sent back. This gives you a powerful and flexible way to manage cross-cutting concerns such as caching, localization, and security throughout your application. Understanding middleware is key to building robust and maintainable ASP.NET Core applications.

Takeaway

Understanding middleware gives you full control over how your application handles HTTP traffic. It’s one of the most powerful features of ASP.NET Core — and mastering it will elevate your ability to build scalable, maintainable web applications.

Found this article useful? Share it with your network and spark a conversation.