Skip to content

MapShortCircuit causes runtime exceptions in app published as native AOT #47941

@DamianEdwards

Description

@DamianEdwards

Using MapShortCircuit in an app that's published native AOT results in an exception on first request when the endpoint itself is built:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HMQ7EGDA44U5", Request id "0HMQ7EGDA44U5:00000001": An unhandled exception was thrown by the application.
      System.NotSupportedException: Cannot retrieve a MethodInfo for this delegate because the method it targeted (Microsoft.AspNetCore.Routing.RouteShortCircuitEndpointRouteBuilderExtensions.<>c.<.cctor>b__3_0(HttpContext)) was not enabled for metadata.
         at Internal.Reflection.Extensions.NonPortable.DelegateMethodInfoRetriever.GetDelegateMethodInfo(Delegate) + 0x34f
         at Microsoft.AspNetCore.Routing.RouteEndpointDataSource.CreateRouteEndpointBuilder(RouteEndpointDataSource.RouteEntry, RoutePattern, IReadOnlyList`1, IReadOnlyList`1) + 0x42e
         at Microsoft.AspNetCore.Routing.RouteEndpointDataSource.GetGroupedEndpoints(RouteGroupContext) + 0xb4
         at Microsoft.AspNetCore.Routing.RouteGroupBuilder.GroupEndpointDataSource.GetGroupedEndpointsWithNullablePrefix(RoutePattern, IReadOnlyList`1, IReadOnlyList`1, IServiceProvider) + 0x58
         at Microsoft.AspNetCore.Routing.RouteGroupBuilder.GroupEndpointDataSource.get_Endpoints() + 0x3f
         at Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.CreateEndpointsUnsynchronized() + 0x6c
         at Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.EnsureEndpointsInitialized() + 0x4c
         at Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.get_Endpoints() + 0x10
         at Microsoft.AspNetCore.Routing.DataSourceDependentCache`1.Initialize() + 0x44
         at System.Threading.LazyInitializer.EnsureInitializedCore[T](T&, Boolean&, Object&, Func`1) + 0x43
         at Microsoft.AspNetCore.Routing.Matching.DataSourceDependentMatcher..ctor(EndpointDataSource, DataSourceDependentMatcher.Lifetime, Func`1) + 0x96
         at Microsoft.AspNetCore.Routing.Matching.DfaMatcherFactory.CreateMatcher(EndpointDataSource) + 0x61
         at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.InitializeCoreAsync() + 0x8a
      --- End of stack trace from previous location ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x20
         at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0xb2
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task) + 0x42
         at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.<<Invoke>g__AwaitMatcher|9_0>d.MoveNext() + 0xc1
      --- End of stack trace from previous location ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x20
         at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0xb2
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<ProcessRequests>d__238`1.MoveNext() + 0x460

This is due to MapShortCircuit using a compiler generated delegate as the RequestDelegate and native AOT not supporting getting MethodInfo on compiler generated delegates (see dotnet/runtime#85042):

private static readonly RequestDelegate _shortCircuitDelegate = (context) => Task.CompletedTask;

Repro

  1. Create an ASP.NET Core project using the "API' project template and enabled for native AOT publish: dotnet new api --aot
  2. Add a call to app.MapShortCircuit(StatusCodes.Status404NotFound, "/favicon.ico"), e.g.:
    using System.Text.Json.Serialization;
    using ShortCircuitNativeAot;
    
    var builder = WebApplication.CreateSlimBuilder(args);
    builder.Logging.AddConsole();
    
    builder.Services.ConfigureHttpJsonOptions(options =>
    {
        options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
    });
    
    var app = builder.Build();
    
    app.MapShortCircuit(StatusCodes.Status404NotFound, "/favicon.ico");
    
    var sampleTodos = TodoGenerator.GenerateTodos().ToArray();
    
    var todosApi = app.MapGroup("/todos");
    todosApi.MapGet("/", () => sampleTodos);
    todosApi.MapGet("/{id}", (int id) =>
        sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
            ? Results.Ok(todo)
            : Results.NotFound());
    
    app.Run();
    
    [JsonSerializable(typeof(Todo[]))]
    internal partial class AppJsonSerializerContext : JsonSerializerContext
    {
    
    }
  3. Publish the project: dotnet publish
  4. Run the published app, e.g.: .\bin\Release\net8.0\win-x64\publish\ShortCircuitNativeAot.exe
  5. Make a request to the app on http://localhost:5000

Metadata

Metadata

Assignees

No one assigned

    Labels

    NativeAOTfeature-routingneeds-area-labelUsed by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions