dotnet / dotnet/aspnetcore

Attributes on parameters of primary constructors should be reflected in generated OpenAPI documents

Open
#61,538 5 comments 1 reaction 1 assignee Assigned to @Copilot View on GitHub
area-minimal feature-openapi help wanted
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.