Skip to content

Commit 84072db

Browse files
This branch used a private, one-off build of Microsoft.AspNetCore.OpenApi that makes the bare minimum types and constructors public to avoid all use of reflection.
https://github.com/commonsensesoftware/aspnetcore/tree/css/versioning-with-openapi
1 parent aa8bb4c commit 84072db

8 files changed

Lines changed: 90 additions & 286 deletions

File tree

src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Builder/IEndpointConventionBuilderExtensions.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44

55
namespace Microsoft.AspNetCore.Builder;
66

7-
using Asp.Versioning;
8-
using Asp.Versioning.ApiExplorer;
9-
using Asp.Versioning.OpenApi.Reflection;
10-
using Microsoft.AspNetCore.Builder;
117
using Microsoft.AspNetCore.Http;
128
using Microsoft.Extensions.DependencyInjection;
139

@@ -44,9 +40,9 @@ private static void ApplyApiVersioning( EndpointBuilder builder )
4440

4541
private static Task InterceptRequestServices( HttpContext context, RequestDelegate action )
4642
{
47-
if ( context.RequestServices is not KeyedServiceContainer requestServices )
43+
if ( context.RequestServices is not KeyedServiceProvider requestServices )
4844
{
49-
requestServices = context.RequestServices.GetRequiredService<KeyedServiceContainer>();
45+
requestServices = context.RequestServices.GetRequiredService<KeyedServiceProvider>();
5046
}
5147

5248
context.RequestServices = requestServices;

src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Configuration/ConfigureOpenApiOptions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace Asp.Versioning.OpenApi.Configuration;
66

77
using Asp.Versioning.ApiExplorer;
8-
using Asp.Versioning.OpenApi.Reflection;
98
using Asp.Versioning.OpenApi.Transformers;
109
using Microsoft.AspNetCore.OpenApi;
1110
using Microsoft.Extensions.Options;
@@ -48,7 +47,7 @@ private static void Configure( VersionedOpenApiOptions versionedOptions, XmlComm
4847
var options = versionedOptions.Document;
4948
var apiExplorer = new ApiExplorerTransformer( versionedOptions );
5049

51-
options.SetDocumentName( versionedOptions.Description.GroupName );
50+
options.DocumentName = versionedOptions.Description.GroupName;
5251
options.AddDocumentTransformer( apiExplorer );
5352
options.AddSchemaTransformer( apiExplorer );
5453
options.AddOperationTransformer( apiExplorer );

src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/DependencyInjection/IApiVersioningBuilderExtensions.cs

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
namespace Microsoft.Extensions.DependencyInjection;
66

77
using Asp.Versioning;
8-
using Asp.Versioning.ApiExplorer;
98
using Asp.Versioning.OpenApi;
109
using Asp.Versioning.OpenApi.Configuration;
11-
using Asp.Versioning.OpenApi.Reflection;
1210
using Asp.Versioning.OpenApi.Transformers;
1311
using Microsoft.AspNetCore.Http.Json;
1412
using Microsoft.AspNetCore.OpenApi;
13+
using Microsoft.Extensions.ApiDescriptions;
1514
using Microsoft.Extensions.DependencyInjection.Extensions;
1615
using Microsoft.Extensions.Hosting;
1716
using Microsoft.Extensions.Options;
@@ -64,8 +63,8 @@ private static void AddOpenApiServices( IApiVersioningBuilder builder, Assembly[
6463

6564
var services = builder.Services;
6665

67-
services.AddTransient( NewRequestServices );
68-
services.Add( Singleton( Type.IDocumentProvider, ResolveDocumentProvider ) );
66+
services.AddSingleton<KeyedServiceProvider>();
67+
services.AddSingleton( ResolveDocumentProvider );
6968
services.AddSingleton<VersionedOpenApiOptionsFactory>();
7069
services.TryAddEnumerable( Transient<IPostConfigureOptions<OpenApiOptions>, ConfigureOpenApiOptions>() );
7170
services.TryAdd( Singleton<IOptionsFactory<VersionedOpenApiOptions>>( EM.GetRequiredService<VersionedOpenApiOptionsFactory> ) );
@@ -94,6 +93,9 @@ private static Assembly[] GetAssemblies( Assembly callingAssembly )
9493
return [.. assemblies];
9594
}
9695

96+
private static IDocumentProvider ResolveDocumentProvider( IServiceProvider provider ) =>
97+
provider.GetRequiredService<KeyedServiceProvider>().GetRequiredService<IDocumentProvider>();
98+
9799
// HACK: the json configuration is internal; this approach negates the use of reflection
98100
// REF: https://github.com/dotnet/aspnetcore/blob/08a9fc2c3864d99759ab3d71cfda868d852bfc4b/src/OpenApi/src/Extensions/OpenApiServiceCollectionExtensions.cs#L121
99101
private static ServiceDescriptor? GetJsonConfiguration()
@@ -102,47 +104,4 @@ private static Assembly[] GetAssemblies( Assembly callingAssembly )
102104
services.AddOpenApi( "*" );
103105
return services.SingleOrDefault( sd => sd.ServiceType == typeof( IConfigureOptions<JsonOptions> ) );
104106
}
105-
106-
private static object ResolveDocumentProvider( IServiceProvider provider ) =>
107-
provider.GetRequiredService<KeyedServiceContainer>().GetRequiredService( Type.IDocumentProvider );
108-
109-
[UnconditionalSuppressMessage( "ILLink", "IL3050" )]
110-
private static KeyedServiceContainer NewRequestServices( IServiceProvider services )
111-
{
112-
var provider = services.GetRequiredService<IApiVersionDescriptionProvider>();
113-
var container = new KeyedServiceContainer( services );
114-
var type = typeof( IOpenApiDocumentProvider );
115-
var descriptions = provider.ApiVersionDescriptions;
116-
var names = new List<string>( descriptions.Count );
117-
118-
for ( var i = 0; i < descriptions.Count; i++ )
119-
{
120-
var description = descriptions[i];
121-
122-
// REF: https://github.com/dotnet/aspnetcore/blob/319e87fd950a99f3baae2aa79db3d4fb68783d85/src/OpenApi/src/Extensions/OpenApiServiceCollectionExtensions.cs#L64
123-
#pragma warning disable CA1308 // Normalize strings to uppercase
124-
var key = description.GroupName.ToLowerInvariant();
125-
#pragma warning restore CA1308
126-
127-
names.Add( key );
128-
container.AddService( Type.OpenApiSchemaService, key, Class.OpenApiSchemaService.New );
129-
container.AddService( Type.OpenApiDocumentService, key, Class.OpenApiDocumentService.New );
130-
container.AddService( type, key, ( sp, k ) => sp.GetRequiredKeyedService( Type.OpenApiDocumentService, k ) );
131-
}
132-
133-
if ( names.Count > 0 )
134-
{
135-
var array = Array.CreateInstance( Type.NamedService, names.Count );
136-
137-
for ( var i = 0; i < names.Count; i++ )
138-
{
139-
array.SetValue( Class.NamedService.New( names[i] ), i );
140-
}
141-
142-
container.AddService( Type.IDocumentProvider, Class.OpenApiDocumentProvider.New );
143-
container.AddService( Type.IEnumerableOfNamedService, array );
144-
}
145-
146-
return container;
147-
}
148107
}

src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/DependencyInjection/KeyedServiceContainer.cs

Lines changed: 0 additions & 63 deletions
This file was deleted.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
#pragma warning disable CA1812
4+
#pragma warning disable IDE0130
5+
6+
namespace Microsoft.Extensions.DependencyInjection;
7+
8+
using Asp.Versioning.ApiExplorer;
9+
using Microsoft.AspNetCore.Hosting.Server;
10+
using Microsoft.AspNetCore.Http.Json;
11+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
12+
using Microsoft.AspNetCore.OpenApi;
13+
using Microsoft.Extensions.ApiDescriptions;
14+
using Microsoft.Extensions.DependencyInjection.Extensions;
15+
using Microsoft.Extensions.Hosting;
16+
using Microsoft.Extensions.Options;
17+
using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor;
18+
19+
/// <summary>
20+
/// Represents a service provider that bridges two service providers together to support keyed services for OpenAPI documents.
21+
/// </summary>
22+
/// <remarks>Service resolution must go through this class. DI does not support container/provider chaining. This also
23+
/// requires explicit constructor registration to ensure that happens.</remarks>
24+
internal sealed class KeyedServiceProvider : IKeyedServiceProvider
25+
{
26+
private readonly ServiceProvider me;
27+
private readonly IServiceProvider parent;
28+
29+
public KeyedServiceProvider( IServiceProvider parent, IApiVersionDescriptionProvider provider )
30+
{
31+
this.parent = parent;
32+
var services = new ServiceCollection();
33+
var descriptions = provider.ApiVersionDescriptions;
34+
35+
for ( var i = 0; i < descriptions.Count; i++ )
36+
{
37+
var description = descriptions[i];
38+
var documentName = GetDocumentName( description );
39+
40+
services.Add( KeyedSingleton( documentName, NewOpenApiSchemaService ) );
41+
services.Add( KeyedSingleton( documentName, NewOpenApiDocumentService ) );
42+
services.Add( KeyedSingleton( documentName, ResolveDocumentProvider ) );
43+
services.AddSingleton( new NamedService<OpenApiDocumentService>( documentName ) );
44+
}
45+
46+
services.Add( Singleton<IDocumentProvider>( _ => new OpenApiDocumentProvider( this ) ) );
47+
me = services.BuildServiceProvider();
48+
}
49+
50+
public object? GetKeyedService( Type serviceType, object? serviceKey ) =>
51+
me.GetKeyedService( serviceType, serviceKey ) ?? parent.GetKeyedService( serviceType, serviceKey );
52+
53+
public object GetRequiredKeyedService( Type serviceType, object? serviceKey ) =>
54+
me.GetKeyedService( serviceType, serviceKey ) ?? parent.GetRequiredKeyedService( serviceType, serviceKey );
55+
56+
public object? GetService( Type serviceType ) =>
57+
me.GetService( serviceType ) ?? parent.GetService( serviceType );
58+
59+
// REF: https://github.com/dotnet/aspnetcore/blob/319e87fd950a99f3baae2aa79db3d4fb68783d85/src/OpenApi/src/Extensions/OpenApiServiceCollectionExtensions.cs#L64
60+
#pragma warning disable CA1308 // Normalize strings to uppercase
61+
private static string GetDocumentName( ApiVersionDescription description ) => description.GroupName.ToLowerInvariant();
62+
#pragma warning restore CA1308
63+
64+
private static IOpenApiDocumentProvider ResolveDocumentProvider( IServiceProvider provider, object key ) =>
65+
provider.GetRequiredKeyedService<OpenApiDocumentService>( key );
66+
67+
private OpenApiSchemaService NewOpenApiSchemaService( IServiceProvider provider, object key ) =>
68+
new(
69+
key.ToString()!,
70+
this.GetRequiredService<IOptions<JsonOptions>>(),
71+
this.GetRequiredService<IOptionsMonitor<OpenApiOptions>>() );
72+
73+
private OpenApiDocumentService NewOpenApiDocumentService( IServiceProvider provider, object key ) =>
74+
new(
75+
key.ToString()!,
76+
this.GetRequiredService<IApiDescriptionGroupCollectionProvider>(),
77+
this.GetRequiredService<IHostEnvironment>(),
78+
this.GetRequiredService<IOptionsMonitor<OpenApiOptions>>(),
79+
this,
80+
this.GetService<IServer>() );
81+
}

src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Class.cs

Lines changed: 0 additions & 111 deletions
This file was deleted.

0 commit comments

Comments
 (0)