Skip to content

Surface primary constructor parameter attributes in OpenAPI schemas#65804

Open
matantsach wants to merge 3 commits intodotnet:mainfrom
matantsach:fix/61538-primary-ctor-openapi-attributes
Open

Surface primary constructor parameter attributes in OpenAPI schemas#65804
matantsach wants to merge 3 commits intodotnet:mainfrom
matantsach:fix/61538-primary-ctor-openapi-attributes

Conversation

@matantsach
Copy link
Copy Markdown

Summary

C# 12 primary constructor parameters on classes carry their validation attributes ([Range], [Required], [MinLength], etc.) only on the ParameterInfo — unlike records, the compiler doesn't copy them to the synthesized property. The OpenAPI schema generator reads attributes exclusively from JsonPropertyInfo.AttributeProvider (the PropertyInfo), so these constraints are silently dropped from the generated schema.

This adds a fallback that checks JsonPropertyInfo.AssociatedParameter.AttributeProvider for constructor parameter attributes in two places:

  1. TypeInfoResolver modifier — picks up [Required] from constructor parameters
  2. TransformSchemaNode callback — picks up ValidationAttribute, DefaultValueAttribute, and DescriptionAttribute from constructor parameters

Constructor parameter attributes are applied first; property-level attributes are applied second and take precedence. This means existing types with attributes on both (like records) continue to work correctly — the property-level copy wins, and idempotent re-application of the same values is harmless.

AOT considerations

AssociatedParameter.AttributeProvider follows the same reflection pattern already used by the existing propertyInfo.AttributeProvider path. Under source-generated serialization contexts, AssociatedParameter.AttributeProvider is null (same as AttributeProvider), so the new block is safely skipped. No new trimming or AOT risk surface is introduced.

Test plan

  • HandlesValidationAttributesOnPrimaryConstructorParameters[Range(0, 120)] and [MinLength(1), MaxLength(100)] on class primary constructor params produce correct schema constraints
  • HandlesRequiredAttributeOnPrimaryConstructorParameters[Required] on constructor param marks property as required, with negative assertion on non-required property
  • PropertyAttributeOverridesConstructorParameterAttribute — property-level [Range(10, 50)] correctly overrides constructor-level [Range(0, 100)]
  • HandlesDescriptionAttributeOnPrimaryConstructorParameters[Description] on constructor param appears in schema
  • RecordWithValidationAttributesStillWorks — record types are unaffected by the fallback (no double-application regression)

Fixes #61538

For C# 12 class primary constructors, validation attributes like
[Range], [Required], [MinLength] etc. exist only on the constructor
parameter, not on the synthesized property. The OpenAPI schema
generator reads attributes from JsonPropertyInfo.AttributeProvider
(the PropertyInfo) and never sees them.

Check JsonPropertyInfo.AssociatedParameter.AttributeProvider as a
fallback for constructor parameter attributes. Property-level
attributes still take precedence (applied second, overwriting).

Also extends the TypeInfoResolver modifier to check constructor
parameter attributes for [Required].

Fixes dotnet#61538
@matantsach matantsach requested a review from a team as a code owner March 17, 2026 13:02
Copilot AI review requested due to automatic review settings March 17, 2026 13:02
@github-actions github-actions Bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Mar 17, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Mar 17, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Thanks for your PR, @@matantsach. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@martincostello martincostello added feature-openapi area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Mar 17, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the OpenAPI schema generation pipeline to surface validation and descriptive metadata applied to C# 12 class primary-constructor parameters (which otherwise don’t get copied onto synthesized properties), ensuring generated schemas reflect those constraints.

Changes:

  • Extend required-property detection to also consider JsonPropertyInfo.AssociatedParameter.AttributeProvider for [Required].
  • Extend schema transformation to also read constructor-parameter ValidationAttribute, DefaultValueAttribute, and DescriptionAttribute metadata (with property-level attributes taking precedence).
  • Add test coverage for validation/required/description attributes on primary constructor parameters, plus an override-precedence and record-regression test.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs Adds constructor-parameter attribute fallback for required detection and schema keyword mapping.
src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.PropertySchemas.cs Adds tests validating schema output for primary-constructor parameter attributes and precedence behavior.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +121 to +124
if (parameterAttributes.OfType<ValidationAttribute>() is { } paramValidationAttributes)
{
schema.ApplyValidationAttributes(paramValidationAttributes);
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — though this pattern is inherited from the existing property-level block just below (line 151), which uses the same OfType<ValidationAttribute>() is { } guard. I kept it consistent with the surrounding code, but happy to clean up both if the team prefers.

Comment on lines +129 to +142
var isInlinedParamSchema = !schema.WillBeComponentized();
if (isInlinedParamSchema)
{
if (parameterAttributes.OfType<DescriptionAttribute>().LastOrDefault() is { } paramDescriptionAttribute)
{
schema[OpenApiSchemaKeywords.DescriptionKeyword] = paramDescriptionAttribute.Description;
}
}
else
{
if (parameterAttributes.OfType<DescriptionAttribute>().LastOrDefault() is { } paramDescriptionAttribute)
{
schema[OpenApiConstants.RefDescriptionAnnotation] = paramDescriptionAttribute.Description;
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same story here — the existing property-level block (lines 153-165) has the same duplicated DescriptionAttribute lookup across the inline/componentized branches. Kept it parallel for consistency, but I can consolidate both blocks if the team would like that as part of this PR.

@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Looks like this PR hasn't been active for some time and the codebase could have been changed in the meantime.
To make sure no conflicting changes have occurred, please rerun validation before merging. You can do this by leaving an /azp run comment here (requires commit rights), or by simply closing and reopening.

@dotnet-policy-service dotnet-policy-service Bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Mar 25, 2026
This was referenced Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc community-contribution Indicates that the PR has been added by a community member feature-openapi pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants