diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index a9d644e2a8..46fa05fb5a 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer private readonly MvcOptions _mvcOptions; private readonly IActionResultTypeMapper _mapper; private readonly ApiResponseTypeProvider _responseTypeProvider; + private readonly RouteOptions _routeOptions; private readonly IInlineConstraintResolver _constraintResolver; private readonly IModelMetadataProvider _modelMetadataProvider; @@ -53,6 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// constraints. /// The . /// The . + [Obsolete("This constructor is obsolete and will be removed in a future release.")] public DefaultApiDescriptionProvider( IOptions optionsAccessor, IInlineConstraintResolver constraintResolver, @@ -66,6 +68,30 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer _responseTypeProvider = new ApiResponseTypeProvider(modelMetadataProvider, mapper, _mvcOptions); } + /// + /// Creates a new instance of . + /// + /// The accessor for . + /// The used for resolving inline + /// constraints. + /// The . + /// The . + /// The accessor for . + public DefaultApiDescriptionProvider( + IOptions optionsAccessor, + IInlineConstraintResolver constraintResolver, + IModelMetadataProvider modelMetadataProvider, + IActionResultTypeMapper mapper, + IOptions routeOptions) + { + _mvcOptions = optionsAccessor.Value; + _constraintResolver = constraintResolver; + _modelMetadataProvider = modelMetadataProvider; + _mapper = mapper; + _responseTypeProvider = new ApiResponseTypeProvider(modelMetadataProvider, mapper, _mvcOptions); + _routeOptions = routeOptions.Value; + } + /// public int Order => -1000; @@ -383,7 +409,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { if (part.IsLiteral) { - currentSegment += part.Text; + currentSegment += _routeOptions.LowercaseUrls ? + part.Text.ToLowerInvariant() : + part.Text; } else if (part.IsParameter) { diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index 5caceeaa02..224a6b1276 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -387,6 +387,25 @@ namespace Microsoft.AspNetCore.Mvc.Description Assert.Single(description.ParameterDescriptions, p => p.Name == "id5"); } + [Fact] + public void GetApiDescription_ProducesLowerCaseRelativePaths() + { + // Arrange + var action = CreateActionDescriptor(); + action.AttributeRouteInfo = new AttributeRouteInfo + { + Template = "api/Products/UpdateProduct/{productId}" + }; + var routeOptions = new RouteOptions { LowercaseUrls = true }; + + // Act + var descriptions = GetApiDescriptions(action, routeOptions: routeOptions); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal("api/products/updateproduct/{productId}", description.RelativePath); + } + [Fact] public void GetApiDescription_PopulatesResponseType_WithProduct() { @@ -1797,7 +1816,8 @@ namespace Microsoft.AspNetCore.Mvc.Description ActionDescriptor action, List inputFormatters = null, List outputFormatters = null, - bool allowValidatingTopLevelNodes = true) + bool allowValidatingTopLevelNodes = true, + RouteOptions routeOptions = null) { var context = new ApiDescriptionProviderContext(new ActionDescriptor[] { action }); @@ -1827,7 +1847,8 @@ namespace Microsoft.AspNetCore.Mvc.Description optionsAccessor, constraintResolver.Object, modelMetadataProvider, - new ActionResultTypeMapper()); + new ActionResultTypeMapper(), + Options.Create(routeOptions ?? new RouteOptions())); provider.OnProvidersExecuting(context); provider.OnProvidersExecuted(context);