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.

Feidie Network
Feidie Network
Feidie Network
ASP.NET Core Middleware Pipeline: Delegates, Short-Circuiting & Execution Order

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

IApplicationBuilder

internally 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

Use

registers 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

Run

registers 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

Map

and 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 Phase

Each 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

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Chain of ResponsibilityMiddlewareAuthenticationDependency InjectionAuthorizationASP.NET CoreRequest PipelineShort-Circuiting
Feidie Network
Written by

Feidie Network

Make every read truly rewarding: a knowledge framework and interview guide for .NET architects.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.