Skip to content

Follow-up (#223): body-bound shared DTO ([FromBody]/[FromForm]) may not receive rules when the same type is flattened as [FromQuery] #226

Description

@avgalex

Follow-up split out from the review of #224 (fix for #223): #224 (comment)

Limitation

The #223 fix recovers a concrete schema for a "reserved-but-removed" [FromQuery] container type by generating it into a throwaway SchemaRepository (SwashbuckleSchemaProvider.GetSchemaForType). That is safe for the parameters path, because there the recovered container schema is only used to read constraint values, which are then copied field-by-field onto the real operationParameter.Schema (FluentValidationOperationFilter.ApplyRulesToParameters).

The request-body path is different. In FluentValidationOperationFilter.ApplyRulesToRequestBody (around FluentValidationOperationFilter.cs:464), when schemaRefId != null it does:

resolvedSchema = schemaProvider.GetSchemaForType(parameterType);
...
FluentValidationSchemaBuilder.ApplyRulesToSchema(schema: resolvedSchema, ...); // mutates resolvedSchema in place

Here the recovered schema is the mutation target — there is no copy-back onto a separate document object. If the recovered schema came from the throwaway fallback, any rules applied to it are written to a disposable object and never reach the emitted document.

When this can bite

Only when a single DTO type is used both:

with RemoveUnusedQuerySchemas = true, and with the query operation processed before the body operation (so the reserved-but-removed state is in effect when the body operation runs).

Not a regression

This path already skipped in this situation before #224 — the empty-Properties guard (resolvedSchema.Properties.Count == 0) short-circuited it. #224 did not change body-bound behavior; it deliberately scoped itself to the "container schema used only for reading constraints" pattern (parameters). So this is a pre-existing gap, surfaced by the review, not something #224 introduced.

Needs a concrete repro to confirm how realistic the mixed [FromQuery] + [FromBody] shared-type scenario is in practice.

Fix direction

The structural fix is "Variant B" from the #223 discussion: make FluentValidationDocumentFilter production-ready and promote it (it is currently Experimental, behind RegistrationOptions.ExperimentalUseDocumentFilter). A document filter batches all types/parameters across the whole document and performs the Issue #180 cleanup once at the end, so the per-operation reserved-but-removed state never arises for any binding source — bodies included. That closes this class of bug uniformly instead of per-path.

Its parity checklist (from the #223 notes): required marking (#209), request bodies + [FromForm] + encoding.contentType (#216), full component-schema coverage (response DTOs, nested SetValidator), and robustness fixes (FindParam only inspects the first operation of a path; duplicate schemasForParameters; nullability/obsolete cleanup).

Status

Filing to track; no action pending on #224 (which stays scoped to #223). Would like feedback on whether the mixed body+query shared-DTO scenario is worth prioritizing on its own, or folded into the Variant B effort.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions