dotnet/aspnetcore
在 GitHub 查看Attributes on parameters of primary constructors should be reflected in generated OpenAPI documents
Open
#61,538 创建于 2025年4月17日
area-minimalfeature-openapihelp wanted
描述
Currently OpenAPI document generation does not extract metadata from attributes on parameters of a class/record/struct primary constructor. However, the new validation feature for minimal APIs will perform these validations, so we should update OpenAPI generation to incorporate these into the generated documents.
Minima Repro Steps
// Program.cs
using Microsoft.AspNetCore.OpenApi;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi(); // generates /openapi/v1.json
var app = builder.Build();
// Simple DTO that uses a primary constructor (C# 12 feature)
public class UserDto(string name, [property: System.ComponentModel.DataAnnotations.Range(0,120)] int age);
app.MapPost("/users", (UserDto dto) => Results.Ok(dto))
.WithName("CreateUser") // any attribute placed here is detected
.WithOpenApi(); // emit into default document
app.MapOpenApi(); // GET /openapi/v1.json
app.Run();
- Build & run with the .NET SDK
- Navigate to https://localhost:/openapi/v1.json.
- Observe the schema for UserDto inside the request‐body definition.
Expected Result
- The age property appears with the minimum/maximum constraints (because of [Range]).
- Any other validation / binding attributes placed on the primary-constructor parameter surface in the OpenAPI document, just as they do when the attribute is placed on a manually-declared property or on a record positional parameter.
Actual Result
- The age property is emitted without the minimum/maximum keywords – the attribute is silently ignored.
- The same attribute is picked up if we:
- Convert the type to a record (public record UserDto(...)) or
- Retain class but move [Range] onto an explicitly declared property.
Implementation Notes
- OpenApiSchemaGenerator.GenerateForType (src/OpenApi/src/OpenApiSchemaGenerator.cs) ultimately walks PropertyInfos returned from Type.GetProperties() and merges relevant Attributes into the schema.
- A C# 12 primary constructor on a class does not synthesize properties; its parameters are only ParameterInfos (unlike positional record parameters, which do become properties). The generator therefore never sees those attributes.
- There is currently no fallback that inspects ConstructorInfo.GetParameters() (or Roslyn metadata) to find attributes that belong to primary-constructor parameters and map them to logical schema members.