diff --git a/src/OpenApi/src/OpenApiGenerator.cs b/src/OpenApi/src/OpenApiGenerator.cs index 2720e226c2ba..86e7e46720ac 100644 --- a/src/OpenApi/src/OpenApiGenerator.cs +++ b/src/OpenApi/src/OpenApiGenerator.cs @@ -259,7 +259,7 @@ private static void GenerateDefaultResponses(Dictionary GetOpenApiParameters(MethodInfo methodInfo, Route throw new InvalidOperationException($"Encountered a parameter of type '{parameter.ParameterType}' without a name. Parameters must have a name."); } - var (_, parameterLocation) = GetOpenApiParameterLocation(parameter, pattern, disableInferredBody); + var (_, parameterLocation, attributeName) = GetOpenApiParameterLocation(parameter, pattern, disableInferredBody); // if the parameter doesn't have a valid location // then we should ignore it @@ -379,7 +379,7 @@ private List GetOpenApiParameters(MethodInfo methodInfo, Route var nullabilityContext = new NullabilityInfoContext(); var nullability = nullabilityContext.Create(parameter); var isOptional = parameter.HasDefaultValue || nullability.ReadState != NullabilityState.NotNull; - var name = pattern.GetParameter(parameter.Name) is { } routeParameter ? routeParameter.Name : parameter.Name; + var name = attributeName ?? (pattern.GetParameter(parameter.Name) is { } routeParameter ? routeParameter.Name : parameter.Name); var openApiParameter = new OpenApiParameter() { Name = name, @@ -393,29 +393,29 @@ private List GetOpenApiParameters(MethodInfo methodInfo, Route return openApiParameters; } - private (bool isBodyOrForm, ParameterLocation? locatedIn) GetOpenApiParameterLocation(ParameterInfo parameter, RoutePattern pattern, bool disableInferredBody) + private (bool isBodyOrForm, ParameterLocation? locatedIn, string? name) GetOpenApiParameterLocation(ParameterInfo parameter, RoutePattern pattern, bool disableInferredBody) { var attributes = parameter.GetCustomAttributes(); if (attributes.OfType().FirstOrDefault() is { } routeAttribute) { - return (false, ParameterLocation.Path); + return (false, ParameterLocation.Path, routeAttribute.Name); } else if (attributes.OfType().FirstOrDefault() is { } queryAttribute) { - return (false, ParameterLocation.Query); + return (false, ParameterLocation.Query, queryAttribute.Name); } else if (attributes.OfType().FirstOrDefault() is { } headerAttribute) { - return (false, ParameterLocation.Header); + return (false, ParameterLocation.Header, headerAttribute.Name); } else if (attributes.OfType().FirstOrDefault() is { } fromBodyAttribute) { - return (true, null); + return (true, null, null); } else if (attributes.OfType().FirstOrDefault() is { } fromFormAttribute) { - return (true, null); + return (true, null, null); } else if (parameter.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType)) || parameter.ParameterType == typeof(HttpContext) || @@ -426,7 +426,7 @@ private List GetOpenApiParameters(MethodInfo methodInfo, Route ParameterBindingMethodCache.HasBindAsyncMethod(parameter) || _serviceProviderIsService?.IsService(parameter.ParameterType) == true) { - return (false, null); + return (false, null, null); } else if (parameter.ParameterType == typeof(string) || ParameterBindingMethodCache.HasTryParseMethod(parameter.ParameterType)) { @@ -436,27 +436,27 @@ private List GetOpenApiParameters(MethodInfo methodInfo, Route // Path vs query cannot be determined by RequestDelegateFactory at startup currently because of the layering, but can be done here. if (parameter.Name is { } name && pattern.GetParameter(name) is not null) { - return (false, ParameterLocation.Path); + return (false, ParameterLocation.Path, null); } else { - return (false, ParameterLocation.Query); + return (false, ParameterLocation.Query, null); } } else if (parameter.ParameterType == typeof(IFormFile) || parameter.ParameterType == typeof(IFormFileCollection)) { - return (true, null); + return (true, null, null); } else if (disableInferredBody && ( (parameter.ParameterType.IsArray && ParameterBindingMethodCache.HasTryParseMethod(parameter.ParameterType.GetElementType()!)) || parameter.ParameterType == typeof(string[]) || parameter.ParameterType == typeof(StringValues))) { - return (false, ParameterLocation.Query); + return (false, ParameterLocation.Query, null); } else { - return (true, null); + return (true, null, null); } } } diff --git a/src/OpenApi/test/OpenApiGeneratorTests.cs b/src/OpenApi/test/OpenApiGeneratorTests.cs index 45108b14f5c0..779a036b08d0 100644 --- a/src/OpenApi/test/OpenApiGeneratorTests.cs +++ b/src/OpenApi/test/OpenApiGeneratorTests.cs @@ -913,6 +913,21 @@ public void HandlesEndpointWithNoRequestBody() Assert.Null(operationWithNoBodyParams.RequestBody); } + [Fact] + public void HandlesParameterWithNameInAttribute() + { + static void ValidateParameter(OpenApiOperation operation, string expectedName) + { + var parameter = Assert.Single(operation.Parameters); + Assert.Equal(expectedName, parameter.Name); + } + + ValidateParameter(GetOpenApiOperation(([FromRoute(Name = "routeName")] string param) => ""), "routeName"); + ValidateParameter(GetOpenApiOperation(([FromRoute(Name = "routeName")] string param) => "", "/{param}"), "routeName"); + ValidateParameter(GetOpenApiOperation(([FromQuery(Name = "queryName")] string param) => ""), "queryName"); + ValidateParameter(GetOpenApiOperation(([FromHeader(Name = "headerName")] string param) => ""), "headerName"); + } + private static OpenApiOperation GetOpenApiOperation( Delegate action, string pattern = null,