Attributes on parameters of primary constructors should be reflected in generated OpenAPI documents
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 290
Description
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**
```csharp
// 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();
```
1. Build & run with the .NET SDK
2. Navigate to https://localhost:/openapi/v1.json.
3. 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.
Contributor guide
Assessment
This issue has not been assessed yet.