Skip to content

Fix attributes on primary constructor parameters in OpenAPI documents #62027

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented May 20, 2025

Problem

Currently, validation attributes (like [Range]) on primary constructor parameters in class types aren't being reflected in generated OpenAPI documents. While record types correctly show these attributes, class types don't:

// Works: Schema includes age range constraints of 0-120
public record UserRecordDto([property: Range(0, 120)] int age);

// Doesn't work: Schema doesn't show age range constraints
public class UserClassDto([property: Range(0, 120)] int age)
{
    public int Age => age;
}

This inconsistency is occurring because record primary constructors auto-generate properties that carry the attributes, but class primary constructors don't.

Solution

The implementation enhances the schema generation process to:

  1. Detect when a type is using a primary constructor
  2. Find constructor parameters that match property names in the schema
  3. Apply any validation attributes from those parameters to the corresponding property schemas

This ensures consistency between validation behavior and OpenAPI documentation, particularly for class types using C# 12's primary constructor feature.

Testing

Added tests in two forms:

  • A unit test checking the schema generation directly
  • An integration test using a real HTTP server to verify attributes appear correctly in the schema

Fixes #61538.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • dc.services.visualstudio.com
    • Triggering command: /home/REDACTED/work/aspnetcore/aspnetcore/.dotnet/dotnet msbuild /m /nologo /clp:Summary /v:minimal /nr:true /warnaserror /p:TreatWarningsAsErrors=true /p:ContinuousIntegrationBuild=false /home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/restore.proj /t:__WriteToolsetLocation /clp:ErrorsOnly;NoSummary /p:__ToolsetLocationOutputFile=/home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/10.0.0-beta.25265.101.txt (dns block)
  • pkgs.dev.azure.com
    • Triggering command: /home/REDACTED/work/aspnetcore/aspnetcore/.dotnet/dotnet msbuild /m /nologo /clp:Summary /v:minimal /nr:true /warnaserror /p:TreatWarningsAsErrors=true /p:ContinuousIntegrationBuild=false /home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/restore.proj /t:__WriteToolsetLocation /clp:ErrorsOnly;NoSummary /p:__ToolsetLocationOutputFile=/home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/10.0.0-beta.25265.101.txt (dns block)

If you need me to access, download, or install something from one of these locations, you can either:


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits May 20, 2025 19:49
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] Attributes on parameters of primary constructors should be reflected in generated OpenAPI documents Fix attributes on primary constructor parameters in OpenAPI documents May 20, 2025
@Copilot Copilot AI requested a review from captainsafia May 20, 2025 19:52
Copilot finished work on behalf of captainsafia May 20, 2025 19:52
@captainsafia
Copy link
Member

@copilot Good start! Can you update the implementation so that attributes work even when they don't target the property. For example, the example below should pass.

public record RecordWithPrimaryConstructor(
    [Required] string name, 
    [Range(0, 120)] int age);

…ameters

Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented May 20, 2025

@copilot Good start! Can you update the implementation so that attributes work even when they don't target the property. For example, the example below should pass.

...

I've updated the implementation to support validation attributes without the property: prefix. The changes:

  1. Improved property name matching to check multiple naming conventions (direct match, camel case, pascal case)
  2. Enhanced the attribute detection to work regardless of attribute target

This should now handle the example case you provided with attributes that don't use the property: prefix. Changes are in commit 40d0a45.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cdn.fwupd.org
    • Triggering command: /usr/bin/fwupdmgr refresh (dns block)
  • dc.services.visualstudio.com
    • Triggering command: /home/REDACTED/work/aspnetcore/aspnetcore/.dotnet/dotnet msbuild /m /nologo /clp:Summary /v:minimal /nr:true /warnaserror /p:TreatWarningsAsErrors=true /p:ContinuousIntegrationBuild=false /home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/restore.proj /t:__WriteToolsetLocation /clp:ErrorsOnly;NoSummary /p:__ToolsetLocationOutputFile=/home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/10.0.0-beta.25265.101.txt (dns block)
    • Triggering command: ./.dotnet/dotnet build ./src/OpenApi (dns block)
  • pkgs.dev.azure.com
    • Triggering command: /home/REDACTED/work/aspnetcore/aspnetcore/.dotnet/dotnet msbuild /m /nologo /clp:Summary /v:minimal /nr:true /warnaserror /p:TreatWarningsAsErrors=true /p:ContinuousIntegrationBuild=false /home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/restore.proj /t:__WriteToolsetLocation /clp:ErrorsOnly;NoSummary /p:__ToolsetLocationOutputFile=/home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/10.0.0-beta.25265.101.txt (dns block)
    • Triggering command: ./.dotnet/dotnet build ./src/OpenApi (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of captainsafia May 20, 2025 20:04
@captainsafia
Copy link
Member

@copilot Can you remove the PrimaryConstructorAttributesTest test and make sure that the code that is used to discover the attributes is trim-friendly?

Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented May 20, 2025

@copilot Can you remove the PrimaryConstructorAttributesTest test and make sure that the code that is used to discover the attributes is trim-friendly?

I've removed the PrimaryConstructorAttributesTest and made the attribute discovery code trim-friendly in commit 85468ea. The main changes:

  1. Wrapped the reflection-based code in #if !TRIMMING conditional blocks to prevent it from running during trimming
  2. Moved the parameter attribute logic to a separate helper method for better organization
  3. Added a null check for parameter.Name to improve safety

The unit tests in OpenApiSchemaService.PrimaryConstructorSchemas.cs are still in place to verify the functionality.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • pkgs.dev.azure.com
    • Triggering command: /home/REDACTED/work/aspnetcore/aspnetcore/.dotnet/dotnet msbuild /m /nologo /clp:Summary /v:minimal /nr:true /warnaserror /p:TreatWarningsAsErrors=true /p:ContinuousIntegrationBuild=false /home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/restore.proj /t:__WriteToolsetLocation /clp:ErrorsOnly;NoSummary /p:__ToolsetLocationOutputFile=/home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/10.0.0-beta.25265.101.txt (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of captainsafia May 20, 2025 22:03
@captainsafia
Copy link
Member

@copilot Instead of using the #if TRIMMING if-def to make the code trim-compatible, can you see if applying the [DynamicallyAccessedMembers] attribute on a helper method that you call will work. Something like:

private static void ApplyCtorAttributes(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
    Type dtoType,
    JsonObject properties)
{
    foreach (var ctor in dtoType.GetConstructors( /* public instance only */ ))
    {
        ...
    }
}

…dly code

Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented May 20, 2025

@copilot Instead of using the #if TRIMMING if-def to make the code trim-compatible, can you see if applying the [DynamicallyAccessedMembers] attribute on a helper method that you call will work. Som...

I've updated the implementation to use DynamicallyAccessedMembers instead of #if TRIMMING directives. The changes:

  1. Created a new helper method ApplyCtorAttributes with the [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] attribute on the type parameter
  2. Moved the constructor parameter attribute logic to this helper method
  3. Made the ApplyParameterAttributesToProperties method a regular private static method

This approach preserves the same functionality while ensuring the code is properly annotated for trimming. Changes are in commit 4cc431c.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cdn.fwupd.org
    • Triggering command: /usr/bin/fwupdmgr refresh (dns block)
  • pkgs.dev.azure.com
    • Triggering command: /home/REDACTED/work/aspnetcore/aspnetcore/.dotnet/dotnet msbuild /m /nologo /clp:Summary /v:minimal /nr:true /warnaserror /p:TreatWarningsAsErrors=true /p:ContinuousIntegrationBuild=false /home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/restore.proj /t:__WriteToolsetLocation /clp:ErrorsOnly;NoSummary /p:__ToolsetLocationOutputFile=/home/REDACTED/work/aspnetcore/aspnetcore/artifacts/toolset/10.0.0-beta.25265.101.txt (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of captainsafia May 20, 2025 22:28
@dotnet-policy-service dotnet-policy-service bot added this to the 10.0-preview6 milestone May 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
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
2 participants