Skip to content

Commit 3ade4fb

Browse files
[OpenAPI] Get description with [FromQuery]
Get the description from the associated object's property when `[FromQuery]` is applied to a property of an object used as a `[FromQuery]` parameter. Resolves #61297.
1 parent 02125bd commit 3ade4fb

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

src/OpenApi/src/Services/OpenApiDocumentService.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -494,11 +494,22 @@ private static bool IsRequired(ApiParameterDescription parameter)
494494
}
495495

496496
// Apply [Description] attributes on the parameter to the top-level OpenApiParameter object and not the schema.
497-
private static string? GetParameterDescriptionFromAttribute(ApiParameterDescription parameter) =>
498-
parameter.ParameterDescriptor is IParameterInfoParameterDescriptor { ParameterInfo: { } parameterInfo } &&
499-
parameterInfo.GetCustomAttributes().OfType<DescriptionAttribute>().LastOrDefault() is { } descriptionAttribute ?
500-
descriptionAttribute.Description :
501-
null;
497+
private static string? GetParameterDescriptionFromAttribute(ApiParameterDescription parameter)
498+
{
499+
if (parameter.ParameterDescriptor is IParameterInfoParameterDescriptor { ParameterInfo: { } parameterInfo } &&
500+
parameterInfo.GetCustomAttributes<DescriptionAttribute>().LastOrDefault() is { } parameterDescription)
501+
{
502+
return parameterDescription.Description;
503+
}
504+
505+
if (parameter.ModelMetadata is Mvc.ModelBinding.Metadata.DefaultModelMetadata { Attributes.PropertyAttributes.Count: > 0 } metadata &&
506+
metadata.Attributes.PropertyAttributes.OfType<DescriptionAttribute>().LastOrDefault() is { } propertyDescription)
507+
{
508+
return propertyDescription.Description;
509+
}
510+
511+
return null;
512+
}
502513

503514
private async Task<OpenApiRequestBody?> GetRequestBodyAsync(OpenApiDocument document, ApiDescription description, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
504515
{

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ public ControllerActionDescriptor CreateActionDescriptor(string methodName = nul
229229

230230
action.AttributeRouteInfo = new()
231231
{
232-
Template = action.MethodInfo.GetCustomAttribute<RouteAttribute>()?.Template,
232+
Template = action.MethodInfo.GetCustomAttribute<RouteAttribute>()?.Template ?? string.Empty,
233233
Name = action.MethodInfo.GetCustomAttribute<RouteAttribute>()?.Name,
234234
Order = action.MethodInfo.GetCustomAttribute<RouteAttribute>()?.Order ?? 0,
235235
};

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,39 @@ await VerifyOpenApiDocument(builder, document =>
509509
});
510510
}
511511

512+
[Fact]
513+
public async Task GetOpenApiParameters_HandlesAsParametersParametersWithDescriptionAttribute()
514+
{
515+
// Arrange
516+
var builder = CreateBuilder();
517+
518+
// Act
519+
builder.MapGet("/api", ([AsParameters] FromQueryModel model) => { });
520+
521+
// Assert
522+
await VerifyOpenApiDocument(builder, document =>
523+
{
524+
var operation = document.Paths["/api"].Operations[HttpMethod.Get];
525+
var parameter = Assert.Single(operation.Parameters);
526+
Assert.Equal("The ID of the entity", parameter.Description);
527+
});
528+
}
529+
530+
[Fact]
531+
public async Task GetOpenApiParameters_HandlesFromQueryParametersWithDescriptionAttribute()
532+
{
533+
// Arrange
534+
var actionDescriptor = CreateActionDescriptor(nameof(TestFromQueryController.GetWithFromQueryDto), typeof(TestFromQueryController));
535+
536+
// Assert
537+
await VerifyOpenApiDocument(actionDescriptor, document =>
538+
{
539+
var operation = document.Paths["/"].Operations[HttpMethod.Get];
540+
var parameter = Assert.Single(operation.Parameters);
541+
Assert.Equal("The ID of the entity", parameter.Description);
542+
});
543+
}
544+
512545
[Route("/api/{id}/{date}")]
513546
private void AcceptsParametersInModel(RouteParamsContainer model) { }
514547

@@ -809,4 +842,23 @@ public override void Write(Utf8JsonWriter writer, EnumArrayType value, JsonSeria
809842
writer.WriteEndObject();
810843
}
811844
}
845+
846+
[ApiController]
847+
[Route("[controller]/[action]")]
848+
private class TestFromQueryController : ControllerBase
849+
{
850+
[HttpGet]
851+
public Task<IActionResult> GetWithFromQueryDto([FromQuery] FromQueryModel query)
852+
{
853+
return Task.FromResult<IActionResult>(Ok());
854+
}
855+
}
856+
857+
[Description("A query model.")]
858+
private record FromQueryModel
859+
{
860+
[Description("The ID of the entity")]
861+
[FromQuery(Name = "id")]
862+
public int Id { get; set; }
863+
}
812864
}

0 commit comments

Comments
 (0)