55namespace Microsoft . Extensions . DependencyInjection ;
66
77using Asp . Versioning ;
8- using Asp . Versioning . ApiExplorer ;
98using Asp . Versioning . OpenApi ;
109using Asp . Versioning . OpenApi . Configuration ;
1110using Asp . Versioning . OpenApi . Transformers ;
12- using Microsoft . AspNetCore . Http . Json ;
1311using Microsoft . AspNetCore . OpenApi ;
1412using Microsoft . Extensions . DependencyInjection . Extensions ;
1513using Microsoft . Extensions . Hosting ;
1614using Microsoft . Extensions . Options ;
17- using System . ComponentModel . Design ;
1815using System . Diagnostics ;
1916using System . Reflection ;
2017using static Microsoft . Extensions . DependencyInjection . ServiceDescriptor ;
@@ -65,7 +62,18 @@ private static void AddOpenApiServices( IApiVersioningBuilder builder, Assembly[
6562
6663 var services = builder . Services ;
6764
68- services . AddTransient ( serviceProvider => NewRequestServices ( serviceProvider , services ) ) ;
65+ services . Add ( GetDocumentProviderDescriptor ( ) ) ;
66+
67+ var hostDescriptor = services . Single (
68+ s => ! s . IsKeyedService &&
69+ s . ServiceType == typeof ( IHost ) &&
70+ s . Lifetime == ServiceLifetime . Singleton &&
71+ s . ImplementationInstance is null &&
72+ s . ImplementationType is null &&
73+ s . ImplementationFactory is not null ) ;
74+ var hostDescriptorIndex = services . IndexOf ( hostDescriptor ) ;
75+
76+ builder . Services [ hostDescriptorIndex ] = CreateHostWrapperDescriptor ( services , hostDescriptor . ImplementationFactory ! ) ;
6977
7078 services . AddSingleton < VersionedOpenApiOptionsFactory > ( ) ;
7179 services . TryAddEnumerable ( Transient < IPostConfigureOptions < OpenApiOptions > , ConfigureOpenApiOptions > ( ) ) ;
@@ -74,6 +82,56 @@ private static void AddOpenApiServices( IApiVersioningBuilder builder, Assembly[
7482 services . TryAddTransient ( sp => new XmlCommentsTransformer ( sp . GetRequiredService < XmlCommentsFile > ( ) ) ) ;
7583 }
7684
85+ private static ServiceDescriptor GetDocumentProviderDescriptor ( )
86+ {
87+ var serviceCollection = new ServiceCollection ( ) ;
88+ serviceCollection . AddOpenApi ( ) ;
89+ foreach ( var descriptor in serviceCollection )
90+ {
91+ if ( descriptor . ServiceType . FullName == "Microsoft.Extensions.ApiDescriptions.IDocumentProvider" )
92+ {
93+ return descriptor ;
94+ }
95+ }
96+
97+ throw new UnreachableException ( ) ;
98+ }
99+
100+ private static ServiceDescriptor CreateHostWrapperDescriptor ( IServiceCollection serviceCollection , Func < IServiceProvider , object > hostFactory )
101+ {
102+ Func < IServiceProvider , object > updatedHostFactory = serviceProvider =>
103+ {
104+ var originalHost = ( IHost ) hostFactory ( serviceProvider ) ;
105+ return new OpenApiHost ( originalHost , NewRequestServices ( serviceProvider , serviceCollection ) ) ;
106+ } ;
107+
108+ return new ServiceDescriptor ( typeof ( IHost ) , updatedHostFactory , ServiceLifetime . Singleton ) ;
109+ }
110+
111+ private sealed class OpenApiHost : IHost
112+ {
113+ private readonly IHost originalHost ;
114+ private readonly IServiceProvider customServiceProvider ;
115+
116+ public OpenApiHost ( IHost originalHost , IServiceProvider customServiceProvider )
117+ {
118+ this . originalHost = originalHost ;
119+ this . customServiceProvider = customServiceProvider ;
120+ }
121+
122+ public IServiceProvider Services
123+ => customServiceProvider ;
124+
125+ public void Dispose ( )
126+ => originalHost . Dispose ( ) ;
127+
128+ public Task StartAsync ( CancellationToken cancellationToken = default )
129+ => originalHost . StartAsync ( cancellationToken ) ;
130+
131+ public Task StopAsync ( CancellationToken cancellationToken = default )
132+ => originalHost . StopAsync ( cancellationToken ) ;
133+ }
134+
77135 // NOTE: The calling assembly must be captured at the call site that invokes AddOpenApi. In 99% of the cases that
78136 // should be the entry point to the application. It is technically possible to be invoked from some other assembly -
79137 // perhaps another extension library. If that were to happen, that library must resolve the path on its own and
@@ -90,21 +148,8 @@ private static Assembly[] GetAssemblies( Assembly callingAssembly )
90148 return [ .. assemblies ] ;
91149 }
92150
93- [ UnconditionalSuppressMessage ( "ILLink" , "IL3050" ) ]
94151 private static AggregateKeyedServiceProvider NewRequestServices ( IServiceProvider services , IServiceCollection parentServiceCollection )
95152 {
96- var provider = services . GetRequiredService < IApiVersionDescriptionProvider > ( ) ;
97- var container = new AggregateKeyedServiceProvider ( services ) ;
98- var descriptions = provider . ApiVersionDescriptions ;
99-
100- for ( var i = 0 ; i < descriptions . Count ; i ++ )
101- {
102- var description = descriptions [ i ] ;
103- var serviceCollection = new ServiceCollection ( ) ;
104- serviceCollection . AddOpenApi ( description . GroupName ) ;
105- container . Add ( serviceCollection , parentServiceCollection ) ;
106- }
107-
108- return container ;
153+ return new AggregateKeyedServiceProvider ( services , parentServiceCollection ) ;
109154 }
110155}
0 commit comments