Waqas Ahmad — Software Architect & Technical Consultant - Available USA, Europe, Global

Waqas Ahmad — Software Architect & Technical Consultant

Specializing in

Distributed Systems

.NET ArchitectureCloud-Native ArchitectureAzure Cloud EngineeringAPI ArchitectureMicroservices ArchitectureEvent-Driven ArchitectureDatabase Design & Optimization

👋 Hi, I'm Waqas — a Software Architect and Technical Consultant specializing in .NET, Azure, microservices, and API-first system design..
I help companies build reliable, maintainable, and high-performance backend platforms that scale.

Experienced across engineering ecosystems shaped by Microsoft, the Cloud Native Computing Foundation, and the Apache Software Foundation.

Available for remote consulting (USA, Europe, Global) — flexible across EST, PST, GMT & CET.

services
Article

.NET Core Middleware and Pipeline: In-Depth with Code Examples

ASP.NET Core middleware: order, auth, CORS, security headers, and rate limiting.

services
Read the article

Introduction

This guidance is relevant when the topic of this article applies to your system or design choices; it breaks down when constraints or context differ. I’ve applied it in real projects and refined the takeaways over time (as of 2026).

Putting global HTTP behaviour—error handling, CORS, auth, security headers, logging—inside controllers is hard to maintain and test. This article covers the ASP.NET Core middleware pipeline in depth: what middleware is, correct pipeline order, types (inline vs class-based), and step-by-step code for exception handling, HTTPS, CORS, authentication, authorization, claims, security headers, logging, rate limiting, and health checks. For architects and tech leads, keeping cross-cutting behaviour in one ordered pipeline keeps controllers clean and makes behaviour predictable and testable.

If you are new to middleware, start with Topics covered and Benefits: what middleware can do without touching your code.

For a deeper overview of this topic, explore the full .NET Architecture guide.

Decision Context

  • System scale: Varies by context; the approach in this article applies to the scales and scenarios described in the body.
  • Team size: Typically small to medium teams; ownership and clarity matter more than headcount.
  • Time / budget pressure: Applicable under delivery pressure; I’ve used it in both greenfield and incremental refactors.
  • Technical constraints: .NET and related stack where relevant; constraints are noted in the article where they affect the approach.
  • Non-goals: This article does not optimize for every possible scenario; boundaries are stated where they matter.

What is middleware and the pipeline?

Middleware is a component that handles HTTP requests and responses. It receives a RequestDelegate (the next middleware in the pipeline) and can: (1) call it to pass the request along, (2) modify the request or response and then call next, or (3) short-circuit (not call next) and return a response (e.g. 401, 429, or a health check). The pipeline is the ordered chain of middleware: each request passes through them in registration order. Order is critical—exception handling first, then HTTPS, CORS, auth, authorization, then endpoints.

RequestDelegate is a delegate: Task(HttpContext context). Your middleware receives it in the constructor and calls await _next(context) to pass the request to the next middleware.


Benefits: what middleware can do without touching your code

With middleware you can add cross-cutting behavior globally without changing controllers or endpoints:

What you get Without middleware With middleware
Error handling Try/catch in every action One exception middleware; all errors caught and formatted
HTTPS / HSTS Manual redirect in each app UseHttpsRedirection, UseHsts; all requests covered
CORS CORS logic in controllers UseCors; preflight and responses handled in one place
Authentication Check token in every action UseAuthentication; user set once for pipeline
Authorization Check roles in every action UseAuthorization + policies; 403 before endpoint
Claims enrichment Load claims in each action One middleware adds claims to context.User
Security headers Set headers in every response One middleware adds X-Frame-Options, CSP, etc.
Logging / correlation ID Log in every action One middleware logs request/response and adds correlation ID
Rate limiting Check limits in every action One middleware returns 429 before endpoint
Health checks Dedicated controller Terminal middleware for /health; no controller

Result: Controllers stay focused on business logic; middleware handles infrastructure and cross-cutting concerns.


Types of middleware

Type How When to use
Inline app.Use(async (context, next) => { ...; await next(); }); Quick, one-off logic; no DI needed
Class-based Class with RequestDelegate in constructor, InvokeAsync(HttpContext); register with UseMiddleware<T> Reusable, testable, supports constructor injection
Terminal Middleware that never calls next (e.g. Run). Health checks, static “Under maintenance” page
Pass-through Calls await _next(context) after (or before) its logic. Logging, auth, CORS, most cross-cutting behavior

Use class-based middleware when you need DI (e.g. ILogger, IConfiguration) or reuse. Use inline for small, one-off logic. Use terminal only when the request should never reach endpoints (e.g. /health).


Order is critical. Recommended sequence in Program.cs:

  1. Exception handling – Catch errors from all later middleware and format response.
  2. HTTPS redirection / HSTS – Force HTTPS before any other logic.
  3. Static files – Serve files without hitting app logic (optional).
  4. Routing – Match URL to endpoint (required before UseCors with endpoint routing in some setups).
  5. CORS – Set CORS headers; must run before auth so preflight (OPTIONS) succeeds.
  6. Authentication – Identify the user (set context.User).
  7. Authorization – Check if the user is allowed (return 403 if not).
  8. Endpoints – Run the actual handler (MapControllers, MapGet, etc.).

Full example (Program.cs):

var app = builder.Build();

app.UseExceptionHandler("/Error");  // or UseExceptionHandler with lambda
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("MyPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

Why this order: Exception handling first so any exception in later middleware is caught. CORS before auth so browser preflight (OPTIONS) gets CORS headers without auth. Auth before authorization so context.User is set. Authorization before endpoints so 403 is returned before your code runs.


Exception and error handling

UseExceptionHandler catches unhandled exceptions from later middleware and endpoints and returns a configurable error response without touching your controller code.

Step 1: Basic exception handler (redirect to error page)

app.UseExceptionHandler("/Error");

Step 2: Custom exception handler (return JSON or ProblemDetails)

app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        context.Response.StatusCode = 500;
        context.Response.ContentType = "application/json";
        var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error;
        await context.Response.WriteAsJsonAsync(new
        {
            type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
            title = "An error occurred",
            status = 500,
            detail = ex?.Message
        });
    });
});

Step 3: Enterprise – Hellang.Middleware.ProblemDetails

builder.Services.AddProblemDetails();
app.UseExceptionHandler();
app.UseStatusCodePages();  // 404, 403, etc. as ProblemDetails

How this fits together: Any unhandled exception in the pipeline is caught by the exception handler; the request never reaches your endpoint with a crash. You return a consistent error shape (e.g. ProblemDetails) without try/catch in every action.


HTTPS redirection and security headers

UseHttpsRedirection redirects HTTP to HTTPS. UseHsts adds the Strict-Transport-Security header so browsers use HTTPS for future requests. Both apply globally without touching your code.

app.UseHttpsRedirection();
app.UseHsts();  // add in production; ensure HTTPS is working first

Custom security headers middleware (X-Frame-Options, X-Content-Type-Options, etc.) – see Security headers below.


CORS

UseCors adds CORS headers to responses and handles preflight (OPTIONS) requests. Configure a policy in Program.cs; the middleware applies it before your endpoints run, so you never add CORS logic in controllers.

Step 1: Add CORS policy

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyPolicy", policy =>
    {
        policy.WithOrigins("https://myapp.com")
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

Step 2: Use CORS middleware (before UseAuthentication)

app.UseCors("MyPolicy");

How this fits together: Browser sends OPTIONS preflight; CORS middleware responds with allowed origin/methods/headers. Browser then sends the real request; CORS middleware adds Access-Control-Allow-Origin (and related) to the response. Your endpoints do nothing; CORS is handled in one place.


Authentication and authorization

UseAuthentication runs the registered authentication scheme(s) (e.g. JWT Bearer, cookies). It sets context.User from the token or cookie before the request reaches your endpoints. UseAuthorization runs policy checks (e.g. [Authorize], RequireRole) and returns 403 if the user is not allowed—without you writing checks in every action.

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => { ... });
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", p => p.RequireRole("Admin"));
});

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

How this fits together: Authentication middleware runs first and sets context.User. Authorization middleware runs next; if the endpoint has [Authorize] or [Authorize(Policy = "AdminOnly")], the policy is evaluated. If it fails, 403 is returned and your action is not executed. No if (!User.IsInRole("Admin")) in controllers.


Claims addition and enrichment

You can add claims to context.User in a middleware that runs after UseAuthentication and before UseAuthorization (or endpoints). That way, downstream code (authorization, controllers) sees the enriched user without loading claims in every action.

Step 1: Middleware that enriches claims

public class ClaimsEnrichmentMiddleware
{
    private readonly RequestDelegate _next;

    public ClaimsEnrichmentMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context, IUserClaimsService claimsService)
    {
        if (context.User.Identity?.IsAuthenticated == true)
        {
            var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
            var extraClaims = await claimsService.GetClaimsForUserAsync(userId);
            var identity = (ClaimsIdentity)context.User.Identity;
            foreach (var c in extraClaims)
                identity.AddClaim(c);
        }
        await _next(context);
    }
}

Step 2: Register (after UseAuthentication, before UseAuthorization)

app.UseMiddleware<ClaimsEnrichmentMiddleware>();

Note: If IUserClaimsService is scoped (e.g. uses DbContext), inject IServiceScopeFactory in the middleware and create a scope inside InvokeAsync: using var scope = _scopeFactory.CreateScope(); var claimsService = scope.ServiceProvider.GetRequiredService<IUserClaimsService>(); Then use claimsService to load claims. Middleware is typically singleton, so you must not inject scoped services directly.

How this fits together: After authentication, context.User has basic claims (e.g. from JWT). Your middleware loads additional claims (e.g. from DB) and adds them to context.User. Authorization and controllers then see the full set of claims without each action loading them.


Response modification (headers, wrapping)

Middleware can add or modify response headers (e.g. X-Request-ID, X-Response-Time) without touching your code. It can also wrap the response body (e.g. buffer and modify) with more code; for simple header addition, run after _next and set headers.

Example: add X-Request-ID and X-Response-Time

public class ResponseHeadersMiddleware
{
    private readonly RequestDelegate _next;

    public ResponseHeadersMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        var requestId = context.TraceIdentifier;
        var sw = Stopwatch.StartNew();
        await _next(context);
        sw.Stop();
        context.Response.Headers["X-Request-ID"] = requestId;
        context.Response.Headers["X-Response-Time-Ms"] = sw.ElapsedMilliseconds.ToString();
    }
}

How this fits together: Every response gets the headers without any code in controllers. Register after exception handling, before or after routing as needed.


Security headers (CSP, X-Frame-Options, etc.)

A single middleware can add security headers to every response so you don’t set them in every action:

Header Purpose
X-Frame-Options Prevent clickjacking (e.g. DENY or SAMEORIGIN)
X-Content-Type-Options Prevent MIME sniffing (nosniff)
Referrer-Policy Control referrer sent to other sites
Content-Security-Policy Restrict script/style/source origins
Strict-Transport-Security Force HTTPS (HSTS)

Example: security headers middleware

public class SecurityHeadersMiddleware
{
    private readonly RequestDelegate _next;

    public SecurityHeadersMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        context.Response.Headers["X-Frame-Options"] = "DENY";
        context.Response.Headers["X-Content-Type-Options"] = "nosniff";
        context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
        await _next(context);
    }
}

How this fits together: All responses get the headers; no per-action code. Register early (e.g. after exception handling).


Request logging and correlation ID

Logging middleware logs each request (method, path) and optionally response status. Correlation ID middleware reads X-Correlation-ID from the request or generates a new GUID, sets it in context.Items and response headers, so logs can be traced per request.

Full example: logging + correlation ID

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var correlationId = context.Request.Headers["X-Correlation-ID"].FirstOrDefault()
            ?? Guid.NewGuid().ToString("N");
        context.Response.Headers["X-Correlation-ID"] = correlationId;
        context.Items["CorrelationId"] = correlationId;

        _logger.LogInformation("Request {Method} {Path} [CorrelationId={CorrelationId}]",
            context.Request.Method, context.Request.Path, correlationId);

        await _next(context);

        _logger.LogInformation("Response {StatusCode} [CorrelationId={CorrelationId}]",
            context.Response.StatusCode, correlationId);
    }
}

Registration: app.UseMiddleware<RequestLoggingMiddleware>(); after exception handling, before routing.


Rate limiting and health checks

Rate limiting: Middleware can check a limit (e.g. per IP or per user) before calling _next. If exceeded, return 429 and do not call _next—your endpoints are never hit. In .NET 7+, use Microsoft.AspNetCore.RateLimiting (e.g. UseRateLimiter, AddFixedWindowLimiter).

Health checks: Terminal middleware for /health (or /ready) that returns 200 (and optionally checks DB, etc.) without hitting your controllers. Use MapGet("/health", () => Results.Ok()) or app.UseHealthChecks("/health") with AddHealthChecks.

builder.Services.AddHealthChecks();
app.UseHealthChecks("/health");

Custom terminal middleware (e.g. health):

app.UseWhen(context => context.Request.Path.StartsWithSegments("/health"), appBuilder =>
{
    appBuilder.Run(async context =>
    {
        context.Response.StatusCode = 200;
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsJsonAsync(new { status = "Healthy" });
    });
});

Custom security and business checks

Middleware can enforce API key, tenant header, or custom checks and short-circuit (e.g. 401, 403) before the request reaches your code.

Example: API key middleware

public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private const string ApiKeyHeader = "X-Api-Key";

    public ApiKeyMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context, IConfiguration config)
    {
        if (!context.Request.Headers.TryGetValue(ApiKeyHeader, out var key) ||
            key != config["ApiKey"])
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Invalid or missing API key");
            return;
        }
        await _next(context);
    }
}

How this fits together: Requests without a valid API key get 401 and never reach your endpoints. All API key logic is in one place.


Enterprise practices

  1. Order – Exception handling first; CORS before auth; auth before authorization; authorization before endpoints.
  2. Async – Use async/await in middleware; avoid Task.Wait() or blocking I/O.
  3. Scoped services – Middleware is typically singleton. To use scoped services (e.g. DbContext), inject IServiceScopeFactory and create a scope inside InvokeAsync: using var scope = _scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
  4. Error handling – Use UseExceptionHandler or ProblemDetails so all errors return a consistent shape.
  5. Logging – Use a logging middleware (and correlation ID) so every request/response is logged without per-action code.
  6. Security headers – One middleware for X-Frame-Options, CSP, etc.; apply globally.

Frameworks and libraries

Framework What it adds
Built-in UseExceptionHandler, UseHttpsRedirection, UseCors, UseAuthentication, UseAuthorization, UseRouting, MapControllers, UseHealthChecks
Hellang.Middleware.ProblemDetails RFC 7807 ProblemDetails for exceptions and status pages (404, 403, etc.)
AspNetCoreRateLimit Rate limiting by IP/client (third-party)
Microsoft.AspNetCore.RateLimiting (.NET 7+) Built-in rate limiting (fixed window, sliding, etc.)

What middlewares can do: summary table

Operation Built-in / custom What it does without touching your code
Error handling UseExceptionHandler, custom Catches exceptions; returns JSON or redirect
HTTPS / HSTS UseHttpsRedirection, UseHsts Redirects HTTP to HTTPS; sets HSTS header
CORS UseCors Handles preflight; adds CORS headers
Authentication UseAuthentication Sets context.User from token/cookie
Authorization UseAuthorization Returns 403 if policy fails
Claims enrichment Custom middleware Adds claims to context.User
Response headers Custom middleware X-Request-ID, X-Response-Time, etc.
Security headers Custom middleware X-Frame-Options, CSP, etc.
Logging / correlation ID Custom middleware Logs request/response; adds correlation ID
Rate limiting UseRateLimiter, custom Returns 429 before endpoint
Health check UseHealthChecks, MapGet Returns 200 for /health
API key / tenant Custom middleware Returns 401/403 before endpoint

Common pitfalls

  • Wrong order – Auth or CORS after endpoints; exception handling not first. Fix: use the recommended order.
  • Captive dependency – Singleton middleware injecting Scoped (e.g. DbContext). Fix: use IServiceScopeFactory and create a scope in InvokeAsync.
  • BlockingTask.Wait() or sync I/O in middleware. Fix: use async/await.
  • Short-circuit and response already started – Setting status/headers or writing body after calling _next can fail. Fix: set headers before _next if you might short-circuit; for response body, either short-circuit without calling _next or use response buffering if you need to modify body after _next.

Summary

The ASP.NET Core middleware pipeline is an ordered chain of components that handle requests and responses without touching your endpoint code—error handling, HTTPS, CORS, auth, authorization, security headers, logging, rate limiting, health checks. Wrong order (e.g. auth after CORS or after endpoints) causes subtle bugs; correct order and class-based middleware with proper scoping keep behaviour predictable. Next, review your pipeline order (exception first, then HTTPS, CORS, auth, authorization, endpoints) and move any global logic out of controllers into middleware.


Position & Rationale

I treat the middleware pipeline as the single place for cross-cutting HTTP behaviour: exception handling, CORS, auth, security headers, logging, rate limiting. I avoid putting that logic in controllers or action filters—middleware runs in a defined order and keeps endpoints thin. I use class-based middleware with UseMiddleware<T> whenever I need DI or reuse; I reserve inline Use(lambda) for one-off checks. I reject running auth or CORS after endpoint routing in the same pipeline; order is non-negotiable for correctness. For claims enrichment I always use IServiceScopeFactory when the service is scoped—injecting a scoped dependency directly into middleware is a bug.


Trade-Offs & Failure Modes

  • What this sacrifices: You give up per-action control of cross-cutting behaviour; everything is global. Adding middleware adds latency to every request; heavy logic in middleware (e.g. DB calls for claims) can slow the pipeline.
  • Where it degrades: Under very high throughput, middleware that does I/O (e.g. claims from DB) can become a bottleneck. Wrong order (e.g. CORS after auth) causes subtle production failures that are hard to debug.
  • How it fails when misapplied: Captive dependency (singleton middleware holding scoped DbContext) causes concurrency and stale data. Setting status or writing response after calling _next can throw or be ignored. Blocking in middleware (Task.Wait()) can deadlock.
  • Early warning signs: “We added a check in the controller as well” means middleware is being bypassed. Repeated fixes to order (moving middleware up/down) or per-action overrides suggest the pipeline is not the single source of truth.

What Most Guides Miss

Docs rarely stress that middleware is effectively singleton for the app lifetime: you cannot inject scoped services (e.g. DbContext) in the constructor. The fix—IServiceScopeFactory and creating a scope inside InvokeAsync—is easy to miss, and the resulting bugs (shared instance across requests, disposed-too-early) are hard to trace. Another gap: short-circuiting must happen before you call _next; once the response has started, you cannot change status or headers. Tutorials show the happy path; production issues are usually order, scoping, or short-circuit discipline.


Decision Framework

  • If you need cross-cutting behaviour for every request → Put it in middleware in the correct order; exception handling first, then HTTPS, CORS, auth, authorization, endpoints.
  • If you need DI or reusable logic → Use class-based middleware and UseMiddleware<T>; for scoped dependencies use IServiceScopeFactory inside InvokeAsync.
  • If you need to short-circuit (401, 429, health) → Do not call _next; set status and write response, then return.
  • If CORS or auth “sometimes works” → Fix order: CORS before UseAuthentication so preflight succeeds; UseAuthentication before UseAuthorization so context.User is set.
  • If you are adding “one more check” in a controller → Prefer moving it into middleware so the pipeline stays the single place for that concern.

You can also explore more patterns in the .NET Architecture resource page.

Key Takeaways

  • Middleware is the right place for global HTTP behaviour; keep controllers free of auth, CORS, and logging logic.
  • Order is critical: exception handling → HTTPS → routing → CORS → authentication → authorization → endpoints.
  • Use class-based middleware for anything that needs DI or reuse; use IServiceScopeFactory for scoped services, never inject them in the constructor.
  • Short-circuit before calling _next; avoid blocking and captive dependencies.
  • Revisit pipeline order and scoping when adding new middleware or debugging auth/CORS issues.

Need architectural guidance for real-world .NET platforms? I offer consulting for .NET architecture, API platforms, and enterprise system design.

When I Would Use This Again — and When I Wouldn’t

I would use the pipeline approach again for any ASP.NET Core API or web app that needs global error handling, CORS, auth, or cross-cutting headers—greenfield or refactor. I wouldn’t put business rules or per-endpoint logic in middleware; that belongs in filters or application code. I’d skip a heavy custom middleware stack when the app is a tiny internal tool with no auth and no cross-origin calls; the built-in minimum (exception handler, routing, endpoints) is enough. If the team keeps adding checks in controllers instead of the pipeline, I’d treat that as a signal to consolidate and document order once, then enforce it in review.


services
Frequently Asked Questions

Frequently Asked Questions

What is middleware?

Middleware is a component that handles HTTP requests and responses. It receives the next delegate in the pipeline and can call it to pass the request along or short-circuit (e.g. return 401). Examples: authentication, logging, CORS, error handling.

What is the middleware pipeline?

The pipeline is the ordered chain of middleware components. Each request passes through them in registration order. Order matters: exception handling first, then HTTPS, CORS, auth, authorization, endpoints.

What is RequestDelegate?

RequestDelegate is a delegate that represents the next middleware in the pipeline. It takes HttpContext and returns Task. Your middleware receives it in the constructor and calls await _next(context) to pass the request to the next middleware.

Why does middleware order matter?

Order determines what middleware sees the request first. Exception handling must be first to catch errors. CORS must run before auth so preflight succeeds. Auth must run before authorization so context.User is set. Authorization must run before endpoints so 403 is returned before your code runs.

How do I create custom middleware?

Create a class with a constructor that takes RequestDelegate (and optionally ILogger or other services). Add a method InvokeAsync(HttpContext context). Register with app.UseMiddleware<YourMiddleware>();.

What is short-circuiting?

Not calling await _next(context)—the request does not reach later middleware or endpoints. Use for auth failures (401), rate limiting (429), health checks, or API key rejection.

Can middleware use DI?

Yes. Constructor injection works for singleton services. For scoped services (e.g. DbContext), inject IServiceScopeFactory and create a scope inside InvokeAsync, then resolve the scoped service from the scope.

What are common middleware order mistakes?

Auth or authorization after endpoints (unauthenticated requests hit controllers). CORS after auth (preflight may fail). Exception handling not first (errors not caught). Fix: use the recommended order.

UseMiddleware vs Use vs Run?

UseMiddleware<T> for class-based middleware with DI. Use for inline middleware (lambda). Run for terminal middleware (never calls next).

How do I add correlation ID?

Middleware reads X-Correlation-ID from the request or generates a new GUID. Store it in context.Items["CorrelationId"] and set context.Response.Headers["X-Correlation-ID"]. Use it in logging so all logs for the request share the same id.

What is captive dependency in middleware?

Singleton middleware holding a reference to a scoped service (e.g. DbContext). The scoped instance is never disposed correctly and can cause concurrency or stale data. Fix: inject IServiceScopeFactory and create a scope in InvokeAsync when you need a scoped service.

Where do I put custom middleware?

After exception handling (so your middleware’s errors are caught). Before routing if it does not need the matched endpoint; before auth/authorization if it needs to run for unauthenticated requests (e.g. logging, CORS). Put claims enrichment after UseAuthentication, before UseAuthorization.

How do I test middleware?

Unit test: create a DefaultHttpContext, mock or set up the next delegate, invoke your middleware, assert on context.Response and whether next was called. Integration test: use WebApplicationFactory and send requests; assert on headers and status codes.

What built-in middleware is available?

UseExceptionHandler, UseHttpsRedirection, UseHsts, UseStaticFiles, UseRouting, UseCors, UseAuthentication, UseAuthorization, UseRateLimiter, UseHealthChecks, MapControllers, MapGet, etc.

How do I add security headers (X-Frame-Options, CSP)?

Create a middleware that sets context.Response.Headers["X-Frame-Options"], X-Content-Type-Options, Referrer-Policy, and optionally Content-Security-Policy before calling _next(context). Register early in the pipeline (e.g. after exception handling).

How do I enrich claims in middleware?

Run a middleware after UseAuthentication that reads context.User, loads additional claims (e.g. from DB via a scoped service), and adds them to (ClaimsIdentity)context.User.Identity. Then call _next(context). Use IServiceScopeFactory to resolve scoped services.

services
Related Guides & Resources

services
Related services