Skip to content

Commit b5a54d2

Browse files
Refactor Minimal API support to make the setups consistent
1 parent ff6b588 commit b5a54d2

4 files changed

Lines changed: 52 additions & 106 deletions

File tree

examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@
297297

298298
if ( app.Environment.IsDevelopment() )
299299
{
300-
app.IncludeVersionedEndpoints().MapOpenApi().WithDocumentPerVersion();
300+
app.MapOpenApi().WithDocumentPerVersion();
301301
app.MapScalarApiReference(
302302
options =>
303303
{

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

Lines changed: 8 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@ namespace Microsoft.AspNetCore.Builder;
66

77
using Asp.Versioning;
88
using Asp.Versioning.ApiExplorer;
9-
using Asp.Versioning.OpenApi;
109
using Asp.Versioning.OpenApi.Internal;
1110
using Microsoft.AspNetCore.Builder;
12-
using Microsoft.AspNetCore.OpenApi;
13-
using Microsoft.AspNetCore.Routing;
11+
using Microsoft.AspNetCore.Http;
1412
using Microsoft.Extensions.DependencyInjection;
15-
using System.Diagnostics.CodeAnalysis;
1613

1714
/// <summary>
1815
/// Provides extension methods for <see cref="IEndpointConventionBuilder"/>.
@@ -32,91 +29,27 @@ public static class IEndpointConventionBuilderExtensions
3229
/// <returns>The original <see cref="IEndpointConventionBuilder">endpoint convention builder</see>.</returns>
3330
public IEndpointConventionBuilder WithDocumentPerVersion()
3431
{
35-
builder.Finally( new WithoutRecursion().Run );
32+
builder.Finally( ApplyApiVersioning );
3633
return builder;
3734
}
3835
}
3936

40-
// This is a workaround to prevent infinite recursion when applying API versioning conventions to the OpenAPI
41-
// endpoint, which occurs because IApiVersionDescriptionProviderFactory.Create calls back into the endpoint's
42-
// RequestDelegate to resolve the IApiVersionDescriptionProvider, which causes the conventions to be applied again.
43-
private sealed class WithoutRecursion
44-
{
45-
private bool recursed;
46-
47-
public void Run( EndpointBuilder builder )
48-
{
49-
if ( recursed )
50-
{
51-
return;
52-
}
53-
54-
recursed = true;
55-
ApplyApiVersioning( builder );
56-
recursed = false;
57-
}
58-
}
59-
6037
private static void ApplyApiVersioning( EndpointBuilder builder )
6138
{
6239
if ( builder.RequestDelegate is { } action )
6340
{
64-
#pragma warning disable CA2000 // Dispose objects before losing scope
65-
var requestServices = NewRequestServices( builder.ApplicationServices );
66-
#pragma warning restore CA2000 // Dispose objects before losing scope
67-
68-
builder.RequestDelegate = context =>
69-
{
70-
context.RequestServices = requestServices;
71-
return action( context );
72-
};
41+
builder.RequestDelegate = context => InterceptRequestServices( context, action );
7342
}
7443
}
7544

76-
[UnconditionalSuppressMessage( "ILLink", "IL3050" )]
77-
private static KeyedServiceContainer NewRequestServices( IServiceProvider services )
45+
private static Task InterceptRequestServices( HttpContext context, RequestDelegate action )
7846
{
79-
var configure = services.GetRequiredKeyedService<Action<ApiVersionDescription, OpenApiOptions>>( typeof( ApiVersion ) );
80-
var factory = services.GetRequiredService<IApiVersionDescriptionProviderFactory>();
81-
var sources = services.GetRequiredService<ConfigureOpenApiOptions>().DataSources;
82-
var keyedServices = new KeyedServiceContainer( services );
83-
var names = new List<string>();
84-
IApiVersionDescriptionProvider provider;
85-
86-
if ( sources.Count == 0 )
47+
if ( context.RequestServices is not KeyedServiceContainer requestServices )
8748
{
88-
provider = factory.Create();
89-
}
90-
else
91-
{
92-
using var source = new CompositeEndpointDataSource( sources );
93-
provider = factory.Create( source );
94-
}
95-
96-
foreach ( var description in provider.ApiVersionDescriptions )
97-
{
98-
names.Add( description.GroupName );
99-
keyedServices.Add( Type.OpenApiSchemaService, description.GroupName, Class.OpenApiSchemaService.New );
100-
keyedServices.Add( Type.OpenApiDocumentService, description.GroupName, Class.OpenApiDocumentService.New );
101-
keyedServices.Add(
102-
typeof( IOpenApiDocumentProvider ),
103-
description.GroupName,
104-
( sp, k ) => sp.GetRequiredKeyedService( Type.OpenApiDocumentService, k ) );
105-
}
106-
107-
if ( names.Count > 0 )
108-
{
109-
var array = Array.CreateInstance( Type.NamedService, names.Count );
110-
111-
for ( var i = 0; i < names.Count; i++ )
112-
{
113-
array.SetValue( Class.NamedService.New( names[i] ), i );
114-
}
115-
116-
keyedServices.Add( Type.IDocumentProvider, Class.OpenApiDocumentProvider.New );
117-
keyedServices.Add( Type.IEnumerableOfNamedService, array );
49+
requestServices = context.RequestServices.GetRequiredService<KeyedServiceContainer>();
11850
}
11951

120-
return keyedServices;
52+
context.RequestServices = requestServices;
53+
return action( context );
12154
}
12255
}

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

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

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace Microsoft.Extensions.DependencyInjection;
77
using Asp.Versioning;
88
using Asp.Versioning.ApiExplorer;
99
using Asp.Versioning.OpenApi;
10+
using Asp.Versioning.OpenApi.Internal;
11+
using Asp.Versioning.OpenApi.Transformers;
1012
using Microsoft.AspNetCore.Http.Json;
1113
using Microsoft.AspNetCore.OpenApi;
1214
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -96,8 +98,10 @@ private static void AddOpenApiServices( IApiVersioningBuilder builder )
9698

9799
var services = builder.Services;
98100

101+
services.AddTransient( NewRequestServices );
102+
services.Add( Singleton( Type.IDocumentProvider, ResolveDocumentProvider ) );
99103
services.AddOptions<OpenApiDocumentDescriptionOptions>();
100-
services.Add( Singleton<ConfigureOpenApiOptions, ConfigureOpenApiOptions>() );
104+
services.Add( Transient<ConfigureOpenApiOptions, ConfigureOpenApiOptions>() );
101105
services.TryAddEnumerable( Singleton<IConfigureOptions<OpenApiOptions>, ConfigureOpenApiOptions>( static sp => sp.GetRequiredService<ConfigureOpenApiOptions>() ) );
102106

103107
if ( GetJsonConfiguration() is { } descriptor )
@@ -118,4 +122,42 @@ private static void AddOpenApiServices( IApiVersioningBuilder builder )
118122
#pragma warning disable IDE0060
119123

120124
private static Action<ApiVersionDescription, OpenApiOptions> NoOptions( IServiceProvider provider, object key ) => static ( _, _ ) => { };
125+
126+
private static object ResolveDocumentProvider( IServiceProvider provider ) =>
127+
provider.GetRequiredService<KeyedServiceContainer>().GetRequiredService( Type.IDocumentProvider );
128+
129+
[UnconditionalSuppressMessage( "ILLink", "IL3050" )]
130+
private static KeyedServiceContainer NewRequestServices( IServiceProvider services )
131+
{
132+
var configure = services.GetRequiredKeyedService<Action<ApiVersionDescription, OpenApiOptions>>( typeof( ApiVersion ) );
133+
var provider = services.GetRequiredService<IApiVersionDescriptionProvider>();
134+
var keyedServices = new KeyedServiceContainer( services );
135+
var names = new List<string>();
136+
137+
foreach ( var description in provider.ApiVersionDescriptions )
138+
{
139+
names.Add( description.GroupName );
140+
keyedServices.Add( Type.OpenApiSchemaService, description.GroupName, Class.OpenApiSchemaService.New );
141+
keyedServices.Add( Type.OpenApiDocumentService, description.GroupName, Class.OpenApiDocumentService.New );
142+
keyedServices.Add(
143+
typeof( IOpenApiDocumentProvider ),
144+
description.GroupName,
145+
( sp, k ) => sp.GetRequiredKeyedService( Type.OpenApiDocumentService, k ) );
146+
}
147+
148+
if ( names.Count > 0 )
149+
{
150+
var array = Array.CreateInstance( Type.NamedService, names.Count );
151+
152+
for ( var i = 0; i < names.Count; i++ )
153+
{
154+
array.SetValue( Class.NamedService.New( names[i] ), i );
155+
}
156+
157+
keyedServices.Add( Type.IDocumentProvider, Class.OpenApiDocumentProvider.New );
158+
keyedServices.Add( Type.IEnumerableOfNamedService, array );
159+
}
160+
161+
return keyedServices;
162+
}
121163
}

0 commit comments

Comments
 (0)