Skip to content

Implicit call to UseRouting() in WebApplicationBuilder creates ordering issue with pipeline rerunning middleware, e.g. UseExceptionHandler #34146

@DamianEdwards

Description

@DamianEdwards

API propsal:

Background and Motivation

To support middlewares that modify routes in Minimal APIs where UseRouting is called implicitly, we want to allow UseRouting to be called more than once in the app pipeline. E.g.

// visible configuration
app.UseExceptionHandler("/error")
app.MapGet("/error", () => "oh no!");

// functional configuration
app.UseRouting(); // Implicitly added by Minimal API
app.UseExceptionHandler("/error");
app.UseRouting(); // Added by UseExceptionHandler for the error branch only
app.MapGet("/error", () => "oh no!");

To support this, we need to make a change to UseRouting() since it overrides the EndpointRouteBuilder in the Properties bag of the builder: https://github.com/dotnet/aspnetcore/blob/main/src/Http/Routing/src/Builder/EndpointRoutingApplicationBuilderExtensions.cs#L48. Given this would be a breaking behaviour change, we want to add an overload that would use existing EndpointRouteBuilder if one can be found.

Proposed API

namespace Microsoft.AspNetCore.Builder
{
    public static class EndpointRoutingApplicationBuilderExtensions
    {
+       public static IApplicationBuilder UseRouting(this IApplicationBuilder builder, bool overrideEndpointRouteBuilder);
    }

Usage Examples

The usage pattern we expect is in middleware extensions:

public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, ExceptionHandlerOptions options)
{
...
    return app.Use(next =>
    {
...
        var errorBuilder = app.New();
        errorBuilder.UseRouting(overrideEndpointRouteBuilder: false);
        errorBuilder.Run(next);
        options.ExceptionHandler = errorBuilder.Build();
...
    });
}

Alternative Designs

#30549

Risks

Is this the best way to allow recalculation of the endpoint?

Original issue:

WebApplicationBuilder implicitly calls UseRouting() early on in the pipeline which causes issues for middleware added by the application that re-runs the pipeline after changing the path, including UseExceptionHandler. We likely need to revisit this behavior and potentially decide whether we also implicitly add middleware like UseDeveloperExceptionPage and UseExceptionHandler from WebApplicationBuilder to resolve this kinds of ordering issues.

Consider the following app:

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseExceptionHandler("/error");

app.MapGet("/error", () => "oh no!");
app.MapGet("/", () => "Hello World!");
app.MapGet("/throw", IResult () =>
{
    throw new Exception("bad bad");
});

app.Run();

Requests for "/" and "/error" directly from the browser work fine, but attempting to request "/throw" results in the following exception and a 500 result being returned:

fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.Exception: bad bad
         at <Program>$.<>c.<<Main>$>b__0_2() in C:\src\local\EmptyWebExceptionHandling\Program.cs:line 20
         at lambda_method3(Closure , Object , HttpContext )
         at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass28_0.<Create>b__0(HttpContext httpContext) in Microsoft.AspNetCore.Http.Extensions.dll:token 0x600013c+0x0
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext) in Microsoft.AspNetCore.Routing.dll:token 0x60000a8+0x7b
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task) in Microsoft.AspNetCore.Diagnostics.dll:token 0x60000bf+0x70
fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HMA0QQKH5H54", Request id "0HMA0QQKH5H54:00000003": An unhandled exception was thrown by the application.
      System.InvalidOperationException: The exception handler configured on ExceptionHandlerOptions produced a 404 status response. This InvalidOperationException containing the original exception was thrown since this is often due to a misconfigured ExceptionHandlingPath. If the exception handler is expected to return 404 status responses then set AllowStatusCode404Response to true.
       ---> System.Exception: bad bad
         at <Program>$.<>c.<<Main>$>b__0_2() in C:\src\local\EmptyWebExceptionHandling\Program.cs:line 20
         at lambda_method3(Closure , Object , HttpContext )
         at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass28_0.<Create>b__0(HttpContext httpContext) in Microsoft.AspNetCore.Http.Extensions.dll:token 0x600013c+0x0
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext) in Microsoft.AspNetCore.Routing.dll:token 0x60000a8+0x7b
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task) in Microsoft.AspNetCore.Diagnostics.dll:token 0x60000bf+0x70
         --- End of inner exception stack trace ---
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi) in Microsoft.AspNetCore.Diagnostics.dll:token 0x60000bc+0x268
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task) in Microsoft.AspNetCore.Diagnostics.dll:token 0x60000bf+0xf2
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application) in Microsoft.AspNetCore.Server.Kestrel.Core.dll:token 0x6000aa6+0x1b8

Proposal:

We add UseRouting() in the error branch. This requires a new overload to UseRouting since the current one replaces the IEndpointRouteBuilder here:

Current proposal: https://github.com/dotnet/aspnetcore/compare/johluo/diagnostic-option3?expand=1

@davidfowl

Metadata

Metadata

Assignees

Labels

Priority:0Work that we can't release withoutarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-minimal-hosting

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions