Skip to content

Commit 794f1ee

Browse files
committed
Add IOpenApiDocumentProvider interface and implementation
1 parent 482dab4 commit 794f1ee

File tree

5 files changed

+125
-1
lines changed

5 files changed

+125
-1
lines changed

src/OpenApi/src/Extensions/OpenApiServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ private static IServiceCollection AddOpenApiCore(this IServiceCollection service
110110
services.AddEndpointsApiExplorer();
111111
services.AddKeyedSingleton<OpenApiSchemaService>(documentName);
112112
services.AddKeyedSingleton<OpenApiDocumentService>(documentName);
113+
services.AddKeyedSingleton<IOpenApiDocumentProvider, OpenApiDocumentService>(documentName);
114+
113115
// Required for build-time generation
114116
services.AddSingleton<IDocumentProvider, OpenApiDocumentProvider>();
115117
// Required to resolve document names for build-time generation

src/OpenApi/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#nullable enable
2+
Microsoft.AspNetCore.OpenApi.IOpenApiDocumentProvider
3+
Microsoft.AspNetCore.OpenApi.IOpenApiDocumentProvider.GetOpenApiDocumentAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.OpenApi.Models.OpenApiDocument!>!
24
static Microsoft.AspNetCore.Builder.OpenApiEndpointConventionBuilderExtensions.AddOpenApiOperationTransformer<TBuilder>(this TBuilder builder, System.Func<Microsoft.OpenApi.Models.OpenApiOperation!, Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext!, System.Threading.CancellationToken, System.Threading.Tasks.Task!>! transformer) -> TBuilder
35
Microsoft.AspNetCore.OpenApi.OpenApiDocumentTransformerContext.GetOrCreateSchemaAsync(System.Type! type, Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription? parameterDescription = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.OpenApi.Models.OpenApiSchema!>!
46
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.Document.get -> Microsoft.OpenApi.Models.OpenApiDocument?
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.OpenApi.Models;
5+
6+
namespace Microsoft.AspNetCore.OpenApi;
7+
8+
/// <summary>
9+
/// Represents a provider for OpenAPI documents that can be used by consumers to
10+
/// retrieve generated OpenAPI documents at runtime.
11+
/// </summary>
12+
public interface IOpenApiDocumentProvider
13+
{
14+
/// <summary>
15+
/// Gets the OpenAPI document.
16+
/// </summary>
17+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
18+
/// <returns>A task that represents the asynchronous operation. The task result contains the OpenAPI document.</returns>
19+
/// <remarks>
20+
/// This method is typically used by consumers to retrieve the OpenAPI document. The generated document
21+
/// may not contain the appropriate servers information since it can be instantiated outside the context
22+
/// of an HTTP request. In these scenarios, the <see cref="OpenApiDocument"/> can be modified to
23+
/// include the appropriate servers information.
24+
/// </remarks>
25+
/// <remarks>
26+
/// Any OpenAPI transformers registered in the <see cref="OpenApiOptions"/> instance associated with
27+
/// this document will be applied to the document before it is returned.
28+
/// </remarks>
29+
Task<OpenApiDocument> GetOpenApiDocumentAsync(CancellationToken cancellationToken = default);
30+
}

src/OpenApi/src/Services/OpenApiDocumentService.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ internal sealed class OpenApiDocumentService(
3838
IHostEnvironment hostEnvironment,
3939
IOptionsMonitor<OpenApiOptions> optionsMonitor,
4040
IServiceProvider serviceProvider,
41-
IServer? server = null)
41+
IServer? server = null) : IOpenApiDocumentProvider
4242
{
4343
private readonly OpenApiOptions _options = optionsMonitor.Get(documentName);
4444
private readonly OpenApiSchemaService _componentService = serviceProvider.GetRequiredKeyedService<OpenApiSchemaService>(documentName);
@@ -744,4 +744,8 @@ private static Type GetTargetType(ApiDescription description, ApiParameterDescri
744744
targetType ??= typeof(string);
745745
return targetType;
746746
}
747+
748+
/// <inheritdoc />
749+
public Task<OpenApiDocument> GetOpenApiDocumentAsync(CancellationToken cancellationToken = default)
750+
=> GetOpenApiDocumentAsync(serviceProvider, null, cancellationToken);
747751
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiServiceCollectionExtensionsTests.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.AspNetCore.OpenApi;
5+
using Microsoft.AspNetCore.Routing;
56
using Microsoft.Extensions.ApiDescriptions;
67
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.DependencyInjection.Extensions;
9+
using Microsoft.Extensions.Hosting;
10+
using Microsoft.Extensions.Hosting.Internal;
711
using Microsoft.Extensions.Options;
812
using Microsoft.OpenApi;
13+
using Microsoft.OpenApi.Models;
914

1015
public class OpenApiServiceCollectionExtensions
1116
{
@@ -189,4 +194,85 @@ public void AddOpenApi_WithDuplicateDocumentNames_UsesLastRegistration_ValidateO
189194
Assert.Equal(documentName, namedOption.DocumentName);
190195
Assert.Equal(OpenApiSpecVersion.OpenApi2_0, namedOption.OpenApiVersion);
191196
}
197+
198+
[Fact]
199+
public void AddOpenApi_WithDefaultDocumentName_RegistersIOpenApiDocumentProviderInterface()
200+
{
201+
// Arrange
202+
var services = new ServiceCollection();
203+
// Include dependencies for OpenApiDocumentService
204+
services.AddSingleton<IHostEnvironment>(new HostingEnvironment
205+
{
206+
EnvironmentName = Environments.Development,
207+
ApplicationName = "Test Application"
208+
});
209+
services.AddLogging();
210+
services.AddRouting();
211+
212+
// Act
213+
services.AddOpenApi();
214+
var serviceProvider = services.BuildServiceProvider();
215+
216+
// Assert
217+
var documentProvider = serviceProvider.GetRequiredKeyedService<IOpenApiDocumentProvider>(Microsoft.AspNetCore.OpenApi.OpenApiConstants.DefaultDocumentName);
218+
Assert.NotNull(documentProvider);
219+
Assert.IsType<OpenApiDocumentService>(documentProvider);
220+
}
221+
222+
[Fact]
223+
public void AddOpenApi_WithCustomDocumentName_RegistersIOpenApiDocumentProviderInterface()
224+
{
225+
// Arrange
226+
var services = new ServiceCollection();
227+
// Include dependencies for OpenApiDocumentService
228+
services.AddSingleton<IHostEnvironment>(new HostingEnvironment
229+
{
230+
EnvironmentName = Environments.Development,
231+
ApplicationName = "Test Application"
232+
});
233+
services.AddLogging();
234+
services.AddRouting();
235+
var documentName = "v1";
236+
237+
// Act
238+
services.AddOpenApi(documentName);
239+
var serviceProvider = services.BuildServiceProvider();
240+
241+
// Assert
242+
var documentProvider = serviceProvider.GetRequiredKeyedService<IOpenApiDocumentProvider>(documentName.ToLowerInvariant());
243+
Assert.NotNull(documentProvider);
244+
Assert.IsType<OpenApiDocumentService>(documentProvider);
245+
}
246+
247+
[Fact]
248+
public async Task GetOpenApiDocumentAsync_ReturnsDocument()
249+
{
250+
// Arrange
251+
var services = new ServiceCollection();
252+
// Include dependencies for OpenApiDocumentService
253+
services.AddSingleton<IHostEnvironment>(new HostingEnvironment
254+
{
255+
EnvironmentName = Environments.Development,
256+
ApplicationName = "Test Application"
257+
});
258+
services.AddLogging();
259+
services.AddRouting();
260+
261+
var documentName = "v1";
262+
services.AddOpenApi(documentName);
263+
var serviceProvider = services.BuildServiceProvider();
264+
var documentProvider = serviceProvider.GetRequiredKeyedService<IOpenApiDocumentProvider>(documentName.ToLowerInvariant());
265+
266+
// Act
267+
var document = await documentProvider.GetOpenApiDocumentAsync();
268+
269+
// Assert
270+
Assert.NotNull(document);
271+
Assert.IsType<OpenApiDocument>(document);
272+
273+
// Verify basic document structure
274+
Assert.NotNull(document.Info);
275+
Assert.Equal($"Test Application | {documentName.ToLowerInvariant()}", document.Info.Title);
276+
Assert.Equal("1.0.0", document.Info.Version);
277+
}
192278
}

0 commit comments

Comments
 (0)