Skip to content

Support EndpointGroupName metadata in MVC ApiExplorer #37264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,17 @@ public void OnProvidersExecuting(ApiDescriptionProviderContext context)
continue;
}

// ApiDescriptionActionData is only added to the ControllerActionDescriptor if
// the action is marked as `IsVisible` to the ApiExplorer. This null-check is
// effectively asserting if the endpoint should be generated into the final
// OpenAPI metadata.
var extensionData = action.GetProperty<ApiDescriptionActionData>();
if (extensionData != null)
{
var httpMethods = GetHttpMethods(action);
foreach (var httpMethod in httpMethods)
{
context.Results.Add(CreateApiDescription(action, httpMethod, extensionData.GroupName));
context.Results.Add(CreateApiDescription(action, httpMethod, GetGroupName(action, extensionData)));
}
}
}
Expand Down Expand Up @@ -463,6 +467,19 @@ internal static MediaTypeCollection GetDeclaredContentTypes(IReadOnlyList<IApiRe
.ToArray();
}

private static string? GetGroupName(ControllerActionDescriptor action, ApiDescriptionActionData extensionData)
{
// The `GroupName` set in the `ApiDescriptionActionData` is either the
// group name set via [ApiExplorerSettings(GroupName = "foo")] on the
// action or controller. So, this lookup favors the following sequence:
// - EndpointGroupName on the action, if it is set
// - EndpointGroupName on the controller, if it is set
// - ApiExplorerSettings.GroupName on the action, if it is set
// - ApiExplorerSettings.GroupName on the controller, if it is set
var endpointGroupName = action.EndpointMetadata.OfType<IEndpointGroupNameMetadata>().LastOrDefault();
return endpointGroupName?.EndpointGroupName ?? extensionData.GroupName;
}

private class ApiParameterDescriptionContext
{
public ModelMetadata ModelMetadata { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,37 @@ public void GetApiDescription_PopulatesGroupName()
Assert.Equal("Customers", description.GroupName);
}

[Fact]
public void GetApiDescription_PopulatesGroupName_FromMetadata()
{
// Arrange
var action = CreateActionDescriptor();
action.EndpointMetadata = new List<object>() { new EndpointGroupNameAttribute("Customers") };

// Act
var descriptions = GetApiDescriptions(action);

// Assert
var description = Assert.Single(descriptions);
Assert.Equal("Customers", description.GroupName);
}

[Fact]
public void GetApiDescription_PopulatesGroupName_FromMetadataOrExtensionData()
{
// Arrange
var action = CreateActionDescriptor();
action.EndpointMetadata = new List<object>() { new EndpointGroupNameAttribute("Customers") };
action.GetProperty<ApiDescriptionActionData>().GroupName = "NotUsedCustomers";

// Act
var descriptions = GetApiDescriptions(action);

// Assert
var description = Assert.Single(descriptions);
Assert.Equal("Customers", description.GroupName);
}

[Fact]
public void GetApiDescription_HttpMethodIsNullWithoutConstraint()
{
Expand Down
28 changes: 28 additions & 0 deletions src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,34 @@ public async Task ApiExplorer_GroupName_SetByAttributeOnAction()
Assert.Equal("SetOnAction", description.GroupName);
}

[Fact]
public async Task ApiExplorer_GroupName_SetByEndpointMetadataOnController()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/ApiExplorerApiController/ActionWithIdParameter");

var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);

// Assert
var description = Assert.Single(result);
Assert.Equal("GroupNameOnController", description.GroupName);
}

[Fact]
public async Task ApiExplorer_GroupName_SetByEndpointMetadataOnAction()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/ApiExplorerApiController/ActionWithSomeParameters");

var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);

// Assert
var description = Assert.Single(result);
Assert.Equal("GroupNameOnAction", description.GroupName);
}

[Fact]
public async Task ApiExplorer_RouteTemplate_DisplaysFixedRoute()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;

namespace ApiExplorerWebSite
{
[Route("ApiExplorerApiController/[action]")]
[ApiController]
[EndpointGroupName("GroupNameOnController")]
public class ApiExplorerApiController : Controller
{
public IActionResult ActionWithoutParameters() => Ok();

[EndpointGroupName("GroupNameOnAction")]
public void ActionWithSomeParameters(object input)
{
}
Expand Down