Skip to content

Fix #3169: emit form encoding for properties of a complex form parameter - #4128

Merged
martincostello merged 1 commit into
domaindrivendev:masterfrom
RaphaelFakhri:fix/3169-form-encoding
Sep 13, 2026
Merged

martincostello merged 1 commit into
domaindrivendev:masterfrom
RaphaelFakhri:fix/3169-form-encoding

Conversation

@RaphaelFakhri

Copy link
Copy Markdown
Contributor

Fixes #3169.

GenerateRequestBodyFromFormParameters built the encoding map from the media type schema's own properties:

var totalProperties = schema.AllOf?.FirstOrDefault((p) => p.Properties?.Count > 0)?.Properties ?? schema.Properties;

When a form parameter is a complex type, that schema is an unattached OpenApiSchemaReference whose Properties proxy to a null target while the document is still being generated, so its properties produced no encoding entries and the map came out empty. Parameters bound flat did get style: form, which is what makes Swagger UI explode an array into repeated fields, so an array behaved differently depending only on whether it sat inside a complex object.

The properties are now resolved through SchemaRepository before the map is built, by a new internal ResolveProperties extension that mirrors the existing ResolveType helper: it follows the reference, walks allOf, guards against cycles with a visited-id set, and takes the first value on a key collision.

Two things worth stating rather than leaving to be found in the diff.

The map now covers every allOf member and the schema's own properties, where the old expression took only the first member that had any. Every entry maps to the same Style = Form, so this is strictly broader coverage with identical values.

This changes the emitted document for any application with a complex form parameter: those properties now get style: form encoding entries they previously lacked. That is the fix, not a side effect.

On the spec: OpenAPI 3.0 says an encoding key "MUST exist in the schema as a property". Here that is read as the resolved schema, which is the only coherent reading when the media type schema is a $ref, since every consumer resolves the reference before applying encoding. It also matches the document produced by the workaround in the issue.

A regression test in SwaggerGeneratorTests pins the encoding entries for an array property on a complex form-bound type, and three VerifyTests snapshots in the SwaggerGen tests plus six integration snapshots gain encoding blocks. Every snapshot was regenerated from a real run rather than edited by hand. Swashbuckle.AspNetCore.SwaggerGen.Test is 761 passed, 0 failed on net8.0, net9.0 and net10.0, and the integration VerifyTests are 27 passed, 0 failed on all three.

…lex form parameter

The encoding map was built from the media type schema's own Properties, but a
complex [FromForm] parameter is an unattached schema reference whose Properties
proxy to a null target during generation, so its properties got no encoding
entries at all. Arrays inside such an object then serialized comma joined,
while arrays bound as flat parameters exploded.

Resolve the reference through the schema repository, walking allOf, before
building the map.
@codecov

codecov Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.23%. Comparing base (98c3501) to head (dd841db).
⚠️ Report is 26 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4128      +/-   ##
==========================================
+ Coverage   95.21%   95.23%   +0.01%     
==========================================
  Files         111      111              
  Lines        4115     4132      +17     
  Branches      843      847       +4     
==========================================
+ Hits         3918     3935      +17     
  Misses        197      197              
Flag Coverage Δ
Linux 95.23% <100.00%> (+0.01%) ⬆️
Windows 95.23% <100.00%> (+0.01%) ⬆️
macOS 95.23% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The focused implementation handles references, composition, and cycles while comprehensive regression and snapshot coverage confirms the intended output.

Pull request overview

Fixes complex form-bound array serialization by resolving referenced schema properties before generating OpenAPI encoding metadata.

Changes:

  • Adds recursive $ref/allOf property resolution with cycle protection.
  • Generates style: form encoding for resolved complex-type properties.
  • Adds regression coverage and refreshes snapshots across supported frameworks.
File summaries
File Description
src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs Uses resolved properties for form encoding.
src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/OpenApiSchemaExtensions.cs Adds recursive property resolution.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs Tests complex array form encoding.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/TypeWithArrayProperty.cs Adds the regression model.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/FakeController.cs Adds the regression endpoint fixture.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/snapshots/8_0/VerifyTests.GetSwagger_Works_As_Expected_When_FromFormObject.verified.txt Updates .NET 8 form-object output.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/snapshots/8_0/VerifyTests.GetSwagger_Works_As_Expected_When_FromFormObject_AndString.verified.txt Updates .NET 8 mixed-form output.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/snapshots/8_0/VerifyTests.GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithSeveralFromForms.verified.txt Updates .NET 8 multi-form output.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/snapshots/9_0/VerifyTests.GetSwagger_Works_As_Expected_When_FromFormObject.verified.txt Updates .NET 9 form-object output.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/snapshots/9_0/VerifyTests.GetSwagger_Works_As_Expected_When_FromFormObject_AndString.verified.txt Updates .NET 9 mixed-form output.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/snapshots/9_0/VerifyTests.GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithSeveralFromForms.verified.txt Updates .NET 9 multi-form output.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/snapshots/10_0/VerifyTests.GetSwagger_Works_As_Expected_When_FromFormObject.verified.txt Updates .NET 10 form-object output.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/snapshots/10_0/VerifyTests.GetSwagger_Works_As_Expected_When_FromFormObject_AndString.verified.txt Updates .NET 10 mixed-form output.
test/Swashbuckle.AspNetCore.SwaggerGen.Test/snapshots/10_0/VerifyTests.GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithSeveralFromForms.verified.txt Updates .NET 10 multi-form output.
test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/8_0/VerifyTests.TypesAreRenderedCorrectly.verified.txt Records .NET 8 resolved encodings.
test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/8_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.verified.txt Updates .NET 8 WebApi output.
test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/9_0/VerifyTests.TypesAreRenderedCorrectly.verified.txt Records .NET 9 resolved encodings.
test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/9_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.verified.txt Updates .NET 9 WebApi output.
test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/10_0/VerifyTests.TypesAreRenderedCorrectly.verified.txt Records .NET 10 resolved encodings.
test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/10_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.verified.txt Updates .NET 10 WebApi output.
Review details
  • Files reviewed: 20/20 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@martincostello
martincostello merged commit 4d8c8d5 into domaindrivendev:master Sep 13, 2026
14 checks passed
@martincostello martincostello added this to the v10.2.4 milestone Sep 13, 2026
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Thanks for your contribution @RaphaelFakhri - the changes from this pull request have been published as part of version 10.3.0 📦, which is now available from NuGet.org 🚀

This was referenced Oct 7, 2026
This was referenced Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Array parameter in multipart/form-data does not explode when it's part of a complex object

3 participants