Skip to content

Commit 09d7789

Browse files
authored
Change OpenAPI document name resolution to be case-insensitive (#59199)
* Fix analyser error * Wrote unit test to verify when bug is fixed * Register document names in a case-insensitive manner * Change OpenAPI document name resolution to be case-insensitive * Restore AddOpenApiCore to its own method * Fix typo in extension method
1 parent bc2efb7 commit 09d7789

File tree

3 files changed

+45
-9
lines changed

3 files changed

+45
-9
lines changed

Diff for: src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,22 @@ public static IEndpointConventionBuilder MapOpenApi(this IEndpointRouteBuilder e
3030
var options = endpoints.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenApiOptions>>();
3131
return endpoints.MapGet(pattern, async (HttpContext context, string documentName = OpenApiConstants.DefaultDocumentName) =>
3232
{
33+
// We need to retrieve the document name in a case-insensitive manner
34+
// to support case-insensitive document name resolution.
35+
// Keyed Services are case-sensitive by default, which doesn't work well for document names in ASP.NET Core
36+
// as routing in ASP.NET Core is case-insensitive by default.
37+
var lowercasedDocumentName = documentName.ToLowerInvariant();
38+
3339
// It would be ideal to use the `HttpResponseStreamWriter` to
3440
// asynchronously write to the response stream here but Microsoft.OpenApi
3541
// does not yet support async APIs on their writers.
3642
// See https://github.com/microsoft/OpenAPI.NET/issues/421 for more info.
37-
var documentService = context.RequestServices.GetKeyedService<OpenApiDocumentService>(documentName);
43+
var documentService = context.RequestServices.GetKeyedService<OpenApiDocumentService>(lowercasedDocumentName);
3844
if (documentService is null)
3945
{
4046
context.Response.StatusCode = StatusCodes.Status404NotFound;
4147
context.Response.ContentType = "text/plain;charset=utf-8";
42-
await context.Response.WriteAsync($"No OpenAPI document with the name '{documentName}' was found.");
48+
await context.Response.WriteAsync($"No OpenAPI document with the name '{lowercasedDocumentName}' was found.");
4349
}
4450
else
4551
{

Diff for: src/OpenApi/src/Extensions/OpenApiServiceCollectionExtensions.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,16 @@ public static IServiceCollection AddOpenApi(this IServiceCollection services, st
5757
ArgumentNullException.ThrowIfNull(services);
5858
ArgumentNullException.ThrowIfNull(configureOptions);
5959

60-
services.AddOpenApiCore(documentName);
61-
services.Configure<OpenApiOptions>(documentName, options =>
60+
// We need to store the document name in a case-insensitive manner
61+
// to support case-insensitive document name resolution.
62+
// Keyed Services are case-sensitive by default, which doesn't work well for document names in ASP.NET Core
63+
// as routing in ASP.NET Core is case-insensitive by default.
64+
var lowercasedDocumentName = documentName.ToLowerInvariant();
65+
66+
services.AddOpenApiCore(lowercasedDocumentName);
67+
services.Configure<OpenApiOptions>(lowercasedDocumentName, options =>
6268
{
63-
options.DocumentName = documentName;
69+
options.DocumentName = lowercasedDocumentName;
6470
configureOptions(options);
6571
});
6672
return services;

Diff for: src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiEndpointRouteBuilderExtensionsTests.cs

+28-4
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public async Task MapOpenApi_ReturnsRenderedDocument()
5959
context.Response.Body = responseBodyStream;
6060
context.RequestServices = serviceProvider;
6161
context.Request.RouteValues.Add("documentName", "v1");
62-
var endpoint = builder.DataSources.First().Endpoints.First();
62+
var endpoint = builder.DataSources.First().Endpoints[0];
6363

6464
// Act
6565
var requestDelegate = endpoint.RequestDelegate;
@@ -89,7 +89,7 @@ public async Task MapOpenApi_ReturnsDefaultDocumentIfNoNameProvided(string expec
8989
var responseBodyStream = new MemoryStream();
9090
context.Response.Body = responseBodyStream;
9191
context.RequestServices = serviceProvider;
92-
var endpoint = builder.DataSources.First().Endpoints.First();
92+
var endpoint = builder.DataSources.First().Endpoints[0];
9393

9494
// Act
9595
var requestDelegate = endpoint.RequestDelegate;
@@ -121,7 +121,7 @@ public async Task MapOpenApi_Returns404ForUnresolvedDocument()
121121
context.Response.Body = responseBodyStream;
122122
context.RequestServices = serviceProvider;
123123
context.Request.RouteValues.Add("documentName", "v2");
124-
var endpoint = builder.DataSources.First().Endpoints.First();
124+
var endpoint = builder.DataSources.First().Endpoints[0];
125125

126126
// Act
127127
var requestDelegate = endpoint.RequestDelegate;
@@ -132,6 +132,30 @@ public async Task MapOpenApi_Returns404ForUnresolvedDocument()
132132
Assert.Equal("No OpenAPI document with the name 'v2' was found.", Encoding.UTF8.GetString(responseBodyStream.ToArray()));
133133
}
134134

135+
[Theory]
136+
[InlineData("CaseSensitive", "casesensitive")]
137+
[InlineData("casesensitive", "CaseSensitive")]
138+
public async Task MapOpenApi_ReturnsDocumentWhenPathIsCaseSensitive(string registeredDocumentName, string requestedDocumentName)
139+
{
140+
// Arrange
141+
var serviceProvider = CreateServiceProvider(registeredDocumentName);
142+
var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider));
143+
builder.MapOpenApi("/openapi/{documentName}.json");
144+
var context = new DefaultHttpContext();
145+
var responseBodyStream = new MemoryStream();
146+
context.Response.Body = responseBodyStream;
147+
context.RequestServices = serviceProvider;
148+
context.Request.RouteValues.Add("documentName", requestedDocumentName);
149+
var endpoint = builder.DataSources.First().Endpoints[0];
150+
151+
// Act
152+
var requestDelegate = endpoint.RequestDelegate;
153+
await requestDelegate(context);
154+
155+
// Assert
156+
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
157+
}
158+
135159
[Theory]
136160
[InlineData("/openapi.json", "application/json;charset=utf-8", false)]
137161
[InlineData("/openapi.yaml", "text/plain+yaml;charset=utf-8", true)]
@@ -150,7 +174,7 @@ public async Task MapOpenApi_ReturnsDocumentIfNameProvidedInQuery(string expecte
150174
context.Response.Body = responseBodyStream;
151175
context.RequestServices = serviceProvider;
152176
context.Request.QueryString = new QueryString($"?documentName={documentName}");
153-
var endpoint = builder.DataSources.First().Endpoints.First();
177+
var endpoint = builder.DataSources.First().Endpoints[0];
154178

155179
// Act
156180
var requestDelegate = endpoint.RequestDelegate;

0 commit comments

Comments
 (0)