diff --git a/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs b/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs index 5a5d82e1ff6c..0c61b043cad5 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs @@ -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(); 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))); } } } @@ -463,6 +467,19 @@ internal static MediaTypeCollection GetDeclaredContentTypes(IReadOnlyList().LastOrDefault(); + return endpointGroupName?.EndpointGroupName ?? extensionData.GroupName; + } + private class ApiParameterDescriptionContext { public ModelMetadata ModelMetadata { get; } diff --git a/src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs b/src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs index c3492411df05..6c8201e2bc81 100644 --- a/src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs +++ b/src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs @@ -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() { 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() { new EndpointGroupNameAttribute("Customers") }; + action.GetProperty().GroupName = "NotUsedCustomers"; + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal("Customers", description.GroupName); + } + [Fact] public void GetApiDescription_HttpMethodIsNullWithoutConstraint() { diff --git a/src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs b/src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs index d6305c36acfe..33d102f4b842 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs @@ -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>(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>(body); + + // Assert + var description = Assert.Single(result); + Assert.Equal("GroupNameOnAction", description.GroupName); + } + [Fact] public async Task ApiExplorer_RouteTemplate_DisplaysFixedRoute() { diff --git a/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs index a7bc423c4689..6c999d1a271d 100644 --- a/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs +++ b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs @@ -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) { }