ASP.NET Core Middleware Pipeline: Delegates, Short-Circuiting & Execution Order
This article dissects the ASP.NET Core request pipeline, explaining how middleware delegates form a chain of responsibility, the three registration methods (Use, Run, Map), bidirectional request/response flow, short-circuiting for performance, critical ordering rules, and dependency injection patterns for custom middleware.
Request Processing Pipeline — The Backbone of ASP.NET Core
In ASP.NET Core, everything revolves around the HTTP request/response lifecycle. When an application receives an HTTP request, it passes through a series of components called middleware . Middleware acts as a pipeline where each component serves as a "processing station" that can execute code before and after the next component in the pipeline. This model is essentially an implementation of the Chain of Responsibility pattern , decomposing request handling logic into composable, reusable components.
Architect's Perspective: Understanding the request pipeline is foundational to the entire ASP.NET Core framework. The pipeline model replaces the complex event model based on HttpModule and HttpHandler in classic ASP.NET, providing a clearer, more controllable request processing flow. Middleware pipeline design is one of ASP.NET Core's core advantages — it separates cross-cutting concerns (logging, authentication, exception handling) from business logic, making code cleaner and more testable.
Middleware Essence — Delegate Chain Construction & Execution
Underlying Implementation
In ASP.NET Core, each middleware is fundamentally a delegate ( RequestDelegate), defined as:
public delegate Task RequestDelegate(HttpContext context);The standard middleware form is a function that accepts a RequestDelegate parameter (the next middleware) and returns a RequestDelegate (the current middleware's processing delegate) — conceptually: " receive the next middleware, return the current middleware's processing delegate ".
Pipeline Construction Process
IApplicationBuilderinternally maintains a list of middleware delegates. In the Build() method, these delegates are reverse-linked into a nested structure :
// Conceptual representation
app.UseMiddleware<A>();
app.UseMiddleware<B>();
app.UseMiddleware<C>();
// Build creates: A(B(C))This "reverse nesting" construction ensures middleware executes request handling in registration order and response handling in reverse order .
Three Middleware Registration Methods — Use, Run, Map
1. Use — Chain Middleware
Useregisters middleware that receives a next parameter. After its own logic, it can call next() to pass the request to the next middleware.
2. Run — Terminal Middleware
Runregisters middleware that does not receive a next parameter . It is the terminal point of the pipeline; subsequent middleware will not execute.
3. Map & MapWhen — Branch Pipelines
Mapand MapWhen create pipeline branches based on request path or custom conditions:
Architect's Perspective: Map and MapWhen are core tools for building pipeline branches . Static file middleware, MVC endpoints, etc., actually leverage branch pipelines — matching a specific path enters an independent pipeline for processing, avoiding unnecessary middleware execution overhead. This is essentially separation of concerns at the pipeline level.
Bidirectional Pipeline Flow — Request/Response Symmetry
Middleware pipeline execution is bidirectional , which is key to understanding its behavior:
Request Phase: Request flows from first middleware to last (registration order)
Response Phase: Response flows back from last middleware to first (reverse order)
Example output order for middleware A → B → C (where C is terminal):
A: Request Phase
B: Request Phase
C: Terminal Middleware
B: Response Phase
A: Response PhaseEach middleware's code before next() handles the request ; code after next() handles the response . This makes middleware ideal for cross-cutting concerns — e.g., logging (record request/response time), exception handling (catch exceptions from subsequent middleware).
Short-Circuit Mechanism — Terminating Pipeline Execution
When middleware decides not to call next() , the pipeline short-circuits — subsequent middleware won't execute, and the response returns directly back up the pipeline.
Core Value of Short-Circuiting
Performance Optimization: Static file middleware returns file directly on match, skipping subsequent MVC pipeline
Security Control: Auth failure returns 401/403 immediately, skipping business logic
Error Handling: Developer exception page middleware returns error page after catching exception
Architect's Perspective: Short-circuiting is one of the most efficient optimization mechanisms in the middleware pipeline. Placing high-frequency short-circuit middleware (static files, authentication) at the front of the pipeline allows massive request volumes to be handled before entering complex business logic.
Middleware Execution Order — Critically Important
Registration order determines execution order , which is vital for security, performance, and functionality.
Typical ASP.NET Core Middleware Order
1. Exception Handling (UseExceptionHandler)
2. HTTPS Redirection
3. Static Files
4. Routing (UseRouting)
5. Authentication (UseAuthentication)
6. Authorization (UseAuthorization)
7. Endpoints (MapControllers, etc.)Why Order Matters
Exception handling must be first: To catch exceptions from all subsequent middleware
Static files before authentication: Otherwise static resources require auth, causing unnecessary performance overhead
Authentication before authorization: Authorization needs to know "who you are" before deciding "what you can do"
Architect's Perspective: Order determines behavior is the first principle of middleware design. When adding custom middleware, you must know which middleware results it depends on, and which middleware depend on its results. A wrong position can cause security vulnerabilities or functional failures.
Two Ways to Write Custom Middleware
Method 1: Delegate-Based Inline Middleware
Suitable for simple logic, defined directly in Program.cs:
app.Use(async (context, next) => {
// Request logic
await next();
// Response logic
});Method 2: Class-Based Middleware
Suitable for complex logic requiring dependency injection. By convention, middleware class contains:
Constructor: accepts RequestDelegate and injected services Invoke or InvokeAsync method: handles request
public class CustomMiddleware {
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public CustomMiddleware(RequestDelegate next, ILogger<CustomMiddleware> logger) {
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, IMyScopedService scopedService) {
// Request logic
await _next(context);
// Response logic
}
}Architect's Perspective: Class-based middleware supports dependency injection , allowing injection of ILogger , IOptions , DbContext , etc. Services injected in the constructor are Singleton lifecycle ; services injected in the Invoke method are Scoped lifecycle . Understanding this distinction helps correctly use different lifecycle services in middleware.
Dependency Injection in Middleware — Service Resolution Patterns
Two ways to use DI in middleware:
Constructor Injection
(Resolved at app startup, typically for Singleton services)
public class MyMiddleware {
private readonly RequestDelegate _next;
private readonly IConfiguration _config; // Singleton
public MyMiddleware(RequestDelegate next, IConfiguration config) {
_next = next;
_config = config;
}
public async Task InvokeAsync(HttpContext context) {
await _next(context);
}
}Invoke Method Parameter Injection
(Resolved per request, for Scoped services)
public async Task InvokeAsync(HttpContext context, IMyScopedService scopedService) {
// scopedService is resolved from current request scope
await _next(context);
}Architect's Perspective: Middleware itself is built once at app startup (Singleton behavior), so injecting Scoped services in the constructor causes a Captive Dependency problem. Must inject Scoped services in the Invoke method to get correct instances from the current request scope.
Built-in Middleware Overview
ASP.NET Core provides a series of built-in middleware (illustrated in source image): exception handling, HTTPS redirection, static files, routing, authentication, authorization, CORS, response compression, etc.
Interview Deep-Dive Questions
1. "Difference between middleware and filters?"
Middleware is more general — handles all requests (static files, health checks, etc.), executes before MVC pipeline. Filters only handle MVC/API requests, can access MVC context ( ActionDescriptor, ModelState). Exception handling should prioritize middleware layer ( UseExceptionHandler catches entire pipeline exceptions).
2. "Why must UseAuthentication() be between UseRouting() and MapControllers() ?"
UseRouting()matches endpoint; endpoint info (e.g., [Authorize] attribute) becomes available for subsequent middleware. UseAuthentication() executes after routing, using route-resolved endpoint info for auth decisions. Authorization middleware runs after authentication to check permissions.
3. "Why does static file middleware short-circuit?"
Static file middleware is a typical terminal middleware . If request path matches an existing static file, it returns file content directly and does not call next() , avoiding MVC pipeline entry, significantly boosting performance.
4. "How to handle exceptions in middleware?"
Exception handling middleware should be placed at the very front of the pipeline , wrap await next() in try-catch to catch exceptions from all subsequent middleware. Note: if response has already started ( HttpResponse.HasStarted == true), response cannot be modified.
Summary
The request processing pipeline and middleware are the core skeleton of ASP.NET Core :
Middleware Essence: Components handling HTTP requests/responses, organized as a delegate chain , implementing the Chain of Responsibility pattern
Three Registration Methods: Use (chain, can call next), Run (terminal, no next), Map/MapWhen (branch pipelines)
Bidirectional Flow: Request flows forward in registration order, response flows backward in reverse order — each middleware gets two execution opportunities around next() Short-Circuit Mechanism: Not calling next() terminates pipeline — used for static files, auth failure, exception handling
Order Critical: Exception handling → HTTPS → Static files → Routing → Authentication → Authorization → Endpoints — wrong order causes security vulnerabilities or functional failures
Dependency Injection: Singleton services in constructor, Scoped services in Invoke method, avoiding Captive Dependency
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Feidie Network
Make every read truly rewarding: a knowledge framework and interview guide for .NET architects.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
