Skip to content

Commit ee6f2dd

Browse files
committed
Set supported servers from IServerAddressFeature
1 parent d168bb8 commit ee6f2dd

File tree

4 files changed

+108
-5
lines changed

4 files changed

+108
-5
lines changed

src/OpenApi/src/Services/OpenApiDocumentService.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
using System.IO.Pipelines;
1010
using System.Linq;
1111
using System.Reflection;
12+
using Microsoft.AspNetCore.Hosting.Server;
13+
using Microsoft.AspNetCore.Hosting.Server.Features;
1214
using Microsoft.AspNetCore.Http;
1315
using Microsoft.AspNetCore.Http.Metadata;
1416
using Microsoft.AspNetCore.Mvc;
@@ -30,7 +32,8 @@ internal sealed class OpenApiDocumentService(
3032
IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider,
3133
IHostEnvironment hostEnvironment,
3234
IOptionsMonitor<OpenApiOptions> optionsMonitor,
33-
IServiceProvider serviceProvider)
35+
IServiceProvider serviceProvider,
36+
IServer? server = null)
3437
{
3538
private readonly OpenApiOptions _options = optionsMonitor.Get(documentName);
3639
private readonly OpenApiSchemaService _componentService = serviceProvider.GetRequiredKeyedService<OpenApiSchemaService>(documentName);
@@ -60,6 +63,7 @@ public async Task<OpenApiDocument> GetOpenApiDocumentAsync(CancellationToken can
6063
{
6164
Info = GetOpenApiInfo(),
6265
Paths = await GetOpenApiPathsAsync(capturedTags, cancellationToken),
66+
Servers = GetOpenApiServers(),
6367
Tags = [.. capturedTags]
6468
};
6569
await ApplyTransformersAsync(document, cancellationToken);
@@ -96,6 +100,15 @@ internal OpenApiInfo GetOpenApiInfo()
96100
};
97101
}
98102

103+
internal List<OpenApiServer> GetOpenApiServers()
104+
{
105+
if (server is not null && server.Features.Get<IServerAddressesFeature>()?.Addresses is { Count: > 0 } addresses)
106+
{
107+
return addresses.Select(address => new OpenApiServer { Url = address }).ToList();
108+
}
109+
return [];
110+
}
111+
99112
/// <summary>
100113
/// Gets the OpenApiPaths for the document based on the ApiDescriptions.
101114
/// </summary>

src/OpenApi/test/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Info.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public void GetOpenApiInfo_RespectsHostEnvironmentName()
2323
new Mock<IApiDescriptionGroupCollectionProvider>().Object,
2424
hostEnvironment,
2525
new Mock<IOptionsMonitor<OpenApiOptions>>().Object,
26-
new Mock<IKeyedServiceProvider>().Object);
26+
new Mock<IKeyedServiceProvider>().Object,
27+
new OpenApiTestServer());
2728

2829
// Act
2930
var info = docService.GetOpenApiInfo();
@@ -45,7 +46,8 @@ public void GetOpenApiInfo_RespectsDocumentName()
4546
new Mock<IApiDescriptionGroupCollectionProvider>().Object,
4647
hostEnvironment,
4748
new Mock<IOptionsMonitor<OpenApiOptions>>().Object,
48-
new Mock<IKeyedServiceProvider>().Object);
49+
new Mock<IKeyedServiceProvider>().Object,
50+
new OpenApiTestServer());
4951

5052
// Act
5153
var info = docService.GetOpenApiInfo();
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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.AspNetCore.Mvc.ApiExplorer;
5+
using Microsoft.AspNetCore.OpenApi;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Hosting.Internal;
8+
using Microsoft.Extensions.Options;
9+
using Moq;
10+
11+
public partial class OpenApiDocumentServiceTests
12+
{
13+
[Fact]
14+
public void GetOpenApiServers_HandlesServerAddressFeatureWithValues()
15+
{
16+
// Arrange
17+
var hostEnvironment = new HostingEnvironment
18+
{
19+
ApplicationName = "TestApplication"
20+
};
21+
var docService = new OpenApiDocumentService(
22+
"v1",
23+
new Mock<IApiDescriptionGroupCollectionProvider>().Object,
24+
hostEnvironment,
25+
new Mock<IOptionsMonitor<OpenApiOptions>>().Object,
26+
new Mock<IKeyedServiceProvider>().Object,
27+
new OpenApiTestServer(["http://localhost:5000"]));
28+
29+
// Act
30+
var servers = docService.GetOpenApiServers();
31+
32+
// Assert
33+
Assert.Contains("http://localhost:5000", servers.Select(s => s.Url));
34+
}
35+
36+
[Fact]
37+
public void GetOpenApiServers_HandlesServerAddressFeatureWithNoValues()
38+
{
39+
// Arrange
40+
var hostEnvironment = new HostingEnvironment
41+
{
42+
ApplicationName = "TestApplication"
43+
};
44+
var docService = new OpenApiDocumentService(
45+
"v2",
46+
new Mock<IApiDescriptionGroupCollectionProvider>().Object,
47+
hostEnvironment,
48+
new Mock<IOptionsMonitor<OpenApiOptions>>().Object,
49+
new Mock<IKeyedServiceProvider>().Object,
50+
new OpenApiTestServer());
51+
52+
// Act
53+
var servers = docService.GetOpenApiServers();
54+
55+
// Assert
56+
Assert.Empty(servers);
57+
}
58+
}

src/OpenApi/test/Services/OpenApiDocumentServiceTestsBase.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
using System.Reflection;
55
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Hosting.Server;
7+
using Microsoft.AspNetCore.Hosting.Server.Features;
8+
using Microsoft.AspNetCore.Http.Features;
69
using Microsoft.AspNetCore.Mvc;
710
using Microsoft.AspNetCore.Mvc.Abstractions;
811
using Microsoft.AspNetCore.Mvc.ActionConstraints;
@@ -75,7 +78,7 @@ internal static OpenApiDocumentService CreateDocumentService(ActionDescriptor ac
7578

7679
var schemaService = new OpenApiSchemaService("Test", Options.Create(new Microsoft.AspNetCore.Http.Json.JsonOptions()), builder.ServiceProvider, openApiOptions.Object);
7780
((TestServiceProvider)builder.ServiceProvider).TestSchemaService = schemaService;
78-
var documentService = new OpenApiDocumentService("Test", apiDescriptionGroupCollectionProvider, hostEnvironment, openApiOptions.Object, builder.ServiceProvider);
81+
var documentService = new OpenApiDocumentService("Test", apiDescriptionGroupCollectionProvider, hostEnvironment, openApiOptions.Object, builder.ServiceProvider, new OpenApiTestServer());
7982
((TestServiceProvider)builder.ServiceProvider).TestDocumentService = documentService;
8083

8184
return documentService;
@@ -101,7 +104,7 @@ internal static OpenApiDocumentService CreateDocumentService(IEndpointRouteBuild
101104

102105
var schemaService = new OpenApiSchemaService("Test", Options.Create(new Microsoft.AspNetCore.Http.Json.JsonOptions()), builder.ServiceProvider, options.Object);
103106
((TestServiceProvider)builder.ServiceProvider).TestSchemaService = schemaService;
104-
var documentService = new OpenApiDocumentService("Test", apiDescriptionGroupCollectionProvider, hostEnvironment, options.Object, builder.ServiceProvider);
107+
var documentService = new OpenApiDocumentService("Test", apiDescriptionGroupCollectionProvider, hostEnvironment, options.Object, builder.ServiceProvider, new OpenApiTestServer());
105108
((TestServiceProvider)builder.ServiceProvider).TestDocumentService = documentService;
106109

107110
return documentService;
@@ -276,4 +279,31 @@ public object GetService(Type serviceType)
276279
return _serviceProvider.GetService(serviceType);
277280
}
278281
}
282+
283+
internal class OpenApiTestServer(string[] addresses = null) : IServer
284+
{
285+
public IFeatureCollection Features => GenerateFeatures();
286+
287+
public void Dispose()
288+
{
289+
return;
290+
}
291+
292+
internal virtual IFeatureCollection GenerateFeatures()
293+
{
294+
var features = new FeatureCollection();
295+
features.Set<IServerAddressesFeature>(new TestServerAddressesFeature { Addresses = addresses });
296+
return features;
297+
}
298+
299+
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull => Task.CompletedTask;
300+
301+
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
302+
}
303+
304+
private class TestServerAddressesFeature : IServerAddressesFeature
305+
{
306+
public ICollection<string> Addresses { get; set; }
307+
public bool PreferHostingUrls { get; set; }
308+
}
279309
}

0 commit comments

Comments
 (0)