Skip to content

RDG throws at startup when combined with PublishAot and AuthorizationMiddleware #49855

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
halter73 opened this issue Aug 3, 2023 · 4 comments · Fixed by #49875
Closed

RDG throws at startup when combined with PublishAot and AuthorizationMiddleware #49855

halter73 opened this issue Aug 3, 2023 · 4 comments · Fixed by #49875
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-rdg NativeAOT

Comments

@halter73
Copy link
Member

halter73 commented Aug 3, 2023

RequestDelegateGenerator throws at startup when the AuthorizationMiddleware constructor resolves its AuthorizationPolicyCache and PublishAot is enabled. The throwing call to GetTypeInfo is in the generated code's RequestDelegateFactoryFunc createRequestDelegate in lines generated for the filter pipeline to handle possible object returns with objectJsonTypeInfo.

internal static void EmitJsonPreparation(this Endpoint endpoint, CodeWriter codeWriter)
{
codeWriter.WriteLine("var jsonOptions = serviceProvider?.GetService<IOptions<JsonOptions>>()?.Value ?? new JsonOptions();");
codeWriter.WriteLine($"var objectJsonTypeInfo = (JsonTypeInfo<object?>)jsonOptions.SerializerOptions.GetTypeInfo(typeof(object));");
if (endpoint.Response?.IsSerializableJsonResponse(out var responseType) == true)

I'm not exactly sure why adding the AuthorizationMiddleware triggers it, but here's the repro app:

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

app.MapGet("/", () => "Hello, World!");

app.Run();
Hand here's the unhandled startup exception:
Unhandled exception. System.NotSupportedException: JsonTypeInfo metadata for type 'System.Object' was not provided by TypeInfoResolver of type '<null>'. If using source generation, ensure that all root types passed to the serializer have been annotated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically.
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException_NoMetadataForType(Type type, IJsonTypeInfoResolver resolver)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfo(Type type)
   at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F69328E0708B4B584C5AACA22FE2C51A1CF192D6622828F613FC57C583CA77B63__GeneratedRouteBuilderExtensionsCore.<>c.<MapGet0>b__1_1(Delegate del, RequestDelegateFactoryOptions options, RequestDelegateMetadataResult inferredMetadataResult) in D:\dev\halter73\JwtBearerAot\Microsoft.AspNetCore.Http.RequestDelegateGenerator\Microsoft.AspNetCore.Http.RequestDelegateGenerator.RequestDelegateGenerator\GeneratedRouteBuilderExtensions.g.cs:line 84
   at Microsoft.AspNetCore.Routing.RouteEndpointDataSource.CreateRouteEndpointBuilder(RouteEntry entry, RoutePattern groupPrefix, IReadOnlyList`1 groupConventions, IReadOnlyList`1 groupFinallyConventions)
   at Microsoft.AspNetCore.Routing.RouteEndpointDataSource.get_Endpoints()
   at Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.CreateEndpointsUnsynchronized()
   at Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.EnsureEndpointsInitialized()
   at Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.get_Endpoints()
   at Microsoft.AspNetCore.Routing.DataSourceDependentCache`1.Initialize()
   at System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func`1 valueFactory)
   at Microsoft.AspNetCore.Routing.DataSourceDependentCache`1.EnsureInitialized()
   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationPolicyCache..ctor(EndpointDataSource dataSource)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware..ctor(RequestDelegate next, IAuthorizationPolicyProvider policyProvider, IServiceProvider services)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware..ctor(RequestDelegate next, IAuthorizationPolicyProvider policyProvider, IServiceProvider services, ILogger`1 logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddlewareInternal..ctor(RequestDelegate next, IServiceProvider services, IAuthorizationPolicyProvider policyProvider, ILogger`1 logger)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
   at Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.ReflectionMiddlewareBinder.CreateMiddleware(RequestDelegate next)
   at Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__14_1(IHostedService service, CancellationToken token)
   at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
   at Program.<Main>$(String[] args) in D:\dev\halter73\JwtBearerAot\Program.cs:line 9

@eerhardt @captainsafia

@ghost ghost added needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically NativeAOT labels Aug 3, 2023
@halter73 halter73 changed the title RDG throws at startup when combined with AuthorizationMiddleware RDG throws at startup when combined with PublishAot and AuthorizationMiddleware Aug 3, 2023
@halter73
Copy link
Member Author

halter73 commented Aug 3, 2023

I should note this is using the latest rc1 SDK and you can work around this with the following:

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine();
});

Or by not adding the AuthorizationMiddleware of course.

@captainsafia captainsafia added area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-rdg and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Aug 3, 2023
@eerhardt
Copy link
Member

eerhardt commented Aug 3, 2023

@captainsafia - I thought we fixed these issues with #49393. Is that not the case? Or is it because the source generator uses different code than RDF does? Kind of similar to #47973?

@captainsafia
Copy link
Member

captainsafia commented Aug 3, 2023

Or is it because the source generator uses different code than RDF does? Kind of similar to #47973?

Yep, the code is not shared here. :/

It's super fishy to me that the middleware registration is the triggering factor here. Looking into that now....

EDIT: I suspect this might be related to the fact that the authorization middleware resolves EndpointDataSources via DI and triggers the construction of delegates before routing executes for the first time.

@captainsafia
Copy link
Member

EDIT: I suspect this might be related to the fact that the authorization middleware resolves EndpointDataSources via DI and triggers the construction of delegates before routing executes for the first time.

To follow-up, the AuthorizationMiddleware isn't really the problem here. As mentioned above, it forces the resolution of the endpoints earlier so you get the exception at startup instead of when you hit an endpoint.

The real problem is that RDG doesn't play nice if you're running with PublishAoT enabled but don't have the JSON source generator enabled (that's what the NSE warns about anyway).

We could make the failure mode a little nicer at runtime ("hey you're running with aot enabled but don't have JSON source gen setup"). That would also make the RDG more useful when people want to enable it outside the context of AoT.

Thoughts?

@ghost ghost locked as resolved and limited conversation to collaborators Sep 4, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-rdg NativeAOT
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants