Skip to content

Commit 6e3e6fb

Browse files
committed
Fix paths without binding in Minimal Api's not discovered from the api explorer
Paths without a binding would be omitted by the EndpointMetadataApiDescriptionProvider. Some tests around OpenApi required fixing as arrays are not supported in paths. Fixes #63883
1 parent 344dc33 commit 6e3e6fb

File tree

4 files changed

+801
-17
lines changed

4 files changed

+801
-17
lines changed

src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ private ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, string
114114

115115
var hasBodyOrFormFileParameter = false;
116116
var parameters = routeEndpoint.Metadata.GetOrderedMetadata<IParameterBindingMetadata>();
117+
var remainingRouteParameters = routeEndpoint.RoutePattern.Parameters.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
117118

118119
foreach (var parameter in parameters)
119120
{
@@ -122,13 +123,35 @@ private ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, string
122123
if (parameterDescription is { })
123124
{
124125
apiDescription.ParameterDescriptions.Add(parameterDescription);
126+
if (parameterDescription.Source == BindingSource.Path)
127+
{
128+
remainingRouteParameters.Remove(parameterDescription.Name);
129+
}
125130

126131
hasBodyOrFormFileParameter |=
127132
parameterDescription.Source == BindingSource.Body ||
128133
parameterDescription.Source == BindingSource.FormFile;
129134
}
130135
}
131136

137+
// Add any remaining route parameters that weren't associated with a parameter in the delegate.
138+
foreach (var remainingRouteParameter in remainingRouteParameters.Values)
139+
{
140+
var parameterDescription = new ApiParameterDescription
141+
{
142+
Name = remainingRouteParameter.Name,
143+
Source = BindingSource.Path,
144+
IsRequired = true,
145+
RouteInfo = new ApiParameterRouteInfo
146+
{
147+
IsOptional = false,
148+
Constraints = Array.Empty<IRouteConstraint>(),
149+
DefaultValue = null,
150+
},
151+
};
152+
apiDescription.ParameterDescriptions.Add(parameterDescription);
153+
}
154+
132155
// Get IAcceptsMetadata.
133156
var acceptsMetadata = routeEndpoint.Metadata.GetMetadata<IAcceptsMetadata>();
134157
if (acceptsMetadata is not null)

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.MinimalApis.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,4 +530,45 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document =>
530530
Assert.Equal("Property with only value documentation.", valueOnlyParam2.Description);
531531
});
532532
}
533+
534+
[Fact]
535+
public async Task SupportsRouteParametersFromMinimalApis()
536+
{
537+
var source = """
538+
using System;
539+
using System.Threading.Tasks;
540+
using System.Collections.Generic;
541+
using Microsoft.AspNetCore.Builder;
542+
using Microsoft.Extensions.DependencyInjection;
543+
using Microsoft.AspNetCore.Http;
544+
545+
var builder = WebApplication.CreateBuilder();
546+
547+
builder.Services.AddOpenApi();
548+
549+
var app = builder.Build();
550+
551+
app.MapGet("/{userId}", RouteHandlerExtensionMethods.Get);
552+
553+
app.Run();
554+
555+
public static class RouteHandlerExtensionMethods
556+
{
557+
/// <param name="userId">The id of the user.</param>
558+
public static string Get()
559+
{
560+
return "Hello, World!";
561+
}
562+
}
563+
""";
564+
565+
var generator = new XmlCommentGenerator();
566+
await SnapshotTestHelper.Verify(source, generator, out var compilation);
567+
await SnapshotTestHelper.VerifyOpenApi(compilation, document =>
568+
{
569+
var path = document.Paths["/{userId}"].Operations[HttpMethod.Get];
570+
Assert.NotEmpty(path.Parameters);
571+
Assert.Equal("The id of the user.", path.Parameters[0].Description);
572+
});
573+
}
533574
}

0 commit comments

Comments
 (0)