3. Middleware & HTTP Pipeline

ASP.NET Core Middleware & HTTP Pipeline - Complete Guide

Practical Guide + Technical Reference


📋 Table of Contents

Part 1: Practical Guide (Hands-On)

  1. What is Middleware

  2. HTTP Request Pipeline Flow

  3. Creating Custom Middleware (3 Methods)

  4. Middleware Branching (Use, Run, Map)

  5. Common Middleware Patterns

  6. Built-in Middleware Quick Reference

  7. Troubleshooting Common Issues

  8. Best Practices

Part 2: Technical Reference (Deep Dive)

  1. Important Interfaces & Classes Reference

  2. Configuration Deep-Dive

  3. Built-in Middleware Configuration Details

  4. Advanced Topics


PART 1: PRACTICAL GUIDE


1. What is Middleware?

Simple Definition: Software components that handle HTTP requests and responses.

Think of it like: An assembly line where each worker (middleware) inspects or modifies a product (HTTP request) before passing it to the next worker.

Key Point: Order matters! Request flows top to bottom, response flows bottom to top.


2. HTTP Request Pipeline Flow

Visual Pipeline

Example: Standard Program.cs


3. Creating Custom Middleware (3 Methods)

Method 1: Inline Lambda (Quick & Simple)

When to use:

  • ✅ Simple logic (1-5 lines)

  • ✅ Prototyping

  • ❌ Complex logic

  • ❌ Needs dependency injection

Step 1: Use app.Use() with Lambda

Complete Example:


Method 2: Middleware Class (Production)

When to use:

  • ✅ Complex logic (10+ lines)

  • ✅ Reusable

  • ✅ Production code

  • ✅ Needs logging/DI

Step 1: Create Middleware Class

Step 2: Create Extension Method

Step 3: Use in Program.cs


Method 3: IMiddleware Interface (Scoped Dependencies)

When to use:

  • ✅ Need DbContext (scoped)

  • ✅ Middleware per request

  • ✅ Advanced DI scenarios

Step 1: Implement IMiddleware

Step 2: Register in DI ⚠️ REQUIRED

Step 3: Use in Pipeline


When to use:

  • ✅ Production code

  • ✅ Reusable middleware packages

  • ✅ Follows ASP.NET Core conventions

  • ✅ Clean, discoverable API

The Pattern: ASP.NET Core uses a consistent pattern for all features:

  • AddXxx() - Register services and configure (on IServiceCollection)

  • UseXxx() - Add to pipeline (on IApplicationBuilder)

Examples from ASP.NET Core:

Step 1: Create Your Middleware (Same as Method 3)

Step 2: Create Extension Methods Class

Step 3: Use in Program.cs


Advanced: Add/Use Pattern with Configuration

When you need configuration options:

Step 1: Create Options Class

Step 2: Update Middleware to Accept Options

Step 3: Update Extension Methods with Configuration

Step 4: Usage - Three Configuration Methods

Method 1: Default Configuration

Method 2: Code-based Configuration

Method 3: appsettings.json Configuration


Complete Example: Request Logging with Add/Use Pattern

Full Implementation:


Comparison: Direct Registration vs Add/Use Pattern

Approach
Code
Pros
Cons

Direct

services.AddScoped<MyMiddleware>(); app.UseMiddleware<MyMiddleware>();

Simple, less code

Not discoverable, no configuration, verbose

Add/Use

services.AddMyMiddleware(); app.UseMyMiddleware();

Clean, discoverable, configurable, follows conventions

Requires extension methods

When to Use Add/Use Pattern:

  • ✅ Production middleware

  • ✅ Reusable libraries

  • ✅ Middleware needs configuration

  • ✅ Following ASP.NET Core conventions

  • ✅ IntelliSense discoverability important

When Direct Registration is OK:

  • ⚠️ Quick prototyping

  • ⚠️ Internal middleware (not shared)

  • ⚠️ No configuration needed

  • ⚠️ Simple middleware


Why This Pattern Matters

1. Discoverability

2. Consistency All ASP.NET Core features follow this pattern:

  • Authentication, Authorization, CORS, Controllers, MVC, etc.

3. Configuration Clean place to handle options and dependencies

4. Separation of Concerns

  • AddXxx() - DI registration and configuration

  • UseXxx() - Pipeline registration

5. Testability Easy to mock and test services separately from pipeline


Best Practice Summary

For Production Middleware:

  1. ✅ Use IMiddleware interface

  2. ✅ Create extension methods (Add + Use)

  3. ✅ Support configuration options

  4. ✅ Register as Scoped (if using DbContext)

  5. ✅ Follow naming conventions (AddXxx, UseXxx)

  6. ✅ Return IServiceCollection and IApplicationBuilder for chaining

  7. ✅ Provide overloads for different configuration methods

Template to Follow:


Comparison: Which Method?

Feature
Lambda
Class
IMiddleware

Complexity

Simple

Medium-Complex

Complex

Reusability

No

Yes

Yes

DI Dependencies

Singleton only

Singleton only

Scoped/Transient ✅

Testing

Hard

Easy

Easy

Performance

Fast

Fast

Slightly slower

Decision Tree:


4. Middleware Branching

Use() - Chain Middleware

Run() - Terminal Middleware

Map() - Branch by Path

MapWhen() - Conditional Branch

Complete Branching Example:


5. Common Middleware Patterns

Pattern 1: Correlation ID

Pattern 2: Request Timing

Pattern 3: Error Handling

Pattern 4: Simple Rate Limiting


6. Built-in Middleware Quick Reference

Middleware
Position
Purpose

UseDeveloperExceptionPage()

First

Detailed errors (dev only)

UseExceptionHandler()

First

Global error handler (production)

UseHsts()

After exception

HTTP Strict Transport Security

UseHttpsRedirection()

Early

Redirect HTTP → HTTPS

UseStaticFiles()

Before routing

Serve wwwroot files

UseRouting()

Before auth

Enable endpoint routing

UseCors()

After routing

Cross-origin requests

UseAuthentication()

Before authorization

Authenticate user

UseAuthorization()

After authentication

Check permissions

UseSession()

After routing

Enable sessions

MapControllers()

Last

Map controller endpoints


7. Troubleshooting Common Issues

Issue 1: Wrong Middleware Order

Problem:

Solution:

Issue 2: Response Already Started

Problem:

Solution:

Issue 3: Scoped Service in Singleton Middleware

Problem:

Solution: Use IMiddleware


8. Best Practices

  • ✅ Order middleware correctly (Exception → HTTPS → Static → Routing → Auth)

  • ✅ Use extension methods for custom middleware

  • ✅ Keep middleware focused (single responsibility)

  • ✅ Use IMiddleware for scoped dependencies (DbContext)

  • ✅ Don't modify response after next() (use OnStarting)

  • ✅ Short-circuit when appropriate (don't call next if handled)

  • ✅ Use ILogger, not Console.WriteLine

  • ✅ Handle errors gracefully (try-catch)


PART 2: TECHNICAL REFERENCE


9. Important Interfaces & Classes Reference

IMiddleware Interface ✨ ASP.NET Core 2.0+

Purpose: Factory-based middleware creation (supports scoped/transient lifetimes)

Namespace: Microsoft.AspNetCore.Http

Declaration:

Key Points:

  • Allows scoped/transient lifetime (unlike traditional middleware which is singleton)

  • Must be registered in DI container

  • Slightly slower than traditional middleware

  • Use when you need DbContext or other scoped services

Full Example:


RequestDelegate Delegate

Purpose: Represents the next middleware in the pipeline

Declaration:

Usage:

Key Points:

  • Always injected by framework in constructor

  • Calling await _next(context) continues pipeline

  • Not calling _next short-circuits the pipeline


HttpContext Class ⭐⭐⭐

Purpose: Encapsulates all HTTP request/response information

Namespace: Microsoft.AspNetCore.Http

Key Members:

Member
Type
Purpose

Request

HttpRequest

Incoming HTTP request

Response

HttpResponse

Outgoing HTTP response

User

ClaimsPrincipal

Authenticated user

Items

IDictionary

Request-scoped data storage

RequestServices

IServiceProvider

Access DI services

Connection

ConnectionInfo

Connection details

RequestAborted

CancellationToken

Request cancellation

Session

ISession

Session data

TraceIdentifier

string

Unique request ID

Common Usage Patterns:

Access Request Information:

Modify Response:

Access User Information:

Store Request-Scoped Data:

Resolve Services:


HttpRequest Class ⭐⭐⭐

Purpose: Represents incoming HTTP request

Key Members:

Member
Type
Description

Method

string

HTTP verb (GET, POST, etc.)

Scheme

string

http or https

Host

HostString

example.com:443

Path

PathString

/api/users

QueryString

QueryString

?id=5&page=2

Query

IQueryCollection

Parsed query parameters

Headers

IHeaderDictionary

HTTP headers

Cookies

IRequestCookieCollection

Request cookies

ContentType

string

Content-Type header

ContentLength

long?

Request body size

Body

Stream

Request body stream

Form

IFormCollection

Form data (POST)

IsHttps

bool

Is HTTPS request

Reading URL Components:

Reading Headers:

Reading Request Body:


HttpResponse Class ⭐⭐⭐

Purpose: Represents outgoing HTTP response

Key Members:

Member
Type
Description

StatusCode

int

HTTP status code (200, 404, etc.)

Headers

IHeaderDictionary

Response headers

ContentType

string

Content-Type header

ContentLength

long?

Response body size

Body

Stream

Response body stream

Cookies

IResponseCookies

Response cookies

HasStarted

bool

Has response started sending

Methods:

Method
Purpose

WriteAsync(string)

Write text to response

WriteAsJsonAsync<T>(T)

Write JSON to response

Redirect(string)

Redirect to URL

OnStarting(Func<Task>)

Execute before response starts

OnCompleted(Func<Task>)

Execute after response completes

Setting Status and Headers:

Writing Response:

Setting Cookies:

Response Callbacks:


IApplicationBuilder Interface ⭐⭐⭐

Purpose: Defines the middleware pipeline

Namespace: Microsoft.AspNetCore.Builder

Key Methods:

Method
Purpose

Use(Func<RequestDelegate, RequestDelegate>)

Add middleware

Run(RequestDelegate)

Terminal middleware

Map(PathString, Action<IApplicationBuilder>)

Branch pipeline

MapWhen(Func<HttpContext, bool>, Action<IApplicationBuilder>)

Conditional branch

UseMiddleware<T>()

Add typed middleware

Extension Method Pattern:


10. Configuration Deep-Dive

Pattern 1: Inline Configuration (No Options)

When to use: Simple, hardcoded configuration

Pros: Simple, quick Cons: Not configurable, not reusable


Pattern 2: Constructor Options (Code Configuration)

When to use: Reusable middleware with code-based configuration

Step 1: Create Options Class

Step 2: Middleware Accepts Options

Step 3: Extension Method with Configuration

Step 4: Usage


Pattern 3: IOptions Pattern (appsettings.json)

When to use: Production apps, configuration from files

Step 1: Options Class with Section Name

Step 2: appsettings.json

Step 3: Register in DI

Step 4: Inject IOptions

Step 5: Use Middleware


Configuration Comparison Table

Approach
Source
Reload
Complexity
Production

Inline

Hardcoded

No

Simple

❌ No

Constructor Options

Code

No

Medium

⚠️ Maybe

IOptions

appsettings.json

No

Medium

✅ Yes

IOptionsSnapshot

appsettings.json

Per request

Medium

✅ Yes

IOptionsMonitor

appsettings.json

Live

Complex

✅ Yes


11. Built-in Middleware Configuration Details

CORS Configuration

Important Classes:

  • CorsOptions - Configure policies

  • CorsPolicyBuilder - Build CORS policy

Method 1: Default Policy

Method 2: Named Policies

Method 3: From Configuration


Static Files Configuration

Important Class: StaticFileOptions

Method 1: Default (wwwroot)

Method 2: Custom Directory

Method 3: Custom Headers


Authentication Configuration

Important Classes:

  • AuthenticationOptions - Global settings

  • JwtBearerOptions - JWT configuration

JWT Bearer Setup:


12. Advanced Topics

Short-Circuit Middleware ✨ ASP.NET Core 8.0+

Purpose: Skip remaining middleware

Middleware Order Visualization

Performance Tips

  1. Order matters for performance:

    • Put cheap middleware first (static files)

    • Put expensive middleware last (database checks)

  2. Short-circuit when possible:

    • Health checks don't need authentication

    • Static files don't need routing

  3. Use IMiddleware sparingly:

    • Slight performance overhead

    • Only when you need scoped dependencies


Summary: Complete Middleware Checklist

Creating Middleware:

Configuration:

Usage:

Best Practices:


This completes the Middleware guide combining practical hands-on content with deep technical reference!

Last updated