diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs index 642b1251d..f5acb0ea7 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs @@ -3,6 +3,7 @@ using System.Linq; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.OpenApi.Models; +using Microsoft.AspNetCore.Routing; namespace Swashbuckle.AspNetCore.SwaggerGen { @@ -61,7 +62,19 @@ private bool DefaultDocInclusionPredicate(string documentName, ApiDescription ap private string DefaultOperationIdSelector(ApiDescription apiDescription) { - return apiDescription.ActionDescriptor.AttributeRouteInfo?.Name; + var actionDescriptor = apiDescription.ActionDescriptor; + + // Resolve the operation ID from the route name and fallback to the + // endpoint name if no route name is available. This allows us to + // generate operation IDs for endpoints that are defined using + // minimal APIs. +#if (!NETSTANDARD2_0) + return + actionDescriptor.AttributeRouteInfo?.Name + ?? (actionDescriptor.EndpointMetadata.FirstOrDefault(m => m is IEndpointNameMetadata) as IEndpointNameMetadata)?.EndpointName; +#else + return actionDescriptor.AttributeRouteInfo?.Name; +#endif } private IList DefaultTagsSelector(ApiDescription apiDescription) diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs index e5a10bd32..c64f41c89 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs @@ -1,9 +1,12 @@ using System.Linq; using System.Collections.Generic; +using System.Reflection; using System.Text.Json; using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.OpenApi.Models; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Routing; using Microsoft.OpenApi.Any; using Xunit; using Swashbuckle.AspNetCore.Swagger; @@ -77,6 +80,31 @@ public void GetSwagger_SetsOperationIdToRouteName_IfActionHasRouteNameMetadata() Assert.Equal("SomeRouteName", document.Paths["/resource"].Operations[OperationType.Post].OperationId); } + [Fact] + public void GetSwagger_SetsOperationIdToEndpointName_IfActionHasEndpointNameMetadata() + { + var methodInfo = typeof(FakeController).GetMethod(nameof(FakeController.ActionWithParameter)); + var actionDescriptor = new ActionDescriptor + { + EndpointMetadata = new List() { new EndpointNameMetadata("SomeEndpointName") }, + RouteValues = new Dictionary + { + ["controller"] = methodInfo.DeclaringType.Name.Replace("Controller", string.Empty) + } + }; + var subject = Subject( + apiDescriptions: new[] + { + ApiDescriptionFactory.Create(actionDescriptor, methodInfo, groupName: "v1", httpMethod: "POST", relativePath: "resource"), + } + ); + + var document = subject.GetSwagger("v1"); + + Assert.Equal("SomeEndpointName", document.Paths["/resource"].Operations[OperationType.Post].OperationId); + } + + [Fact] public void GetSwagger_SetsDeprecated_IfActionHasObsoleteAttribute() { diff --git a/test/Swashbuckle.AspNetCore.TestSupport/ApiExplorer/ApiDescriptionFactory.cs b/test/Swashbuckle.AspNetCore.TestSupport/ApiExplorer/ApiDescriptionFactory.cs index 320eb000a..c385c9e25 100644 --- a/test/Swashbuckle.AspNetCore.TestSupport/ApiExplorer/ApiDescriptionFactory.cs +++ b/test/Swashbuckle.AspNetCore.TestSupport/ApiExplorer/ApiDescriptionFactory.cs @@ -12,6 +12,7 @@ namespace Swashbuckle.AspNetCore.TestSupport public static class ApiDescriptionFactory { public static ApiDescription Create( + ActionDescriptor actionDescriptor, MethodInfo methodInfo, string groupName = "v1", string httpMethod = "POST", @@ -20,8 +21,6 @@ public static class ApiDescriptionFactory IEnumerable supportedRequestFormats = null, IEnumerable supportedResponseTypes = null) { - var actionDescriptor = CreateActionDescriptor(methodInfo); - var apiDescription = new ApiDescription { ActionDescriptor = actionDescriptor, @@ -74,6 +73,30 @@ public static class ApiDescriptionFactory return apiDescription; } + public static ApiDescription Create( + MethodInfo methodInfo, + string groupName = "v1", + string httpMethod = "POST", + string relativePath = "resoure", + IEnumerable parameterDescriptions = null, + IEnumerable supportedRequestFormats = null, + IEnumerable supportedResponseTypes = null) + { + + var actionDescriptor = CreateActionDescriptor(methodInfo); + + return Create( + actionDescriptor, + methodInfo, + groupName, + httpMethod, + relativePath, + parameterDescriptions, + supportedRequestFormats, + supportedResponseTypes + ); + } + public static ApiDescription Create( Func actionNameSelector, string groupName = "v1",