ASP.NET Core middleware is not a bag of independent features. It is an ordered pipeline. Each component can run before the next one, after the next one, short-circuit the request, or change the request and response seen by everything later.
That means order affects behavior. Exception handling needs to wrap the parts of the pipeline whose failures it should catch. Routing needs to run before endpoint-aware middleware. Authentication needs to establish the user before authorization checks policies. CORS, static files, response compression, rate limiting, antiforgery, and custom middleware all have placement rules that change what they can see and enforce.
This becomes more important when you add cross-cutting behavior such as API key checks, tenant resolution, logging scopes, correlation IDs, request body limits, or model/tool authorization. Put those checks too late and sensitive work may already have started. Put them too early and they may not have route or user context yet.
The boring MVC or API pipeline usually looks close to this:
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("Frontend");
app.UseAuthentication();
app.UseMiddleware<TenantResolutionMiddleware>();
app.UseAuthorization();
app.MapControllers();
The exact middleware set depends on the app, but the relative order matters. UseRouting() selects endpoint data. UseCors() belongs after routing and before authorization so preflight and rejected requests still get the right CORS headers. UseAuthentication() must run before UseAuthorization(). Custom middleware that needs both route data and the authenticated user usually belongs between authentication and authorization.
Keep the pipeline boring and intentional. Group related middleware, avoid clever custom short-circuits, and add a comment when order is non-obvious. If changing the order would change security or endpoint behavior, make that visible in review.