Skip to content

Commit fa946a8

Browse files
Support EndpointGroupName metadata in MVC ApiExplorer (#37264)
* Support EndpointGroupName metadata in MVC ApiExplorer * Address feedback from peer review * Update src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs Co-authored-by: Pranav K <[email protected]> Co-authored-by: Pranav K <[email protected]>
1 parent 0cff8a7 commit fa946a8

File tree

4 files changed

+80
-1
lines changed

4 files changed

+80
-1
lines changed

src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs

+18-1
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,17 @@ public void OnProvidersExecuting(ApiDescriptionProviderContext context)
7171
continue;
7272
}
7373

74+
// ApiDescriptionActionData is only added to the ControllerActionDescriptor if
75+
// the action is marked as `IsVisible` to the ApiExplorer. This null-check is
76+
// effectively asserting if the endpoint should be generated into the final
77+
// OpenAPI metadata.
7478
var extensionData = action.GetProperty<ApiDescriptionActionData>();
7579
if (extensionData != null)
7680
{
7781
var httpMethods = GetHttpMethods(action);
7882
foreach (var httpMethod in httpMethods)
7983
{
80-
context.Results.Add(CreateApiDescription(action, httpMethod, extensionData.GroupName));
84+
context.Results.Add(CreateApiDescription(action, httpMethod, GetGroupName(action, extensionData)));
8185
}
8286
}
8387
}
@@ -463,6 +467,19 @@ internal static MediaTypeCollection GetDeclaredContentTypes(IReadOnlyList<IApiRe
463467
.ToArray();
464468
}
465469

470+
private static string? GetGroupName(ControllerActionDescriptor action, ApiDescriptionActionData extensionData)
471+
{
472+
// The `GroupName` set in the `ApiDescriptionActionData` is either the
473+
// group name set via [ApiExplorerSettings(GroupName = "foo")] on the
474+
// action or controller. So, this lookup favors the following sequence:
475+
// - EndpointGroupName on the action, if it is set
476+
// - EndpointGroupName on the controller, if it is set
477+
// - ApiExplorerSettings.GroupName on the action, if it is set
478+
// - ApiExplorerSettings.GroupName on the controller, if it is set
479+
var endpointGroupName = action.EndpointMetadata.OfType<IEndpointGroupNameMetadata>().LastOrDefault();
480+
return endpointGroupName?.EndpointGroupName ?? extensionData.GroupName;
481+
}
482+
466483
private class ApiParameterDescriptionContext
467484
{
468485
public ModelMetadata ModelMetadata { get; }

src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs

+31
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,37 @@ public void GetApiDescription_PopulatesGroupName()
8888
Assert.Equal("Customers", description.GroupName);
8989
}
9090

91+
[Fact]
92+
public void GetApiDescription_PopulatesGroupName_FromMetadata()
93+
{
94+
// Arrange
95+
var action = CreateActionDescriptor();
96+
action.EndpointMetadata = new List<object>() { new EndpointGroupNameAttribute("Customers") };
97+
98+
// Act
99+
var descriptions = GetApiDescriptions(action);
100+
101+
// Assert
102+
var description = Assert.Single(descriptions);
103+
Assert.Equal("Customers", description.GroupName);
104+
}
105+
106+
[Fact]
107+
public void GetApiDescription_PopulatesGroupName_FromMetadataOrExtensionData()
108+
{
109+
// Arrange
110+
var action = CreateActionDescriptor();
111+
action.EndpointMetadata = new List<object>() { new EndpointGroupNameAttribute("Customers") };
112+
action.GetProperty<ApiDescriptionActionData>().GroupName = "NotUsedCustomers";
113+
114+
// Act
115+
var descriptions = GetApiDescriptions(action);
116+
117+
// Assert
118+
var description = Assert.Single(descriptions);
119+
Assert.Equal("Customers", description.GroupName);
120+
}
121+
91122
[Fact]
92123
public void GetApiDescription_HttpMethodIsNullWithoutConstraint()
93124
{

src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs

+28
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,34 @@ public async Task ApiExplorer_GroupName_SetByAttributeOnAction()
120120
Assert.Equal("SetOnAction", description.GroupName);
121121
}
122122

123+
[Fact]
124+
public async Task ApiExplorer_GroupName_SetByEndpointMetadataOnController()
125+
{
126+
// Arrange & Act
127+
var response = await Client.GetAsync("http://localhost/ApiExplorerApiController/ActionWithIdParameter");
128+
129+
var body = await response.Content.ReadAsStringAsync();
130+
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
131+
132+
// Assert
133+
var description = Assert.Single(result);
134+
Assert.Equal("GroupNameOnController", description.GroupName);
135+
}
136+
137+
[Fact]
138+
public async Task ApiExplorer_GroupName_SetByEndpointMetadataOnAction()
139+
{
140+
// Arrange & Act
141+
var response = await Client.GetAsync("http://localhost/ApiExplorerApiController/ActionWithSomeParameters");
142+
143+
var body = await response.Content.ReadAsStringAsync();
144+
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
145+
146+
// Assert
147+
var description = Assert.Single(result);
148+
Assert.Equal("GroupNameOnAction", description.GroupName);
149+
}
150+
123151
[Fact]
124152
public async Task ApiExplorer_RouteTemplate_DisplaysFixedRoute()
125153
{

src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs

+3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
using System.IO;
55
using Microsoft.AspNetCore.Http;
66
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.AspNetCore.Routing;
78

89
namespace ApiExplorerWebSite
910
{
1011
[Route("ApiExplorerApiController/[action]")]
1112
[ApiController]
13+
[EndpointGroupName("GroupNameOnController")]
1214
public class ApiExplorerApiController : Controller
1315
{
1416
public IActionResult ActionWithoutParameters() => Ok();
1517

18+
[EndpointGroupName("GroupNameOnAction")]
1619
public void ActionWithSomeParameters(object input)
1720
{
1821
}

0 commit comments

Comments
 (0)