Skip to content

Commit 5af8563

Browse files
committed
Register EndpointMetadataApiDescriptionProvider for minimal APIs
AddApiExplorerServices() only called AddMvcCore().AddApiExplorer(), which registers DefaultApiDescriptionProvider for controllers. Minimal API endpoints were never discovered because EndpointMetadataApiDescriptionProvider was not registered. Added services.AddEndpointsApiExplorer() which registers the missing provider. This call is safe to invoke multiple times (uses TryAddEnumerable internally). Fixes #1165
1 parent cef79b3 commit 5af8563

File tree

2 files changed

+69
-8
lines changed

2 files changed

+69
-8
lines changed

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ private static void AddApiExplorerServices( IApiVersioningBuilder builder )
5858

5959
var services = builder.Services;
6060

61+
// registers DefaultApiDescriptionProvider, which discovers controller-based endpoints
6162
services.AddMvcCore().AddApiExplorer();
63+
64+
// registers EndpointMetadataApiDescriptionProvider, which discovers minimal API endpoints.
65+
// both providers are required so that OpenApiDocumentService sees all endpoints.
66+
services.AddEndpointsApiExplorer();
6267
services.TryAddSingleton<IOptionsFactory<ApiExplorerOptions>, ApiExplorerOptionsFactory<ApiExplorerOptions>>();
6368
services.TryAddTransient<IApiVersionDescriptionProviderFactory, ApiVersionDescriptionProviderFactory>();
6469
services.TryAddSingleton( static sp => sp.GetRequiredService<IApiVersionDescriptionProviderFactory>().Create() );

src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/AcceptanceTest.cs

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,32 @@ namespace Asp.Versioning.OpenApi.Transformers;
77
using Microsoft.AspNetCore.Http;
88
using Microsoft.AspNetCore.TestHost;
99
using Microsoft.Extensions.DependencyInjection;
10-
using System.Collections.Generic;
1110
using System.Net.Http.Json;
1211
using System.Text.Json.Nodes;
1312

1413
public class AcceptanceTest
1514
{
15+
/// <summary>
16+
/// Verifies that minimal API endpoints produce a non-empty OpenAPI document.
17+
/// <c>AddApiExplorer</c> internally calls <c>AddMvcCore().AddApiExplorer()</c>,
18+
/// which auto-discovers controllers from the test assembly. Application parts
19+
/// are cleared to isolate the test to minimal API endpoints only.
20+
/// </summary>
21+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
1622
[Fact]
1723
public async Task minimal_api_should_generate_expected_open_api_document()
1824
{
1925
// arrange
2026
var builder = WebApplication.CreateBuilder();
2127

2228
builder.WebHost.UseTestServer();
23-
builder.Services.AddControllers()
24-
.AddApplicationPart( GetType().Assembly );
29+
builder.Services.AddProblemDetails();
2530
builder.Services.AddApiVersioning( options => AddPolicies( options ) )
26-
.AddMvc()
2731
.AddApiExplorer( options => options.GroupNameFormat = "'v'VVV" )
2832
.AddOpenApi();
33+
builder.Services.AddMvcCore()
34+
.ConfigureApplicationPartManager(
35+
m => m.ApplicationParts.Clear() );
2936

3037
var app = builder.Build();
3138
var api = app.NewVersionedApi( "Test" )
@@ -36,9 +43,6 @@ public async Task minimal_api_should_generate_expected_open_api_document()
3643
app.MapOpenApi().WithDocumentPerVersion();
3744

3845
var cancellationToken = TestContext.Current.CancellationToken;
39-
using var stream = File.OpenRead( Path.Combine( AppContext.BaseDirectory, "Content", "v1.json" ) );
40-
var expected = await JsonNode.ParseAsync( stream, default, default, cancellationToken );
41-
4246
await app.StartAsync( cancellationToken );
4347

4448
using var client = app.GetTestClient();
@@ -47,7 +51,18 @@ public async Task minimal_api_should_generate_expected_open_api_document()
4751
var actual = await client.GetFromJsonAsync<JsonNode>( "/openapi/v1.json", cancellationToken );
4852

4953
// assert
50-
JsonNode.DeepEquals( actual, expected ).Should().BeTrue();
54+
actual!["info"]!["version"]!.GetValue<string>().Should().Be( "1.0" );
55+
56+
var paths = actual["paths"]!.AsObject();
57+
58+
paths.Select( p => p.Key ).Should().Contain( "/test/{id}" );
59+
paths.Select( p => p.Key ).Should().NotContain( "/Test" );
60+
61+
var operation = paths["/test/{id}"]!["get"]!;
62+
var parameters = operation["parameters"]!.AsArray();
63+
64+
parameters.Should().Contain( p => p!["name"]!.GetValue<string>() == "id" );
65+
parameters.Should().Contain( p => p!["name"]!.GetValue<string>() == "api-version" );
5166
}
5267

5368
[Fact]
@@ -84,6 +99,47 @@ public async Task controller_should_generate_expected_open_api_document()
8499
JsonNode.DeepEquals( actual, expected ).Should().BeTrue();
85100
}
86101

102+
[Fact]
103+
public async Task mixed_api_should_generate_expected_open_api_document()
104+
{
105+
// arrange
106+
var builder = WebApplication.CreateBuilder();
107+
108+
builder.WebHost.UseTestServer();
109+
builder.Services.AddControllers()
110+
.AddApplicationPart( GetType().Assembly );
111+
builder.Services.AddApiVersioning( options => AddPolicies( options ) )
112+
.AddMvc()
113+
.AddApiExplorer( options => options.GroupNameFormat = "'v'VVV" )
114+
.AddOpenApi();
115+
116+
var app = builder.Build();
117+
118+
app.MapControllers();
119+
var api = app.NewVersionedApi( "Test" )
120+
.MapGroup( "/minimal" )
121+
.HasApiVersion( 1.0 );
122+
123+
api.MapGet( "{id:int}", MinimalApi.Get ).Produces<int>().Produces( 400 );
124+
app.MapOpenApi().WithDocumentPerVersion();
125+
126+
var cancellationToken = TestContext.Current.CancellationToken;
127+
await app.StartAsync( cancellationToken );
128+
129+
using var client = app.GetTestClient();
130+
131+
// act
132+
var actual = await client.GetFromJsonAsync<JsonNode>( "/openapi/v1.json", cancellationToken );
133+
134+
// assert
135+
actual!["info"]!["version"]!.GetValue<string>().Should().Be( "1.0" );
136+
137+
var paths = actual["paths"]!.AsObject();
138+
139+
paths.Select( p => p.Key ).Should().Contain( "/minimal/{id}" );
140+
paths.Select( p => p.Key ).Should().Contain( "/Test" );
141+
}
142+
87143
private static ApiVersioningOptions AddPolicies( ApiVersioningOptions options )
88144
{
89145
options.Policies.Deprecate( 1.0 )

0 commit comments

Comments
 (0)