[{"data":1,"prerenderedAt":1247},["ShallowReactive",2],{"blog-\u002Fblog\u002Fdotnet-aspnet-core-middleware-pipeline":3},{"id":4,"title":5,"body":6,"description":1233,"difficulty":1234,"extension":1235,"framework":1236,"frameworkSlug":1237,"meta":1238,"navigation":87,"order":54,"path":1239,"qaPath":1240,"seo":1241,"stem":1242,"subtopic":1004,"topic":1243,"topicSlug":1244,"updated":1245,"__hash__":1246},"blog\u002Fblog\u002Fdotnet-aspnet-core-middleware-pipeline.md","How the ASP.NET Core Middleware Pipeline Works",{"type":7,"value":8,"toc":1221},"minimark",[9,14,18,22,41,118,126,130,301,305,308,441,444,512,516,522,587,590,594,597,798,805,883,887,892,985,992,996,1168,1172,1179,1185,1192,1196,1217],[10,11,13],"h2",{"id":12},"why-middleware-mastery-matters-for-net-interviews","Why middleware mastery matters for .NET interviews",[15,16,17],"p",{},"The middleware pipeline is the backbone of every ASP.NET Core application. Interviewers test\nit because a wrong ordering can silently break authentication, expose APIs to CORS attacks, or\nswallow exceptions without logging. This article explains how it works, why ordering matters,\nand how to write your own.",[10,19,21],{"id":20},"what-middleware-actually-is","What middleware actually is",[15,23,24,25,29,30,33,34,37,38,40],{},"Middleware are delegates composed into a pipeline. Each component receives an ",[26,27,28],"code",{},"HttpContext","\nand a ",[26,31,32],{},"RequestDelegate"," (",[26,35,36],{},"next","). Calling ",[26,39,36],{}," passes control to the subsequent component.\nNot calling it short-circuits the pipeline.",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-csharp shiki shiki-themes github-light github-dark","\u002F\u002F The simplest possible middleware — wraps everything:\napp.Use(async (context, next) =>\n{\n    \u002F\u002F → request path: runs before next middleware\n    Console.WriteLine($\"→ {context.Request.Path}\");\n\n    await next(context); \u002F\u002F pass to the next component\n\n    \u002F\u002F ← response path: runs after all subsequent middleware complete\n    Console.WriteLine($\"← {context.Response.StatusCode}\");\n});\n","csharp","",[26,49,50,58,64,70,76,82,89,95,100,106,112],{"__ignoreMap":47},[51,52,55],"span",{"class":53,"line":54},"line",1,[51,56,57],{},"\u002F\u002F The simplest possible middleware — wraps everything:\n",[51,59,61],{"class":53,"line":60},2,[51,62,63],{},"app.Use(async (context, next) =>\n",[51,65,67],{"class":53,"line":66},3,[51,68,69],{},"{\n",[51,71,73],{"class":53,"line":72},4,[51,74,75],{},"    \u002F\u002F → request path: runs before next middleware\n",[51,77,79],{"class":53,"line":78},5,[51,80,81],{},"    Console.WriteLine($\"→ {context.Request.Path}\");\n",[51,83,85],{"class":53,"line":84},6,[51,86,88],{"emptyLinePlaceholder":87},true,"\n",[51,90,92],{"class":53,"line":91},7,[51,93,94],{},"    await next(context); \u002F\u002F pass to the next component\n",[51,96,98],{"class":53,"line":97},8,[51,99,88],{"emptyLinePlaceholder":87},[51,101,103],{"class":53,"line":102},9,[51,104,105],{},"    \u002F\u002F ← response path: runs after all subsequent middleware complete\n",[51,107,109],{"class":53,"line":108},10,[51,110,111],{},"    Console.WriteLine($\"← {context.Response.StatusCode}\");\n",[51,113,115],{"class":53,"line":114},11,[51,116,117],{},"});\n",[15,119,120,121,125],{},"The pipeline is an ",[122,123,124],"strong",{},"onion"," — the request travels inward through each layer, the response\ntravels outward in reverse. The first middleware registered has the outermost shell.",[10,127,129],{"id":128},"use-run-and-map-the-three-primitives","Use, Run, and Map — the three primitives",[42,131,133],{"className":44,"code":132,"language":46,"meta":47,"style":47},"\u002F\u002F Use — middleware that continues:\napp.Use(async (ctx, next) =>\n{\n    ctx.Items[\"start\"] = DateTime.UtcNow; \u002F\u002F add context for later middleware\n    await next(ctx);\n    var elapsed = DateTime.UtcNow - (DateTime)ctx.Items[\"start\"]!;\n    ctx.Response.Headers[\"X-Elapsed-Ms\"] = elapsed.TotalMilliseconds.ToString(\"F0\");\n});\n\n\u002F\u002F Run — terminal middleware (never calls next):\napp.Run(async ctx =>\n{\n    await ctx.Response.WriteAsync(\"Hello from terminal middleware\");\n    \u002F\u002F Calling next here would do nothing useful — there is no next\n});\n\n\u002F\u002F Map — branch the pipeline for a path prefix:\napp.Map(\"\u002Fhealth\", healthApp =>\n{\n    healthApp.Run(async ctx =>\n        await ctx.Response.WriteAsync(\"OK\")); \u002F\u002F only \u002Fhealth requests reach here\n});\n\n\u002F\u002F MapWhen — branch on any predicate:\napp.MapWhen(\n    ctx => ctx.Request.Method == \"OPTIONS\",\n    corsApp => corsApp.Run(async ctx =>\n    {\n        ctx.Response.StatusCode = 204;\n        \u002F\u002F Handle CORS preflight manually\n    }));\n",[26,134,135,140,145,149,154,159,164,169,173,177,182,187,192,198,204,209,214,220,226,231,237,243,248,253,259,265,271,277,283,289,295],{"__ignoreMap":47},[51,136,137],{"class":53,"line":54},[51,138,139],{},"\u002F\u002F Use — middleware that continues:\n",[51,141,142],{"class":53,"line":60},[51,143,144],{},"app.Use(async (ctx, next) =>\n",[51,146,147],{"class":53,"line":66},[51,148,69],{},[51,150,151],{"class":53,"line":72},[51,152,153],{},"    ctx.Items[\"start\"] = DateTime.UtcNow; \u002F\u002F add context for later middleware\n",[51,155,156],{"class":53,"line":78},[51,157,158],{},"    await next(ctx);\n",[51,160,161],{"class":53,"line":84},[51,162,163],{},"    var elapsed = DateTime.UtcNow - (DateTime)ctx.Items[\"start\"]!;\n",[51,165,166],{"class":53,"line":91},[51,167,168],{},"    ctx.Response.Headers[\"X-Elapsed-Ms\"] = elapsed.TotalMilliseconds.ToString(\"F0\");\n",[51,170,171],{"class":53,"line":97},[51,172,117],{},[51,174,175],{"class":53,"line":102},[51,176,88],{"emptyLinePlaceholder":87},[51,178,179],{"class":53,"line":108},[51,180,181],{},"\u002F\u002F Run — terminal middleware (never calls next):\n",[51,183,184],{"class":53,"line":114},[51,185,186],{},"app.Run(async ctx =>\n",[51,188,190],{"class":53,"line":189},12,[51,191,69],{},[51,193,195],{"class":53,"line":194},13,[51,196,197],{},"    await ctx.Response.WriteAsync(\"Hello from terminal middleware\");\n",[51,199,201],{"class":53,"line":200},14,[51,202,203],{},"    \u002F\u002F Calling next here would do nothing useful — there is no next\n",[51,205,207],{"class":53,"line":206},15,[51,208,117],{},[51,210,212],{"class":53,"line":211},16,[51,213,88],{"emptyLinePlaceholder":87},[51,215,217],{"class":53,"line":216},17,[51,218,219],{},"\u002F\u002F Map — branch the pipeline for a path prefix:\n",[51,221,223],{"class":53,"line":222},18,[51,224,225],{},"app.Map(\"\u002Fhealth\", healthApp =>\n",[51,227,229],{"class":53,"line":228},19,[51,230,69],{},[51,232,234],{"class":53,"line":233},20,[51,235,236],{},"    healthApp.Run(async ctx =>\n",[51,238,240],{"class":53,"line":239},21,[51,241,242],{},"        await ctx.Response.WriteAsync(\"OK\")); \u002F\u002F only \u002Fhealth requests reach here\n",[51,244,246],{"class":53,"line":245},22,[51,247,117],{},[51,249,251],{"class":53,"line":250},23,[51,252,88],{"emptyLinePlaceholder":87},[51,254,256],{"class":53,"line":255},24,[51,257,258],{},"\u002F\u002F MapWhen — branch on any predicate:\n",[51,260,262],{"class":53,"line":261},25,[51,263,264],{},"app.MapWhen(\n",[51,266,268],{"class":53,"line":267},26,[51,269,270],{},"    ctx => ctx.Request.Method == \"OPTIONS\",\n",[51,272,274],{"class":53,"line":273},27,[51,275,276],{},"    corsApp => corsApp.Run(async ctx =>\n",[51,278,280],{"class":53,"line":279},28,[51,281,282],{},"    {\n",[51,284,286],{"class":53,"line":285},29,[51,287,288],{},"        ctx.Response.StatusCode = 204;\n",[51,290,292],{"class":53,"line":291},30,[51,293,294],{},"        \u002F\u002F Handle CORS preflight manually\n",[51,296,298],{"class":53,"line":297},31,[51,299,300],{},"    }));\n",[10,302,304],{"id":303},"ordering-the-most-common-source-of-bugs","Ordering — the most common source of bugs",[15,306,307],{},"Wrong ordering causes real problems. Here is the Microsoft-recommended ordering with reasoning:",[42,309,311],{"className":44,"code":310,"language":46,"meta":47,"style":47},"\u002F\u002F 1. Exception handler — OUTERMOST: catches exceptions from everything below\napp.UseExceptionHandler(\"\u002Ferror\");\n\n\u002F\u002F 2. HSTS \u002F HTTPS redirect — security headers before any content\napp.UseHsts();\napp.UseHttpsRedirection();\n\n\u002F\u002F 3. Static files — served WITHOUT authentication (by design)\n\u002F\u002F Do not put auth before this unless static assets are private\napp.UseStaticFiles();\n\n\u002F\u002F 4. Routing — MUST come before CORS and auth so they can read endpoint metadata\napp.UseRouting();\n\n\u002F\u002F 5. CORS — MUST be after UseRouting (reads endpoint CORS policies)\n\u002F\u002F MUST be before UseAuthentication\napp.UseCors(\"MyPolicy\");\n\n\u002F\u002F 6. Auth — MUST be after routing (needs matched endpoint to check [Authorize])\napp.UseAuthentication();\napp.UseAuthorization();\n\n\u002F\u002F 7. Custom business middleware (e.g., rate limiting, tenant resolution)\napp.UseRateLimiter();\n\n\u002F\u002F 8. Endpoint dispatch — INNERMOST: runs the actual controller action or handler\napp.MapControllers();\n",[26,312,313,318,323,327,332,337,342,346,351,356,361,365,370,375,379,384,389,394,398,403,408,413,417,422,427,431,436],{"__ignoreMap":47},[51,314,315],{"class":53,"line":54},[51,316,317],{},"\u002F\u002F 1. Exception handler — OUTERMOST: catches exceptions from everything below\n",[51,319,320],{"class":53,"line":60},[51,321,322],{},"app.UseExceptionHandler(\"\u002Ferror\");\n",[51,324,325],{"class":53,"line":66},[51,326,88],{"emptyLinePlaceholder":87},[51,328,329],{"class":53,"line":72},[51,330,331],{},"\u002F\u002F 2. HSTS \u002F HTTPS redirect — security headers before any content\n",[51,333,334],{"class":53,"line":78},[51,335,336],{},"app.UseHsts();\n",[51,338,339],{"class":53,"line":84},[51,340,341],{},"app.UseHttpsRedirection();\n",[51,343,344],{"class":53,"line":91},[51,345,88],{"emptyLinePlaceholder":87},[51,347,348],{"class":53,"line":97},[51,349,350],{},"\u002F\u002F 3. Static files — served WITHOUT authentication (by design)\n",[51,352,353],{"class":53,"line":102},[51,354,355],{},"\u002F\u002F Do not put auth before this unless static assets are private\n",[51,357,358],{"class":53,"line":108},[51,359,360],{},"app.UseStaticFiles();\n",[51,362,363],{"class":53,"line":114},[51,364,88],{"emptyLinePlaceholder":87},[51,366,367],{"class":53,"line":189},[51,368,369],{},"\u002F\u002F 4. Routing — MUST come before CORS and auth so they can read endpoint metadata\n",[51,371,372],{"class":53,"line":194},[51,373,374],{},"app.UseRouting();\n",[51,376,377],{"class":53,"line":200},[51,378,88],{"emptyLinePlaceholder":87},[51,380,381],{"class":53,"line":206},[51,382,383],{},"\u002F\u002F 5. CORS — MUST be after UseRouting (reads endpoint CORS policies)\n",[51,385,386],{"class":53,"line":211},[51,387,388],{},"\u002F\u002F MUST be before UseAuthentication\n",[51,390,391],{"class":53,"line":216},[51,392,393],{},"app.UseCors(\"MyPolicy\");\n",[51,395,396],{"class":53,"line":222},[51,397,88],{"emptyLinePlaceholder":87},[51,399,400],{"class":53,"line":228},[51,401,402],{},"\u002F\u002F 6. Auth — MUST be after routing (needs matched endpoint to check [Authorize])\n",[51,404,405],{"class":53,"line":233},[51,406,407],{},"app.UseAuthentication();\n",[51,409,410],{"class":53,"line":239},[51,411,412],{},"app.UseAuthorization();\n",[51,414,415],{"class":53,"line":245},[51,416,88],{"emptyLinePlaceholder":87},[51,418,419],{"class":53,"line":250},[51,420,421],{},"\u002F\u002F 7. Custom business middleware (e.g., rate limiting, tenant resolution)\n",[51,423,424],{"class":53,"line":255},[51,425,426],{},"app.UseRateLimiter();\n",[51,428,429],{"class":53,"line":261},[51,430,88],{"emptyLinePlaceholder":87},[51,432,433],{"class":53,"line":267},[51,434,435],{},"\u002F\u002F 8. Endpoint dispatch — INNERMOST: runs the actual controller action or handler\n",[51,437,438],{"class":53,"line":273},[51,439,440],{},"app.MapControllers();\n",[15,442,443],{},"Common ordering bugs and their symptoms:",[445,446,447,460],"table",{},[448,449,450],"thead",{},[451,452,453,457],"tr",{},[454,455,456],"th",{},"Bug",[454,458,459],{},"Symptom",[461,462,463,478,490,501],"tbody",{},[451,464,465,475],{},[466,467,468,471,472],"td",{},[26,469,470],{},"UseCors"," before ",[26,473,474],{},"UseRouting",[466,476,477],{},"Per-endpoint CORS policies ignored; headers always apply the global policy",[451,479,480,487],{},[466,481,482,471,485],{},[26,483,484],{},"UseAuthentication",[26,486,474],{},[466,488,489],{},"Auth runs for every path including static files and health checks",[451,491,492,498],{},[466,493,494,497],{},[26,495,496],{},"UseStaticFiles"," after auth",[466,499,500],{},"Every CSS\u002FJS\u002Fimage request requires authentication",[451,502,503,509],{},[466,504,505,508],{},[26,506,507],{},"UseExceptionHandler"," not first",[466,510,511],{},"Exceptions from authentication or CORS middleware crash with no error page",[10,513,515],{"id":514},"short-circuiting-the-pipeline","Short-circuiting the pipeline",[15,517,518,519,521],{},"A middleware that returns without calling ",[26,520,36],{}," terminates processing — no subsequent\nmiddleware or controller runs:",[42,523,525],{"className":44,"code":524,"language":46,"meta":47,"style":47},"app.Use(async (context, next) =>\n{\n    \u002F\u002F Rate limit check:\n    if (!await _rateLimiter.TryConsumeAsync(context.Connection.RemoteIpAddress))\n    {\n        context.Response.StatusCode = 429; \u002F\u002F Too Many Requests\n        context.Response.Headers.RetryAfter = \"60\";\n        await context.Response.WriteAsync(\"Rate limit exceeded\");\n        return; \u002F\u002F ← short-circuit; next is never called\n    }\n\n    await next(context); \u002F\u002F within limit — continue\n});\n",[26,526,527,531,535,540,545,549,554,559,564,569,574,578,583],{"__ignoreMap":47},[51,528,529],{"class":53,"line":54},[51,530,63],{},[51,532,533],{"class":53,"line":60},[51,534,69],{},[51,536,537],{"class":53,"line":66},[51,538,539],{},"    \u002F\u002F Rate limit check:\n",[51,541,542],{"class":53,"line":72},[51,543,544],{},"    if (!await _rateLimiter.TryConsumeAsync(context.Connection.RemoteIpAddress))\n",[51,546,547],{"class":53,"line":78},[51,548,282],{},[51,550,551],{"class":53,"line":84},[51,552,553],{},"        context.Response.StatusCode = 429; \u002F\u002F Too Many Requests\n",[51,555,556],{"class":53,"line":91},[51,557,558],{},"        context.Response.Headers.RetryAfter = \"60\";\n",[51,560,561],{"class":53,"line":97},[51,562,563],{},"        await context.Response.WriteAsync(\"Rate limit exceeded\");\n",[51,565,566],{"class":53,"line":102},[51,567,568],{},"        return; \u002F\u002F ← short-circuit; next is never called\n",[51,570,571],{"class":53,"line":108},[51,572,573],{},"    }\n",[51,575,576],{"class":53,"line":114},[51,577,88],{"emptyLinePlaceholder":87},[51,579,580],{"class":53,"line":189},[51,581,582],{},"    await next(context); \u002F\u002F within limit — continue\n",[51,584,585],{"class":53,"line":194},[51,586,117],{},[15,588,589],{},"Every built-in auth\u002Fauthorization middleware short-circuits on failure — that's how a 401 or\n403 is returned before the controller ever runs.",[10,591,593],{"id":592},"writing-a-custom-middleware-class","Writing a custom middleware class",[15,595,596],{},"Inline lambdas work for simple cases but become unwieldy. A class is cleaner and testable:",[42,598,600],{"className":44,"code":599,"language":46,"meta":47,"style":47},"\u002F\u002F Convention-based middleware (singleton instantiation):\npublic class RequestTimingMiddleware\n{\n    private readonly RequestDelegate _next;\n    private readonly ILogger\u003CRequestTimingMiddleware> _logger;\n\n    \u002F\u002F Constructor: only singleton deps here\n    public RequestTimingMiddleware(RequestDelegate next,\n        ILogger\u003CRequestTimingMiddleware> logger)\n    {\n        _next = next;\n        _logger = logger;\n    }\n\n    \u002F\u002F InvokeAsync: scoped\u002Ftransient deps injected as parameters\n    public async Task InvokeAsync(HttpContext context, IMetricsService metrics)\n    {\n        var sw = Stopwatch.StartNew();\n        await _next(context);\n        sw.Stop();\n\n        _logger.LogInformation(\"{Method} {Path} → {StatusCode} in {Ms}ms\",\n            context.Request.Method,\n            context.Request.Path,\n            context.Response.StatusCode,\n            sw.ElapsedMilliseconds);\n\n        metrics.Record(context.Request.Path, sw.ElapsedMilliseconds);\n    }\n}\n\n\u002F\u002F Extension method — conventional registration:\npublic static class RequestTimingExtensions\n{\n    public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder app)\n        => app.UseMiddleware\u003CRequestTimingMiddleware>();\n}\n\n\u002F\u002F Program.cs:\napp.UseRequestTiming();\n",[26,601,602,607,612,616,621,626,630,635,640,645,649,654,659,663,667,672,677,681,686,691,696,700,705,710,715,720,725,729,734,738,743,747,753,759,764,770,776,781,786,792],{"__ignoreMap":47},[51,603,604],{"class":53,"line":54},[51,605,606],{},"\u002F\u002F Convention-based middleware (singleton instantiation):\n",[51,608,609],{"class":53,"line":60},[51,610,611],{},"public class RequestTimingMiddleware\n",[51,613,614],{"class":53,"line":66},[51,615,69],{},[51,617,618],{"class":53,"line":72},[51,619,620],{},"    private readonly RequestDelegate _next;\n",[51,622,623],{"class":53,"line":78},[51,624,625],{},"    private readonly ILogger\u003CRequestTimingMiddleware> _logger;\n",[51,627,628],{"class":53,"line":84},[51,629,88],{"emptyLinePlaceholder":87},[51,631,632],{"class":53,"line":91},[51,633,634],{},"    \u002F\u002F Constructor: only singleton deps here\n",[51,636,637],{"class":53,"line":97},[51,638,639],{},"    public RequestTimingMiddleware(RequestDelegate next,\n",[51,641,642],{"class":53,"line":102},[51,643,644],{},"        ILogger\u003CRequestTimingMiddleware> logger)\n",[51,646,647],{"class":53,"line":108},[51,648,282],{},[51,650,651],{"class":53,"line":114},[51,652,653],{},"        _next = next;\n",[51,655,656],{"class":53,"line":189},[51,657,658],{},"        _logger = logger;\n",[51,660,661],{"class":53,"line":194},[51,662,573],{},[51,664,665],{"class":53,"line":200},[51,666,88],{"emptyLinePlaceholder":87},[51,668,669],{"class":53,"line":206},[51,670,671],{},"    \u002F\u002F InvokeAsync: scoped\u002Ftransient deps injected as parameters\n",[51,673,674],{"class":53,"line":211},[51,675,676],{},"    public async Task InvokeAsync(HttpContext context, IMetricsService metrics)\n",[51,678,679],{"class":53,"line":216},[51,680,282],{},[51,682,683],{"class":53,"line":222},[51,684,685],{},"        var sw = Stopwatch.StartNew();\n",[51,687,688],{"class":53,"line":228},[51,689,690],{},"        await _next(context);\n",[51,692,693],{"class":53,"line":233},[51,694,695],{},"        sw.Stop();\n",[51,697,698],{"class":53,"line":239},[51,699,88],{"emptyLinePlaceholder":87},[51,701,702],{"class":53,"line":245},[51,703,704],{},"        _logger.LogInformation(\"{Method} {Path} → {StatusCode} in {Ms}ms\",\n",[51,706,707],{"class":53,"line":250},[51,708,709],{},"            context.Request.Method,\n",[51,711,712],{"class":53,"line":255},[51,713,714],{},"            context.Request.Path,\n",[51,716,717],{"class":53,"line":261},[51,718,719],{},"            context.Response.StatusCode,\n",[51,721,722],{"class":53,"line":267},[51,723,724],{},"            sw.ElapsedMilliseconds);\n",[51,726,727],{"class":53,"line":273},[51,728,88],{"emptyLinePlaceholder":87},[51,730,731],{"class":53,"line":279},[51,732,733],{},"        metrics.Record(context.Request.Path, sw.ElapsedMilliseconds);\n",[51,735,736],{"class":53,"line":285},[51,737,573],{},[51,739,740],{"class":53,"line":291},[51,741,742],{},"}\n",[51,744,745],{"class":53,"line":297},[51,746,88],{"emptyLinePlaceholder":87},[51,748,750],{"class":53,"line":749},32,[51,751,752],{},"\u002F\u002F Extension method — conventional registration:\n",[51,754,756],{"class":53,"line":755},33,[51,757,758],{},"public static class RequestTimingExtensions\n",[51,760,762],{"class":53,"line":761},34,[51,763,69],{},[51,765,767],{"class":53,"line":766},35,[51,768,769],{},"    public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder app)\n",[51,771,773],{"class":53,"line":772},36,[51,774,775],{},"        => app.UseMiddleware\u003CRequestTimingMiddleware>();\n",[51,777,779],{"class":53,"line":778},37,[51,780,742],{},[51,782,784],{"class":53,"line":783},38,[51,785,88],{"emptyLinePlaceholder":87},[51,787,789],{"class":53,"line":788},39,[51,790,791],{},"\u002F\u002F Program.cs:\n",[51,793,795],{"class":53,"line":794},40,[51,796,797],{},"app.UseRequestTiming();\n",[15,799,800,801,804],{},"The ",[26,802,803],{},"IMiddleware"," interface is the alternative: the class is resolved from DI per request,\nso scoped dependencies can be injected directly into the constructor:",[42,806,808],{"className":44,"code":807,"language":46,"meta":47,"style":47},"public class TenantMiddleware : IMiddleware\n{\n    private readonly ITenantResolver _resolver; \u002F\u002F scoped — works here\n\n    public TenantMiddleware(ITenantResolver resolver) => _resolver = resolver;\n\n    public async Task InvokeAsync(HttpContext context, RequestDelegate next)\n    {\n        context.Items[\"Tenant\"] = await _resolver.ResolveAsync(context);\n        await next(context);\n    }\n}\n\n\u002F\u002F Must register in DI (unlike convention-based):\nbuilder.Services.AddScoped\u003CTenantMiddleware>();\napp.UseMiddleware\u003CTenantMiddleware>();\n",[26,809,810,815,819,824,828,833,837,842,846,851,856,860,864,868,873,878],{"__ignoreMap":47},[51,811,812],{"class":53,"line":54},[51,813,814],{},"public class TenantMiddleware : IMiddleware\n",[51,816,817],{"class":53,"line":60},[51,818,69],{},[51,820,821],{"class":53,"line":66},[51,822,823],{},"    private readonly ITenantResolver _resolver; \u002F\u002F scoped — works here\n",[51,825,826],{"class":53,"line":72},[51,827,88],{"emptyLinePlaceholder":87},[51,829,830],{"class":53,"line":78},[51,831,832],{},"    public TenantMiddleware(ITenantResolver resolver) => _resolver = resolver;\n",[51,834,835],{"class":53,"line":84},[51,836,88],{"emptyLinePlaceholder":87},[51,838,839],{"class":53,"line":91},[51,840,841],{},"    public async Task InvokeAsync(HttpContext context, RequestDelegate next)\n",[51,843,844],{"class":53,"line":97},[51,845,282],{},[51,847,848],{"class":53,"line":102},[51,849,850],{},"        context.Items[\"Tenant\"] = await _resolver.ResolveAsync(context);\n",[51,852,853],{"class":53,"line":108},[51,854,855],{},"        await next(context);\n",[51,857,858],{"class":53,"line":114},[51,859,573],{},[51,861,862],{"class":53,"line":189},[51,863,742],{},[51,865,866],{"class":53,"line":194},[51,867,88],{"emptyLinePlaceholder":87},[51,869,870],{"class":53,"line":200},[51,871,872],{},"\u002F\u002F Must register in DI (unlike convention-based):\n",[51,874,875],{"class":53,"line":206},[51,876,877],{},"builder.Services.AddScoped\u003CTenantMiddleware>();\n",[51,879,880],{"class":53,"line":211},[51,881,882],{},"app.UseMiddleware\u003CTenantMiddleware>();\n",[10,884,886],{"id":885},"exception-handling-middleware","Exception-handling middleware",[15,888,889,891],{},[26,890,507],{}," re-executes a different endpoint when an unhandled exception is thrown:",[42,893,895],{"className":44,"code":894,"language":46,"meta":47,"style":47},"\u002F\u002F Production:\napp.UseExceptionHandler(\"\u002Ferror\");\n\napp.Map(\"\u002Ferror\", (HttpContext ctx) =>\n{\n    var feature = ctx.Features.Get\u003CIExceptionHandlerFeature>();\n    var ex = feature?.Error;\n\n    return ex switch\n    {\n        NotFoundException => Results.Problem(statusCode: 404, title: \"Not found\"),\n        ValidationException v => Results.ValidationProblem(v.Errors),\n        _ => Results.Problem(statusCode: 500, title: \"Internal server error\")\n    };\n});\n\n\u002F\u002F Development:\nif (app.Environment.IsDevelopment())\n    app.UseDeveloperExceptionPage(); \u002F\u002F full stack trace in browser\n",[26,896,897,902,906,910,915,919,924,929,933,938,942,947,952,957,962,966,970,975,980],{"__ignoreMap":47},[51,898,899],{"class":53,"line":54},[51,900,901],{},"\u002F\u002F Production:\n",[51,903,904],{"class":53,"line":60},[51,905,322],{},[51,907,908],{"class":53,"line":66},[51,909,88],{"emptyLinePlaceholder":87},[51,911,912],{"class":53,"line":72},[51,913,914],{},"app.Map(\"\u002Ferror\", (HttpContext ctx) =>\n",[51,916,917],{"class":53,"line":78},[51,918,69],{},[51,920,921],{"class":53,"line":84},[51,922,923],{},"    var feature = ctx.Features.Get\u003CIExceptionHandlerFeature>();\n",[51,925,926],{"class":53,"line":91},[51,927,928],{},"    var ex = feature?.Error;\n",[51,930,931],{"class":53,"line":97},[51,932,88],{"emptyLinePlaceholder":87},[51,934,935],{"class":53,"line":102},[51,936,937],{},"    return ex switch\n",[51,939,940],{"class":53,"line":108},[51,941,282],{},[51,943,944],{"class":53,"line":114},[51,945,946],{},"        NotFoundException => Results.Problem(statusCode: 404, title: \"Not found\"),\n",[51,948,949],{"class":53,"line":189},[51,950,951],{},"        ValidationException v => Results.ValidationProblem(v.Errors),\n",[51,953,954],{"class":53,"line":194},[51,955,956],{},"        _ => Results.Problem(statusCode: 500, title: \"Internal server error\")\n",[51,958,959],{"class":53,"line":200},[51,960,961],{},"    };\n",[51,963,964],{"class":53,"line":206},[51,965,117],{},[51,967,968],{"class":53,"line":211},[51,969,88],{"emptyLinePlaceholder":87},[51,971,972],{"class":53,"line":216},[51,973,974],{},"\u002F\u002F Development:\n",[51,976,977],{"class":53,"line":222},[51,978,979],{},"if (app.Environment.IsDevelopment())\n",[51,981,982],{"class":53,"line":228},[51,983,984],{},"    app.UseDeveloperExceptionPage(); \u002F\u002F full stack trace in browser\n",[15,986,987,988,991],{},"Always register exception handling ",[122,989,990],{},"first"," — if another middleware (e.g., auth) throws,\nthe exception handler must be the outermost layer to catch it.",[10,993,995],{"id":994},"built-in-middleware-cheat-sheet","Built-in middleware cheat sheet",[445,997,998,1011],{},[448,999,1000],{},[451,1001,1002,1005,1008],{},[454,1003,1004],{},"Middleware",[454,1006,1007],{},"Method",[454,1009,1010],{},"Purpose",[461,1012,1013,1029,1042,1055,1067,1079,1091,1103,1116,1129,1142,1155],{},[451,1014,1015,1018,1026],{},[466,1016,1017],{},"Exception handling",[466,1019,1020,1022,1023],{},[26,1021,507],{}," \u002F ",[26,1024,1025],{},"UseDeveloperExceptionPage",[466,1027,1028],{},"Catch unhandled exceptions",[451,1030,1031,1034,1039],{},[466,1032,1033],{},"HSTS",[466,1035,1036],{},[26,1037,1038],{},"UseHsts",[466,1040,1041],{},"Strict-Transport-Security header",[451,1043,1044,1047,1052],{},[466,1045,1046],{},"HTTPS redirect",[466,1048,1049],{},[26,1050,1051],{},"UseHttpsRedirection",[466,1053,1054],{},"Force HTTPS",[451,1056,1057,1060,1064],{},[466,1058,1059],{},"Static files",[466,1061,1062],{},[26,1063,496],{},[466,1065,1066],{},"Serve wwwroot assets",[451,1068,1069,1072,1076],{},[466,1070,1071],{},"Routing",[466,1073,1074],{},[26,1075,474],{},[466,1077,1078],{},"Match requests to endpoints",[451,1080,1081,1084,1088],{},[466,1082,1083],{},"CORS",[466,1085,1086],{},[26,1087,470],{},[466,1089,1090],{},"Cross-origin headers",[451,1092,1093,1096,1100],{},[466,1094,1095],{},"Authentication",[466,1097,1098],{},[26,1099,484],{},[466,1101,1102],{},"Identify user",[451,1104,1105,1108,1113],{},[466,1106,1107],{},"Authorization",[466,1109,1110],{},[26,1111,1112],{},"UseAuthorization",[466,1114,1115],{},"Enforce policies",[451,1117,1118,1121,1126],{},[466,1119,1120],{},"Rate limiting",[466,1122,1123],{},[26,1124,1125],{},"UseRateLimiter",[466,1127,1128],{},"Throttle requests (.NET 7+)",[451,1130,1131,1134,1139],{},[466,1132,1133],{},"Output cache",[466,1135,1136],{},[26,1137,1138],{},"UseOutputCache",[466,1140,1141],{},"Server-side caching (.NET 7+)",[451,1143,1144,1147,1152],{},[466,1145,1146],{},"Response compression",[466,1148,1149],{},[26,1150,1151],{},"UseResponseCompression",[466,1153,1154],{},"Gzip\u002FBrotli",[451,1156,1157,1160,1165],{},[466,1158,1159],{},"Session",[466,1161,1162],{},[26,1163,1164],{},"UseSession",[466,1166,1167],{},"Session state",[10,1169,1171],{"id":1170},"middleware-vs-filters-which-to-use","Middleware vs filters — which to use?",[15,1173,1174,1175,1178],{},"Use ",[122,1176,1177],{},"middleware"," for cross-cutting concerns that apply to all HTTP traffic — logging, auth,\nCORS, rate limiting, compression. It has no knowledge of controllers or model binding.",[15,1180,1174,1181,1184],{},[122,1182,1183],{},"action filters"," when you need MVC context — access to model state, action arguments,\nor controller metadata. Filters only run for matched MVC endpoints.",[15,1186,1187,1188,1191],{},"A good rule of thumb: if the concern needs to apply to health checks, static files, or\nnon-MVC routes, use middleware. If it needs ",[26,1189,1190],{},"ModelState"," or action arguments, use a filter.",[10,1193,1195],{"id":1194},"recap","Recap",[15,1197,1198,1199,1202,1203,1206,1207,1210,1211,1213,1214,1216],{},"The middleware pipeline is a chain of delegates processed in registration order (request path)\nand reverse order (response path). ",[26,1200,1201],{},"Use"," wraps, ",[26,1204,1205],{},"Run"," terminates, ",[26,1208,1209],{},"Map"," branches. Ordering\nis critical: exception handling outermost, static files before auth, routing before CORS and\nauth, endpoint dispatch innermost. Short-circuit by not calling ",[26,1212,36],{},". Use convention-based\nmiddleware for singleton deps in the constructor; use ",[26,1215,803],{}," when you need scoped deps.\nAlways register exception handling before anything else.",[1218,1219,1220],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":47,"searchDepth":60,"depth":60,"links":1222},[1223,1224,1225,1226,1227,1228,1229,1230,1231,1232],{"id":12,"depth":60,"text":13},{"id":20,"depth":60,"text":21},{"id":128,"depth":60,"text":129},{"id":303,"depth":60,"text":304},{"id":514,"depth":60,"text":515},{"id":592,"depth":60,"text":593},{"id":885,"depth":60,"text":886},{"id":994,"depth":60,"text":995},{"id":1170,"depth":60,"text":1171},{"id":1194,"depth":60,"text":1195},"How the ASP.NET Core middleware pipeline processes requests — the order that matters, when short-circuiting applies, and how to write DI-aware custom middleware without introducing bugs.","medium","md",".NET Core","dotnet",{},"\u002Fblog\u002Fdotnet-aspnet-core-middleware-pipeline","\u002Fdotnet\u002Faspnet-core\u002Fmiddleware",{"title":5,"description":1233},"blog\u002Fdotnet-aspnet-core-middleware-pipeline","ASP.NET Core","aspnet-core","2026-06-23","5jg33TLg1M50xTcaFcwCjxbnBf5yYkwErrosBpPT4ws",1782244083435]