Skip to content

Releases: micro-elements/MicroElements.Swashbuckle.FluentValidation

7.2.1

Choose a tag to compare

@avgalex avgalex released this 13 Jul 11:12
8b41798

Fixed — aliased header parameters lost their FluentValidation rules

Reported in #230 by @jgarciadelanoceda.

With the document-filter pipeline enabled (UseDocumentFilter = true), constraints were not applied to aliased operation parameters:

public sealed class HelloRequest
{
    [FromQuery]
    public string? Name { get; set; }

    [FromHeader(Name = "X-Correlation-Id")]   // <- rules were silently dropped
    public string? XCorrelationId { get; set; }
}

Root cause: the document filter resolved the schema property key for the alias ("X-Correlation-Id"XCorrelationId) but used it only for required-marking, while the constraint copy still looked the property up by the raw alias — and never found it. The default operation-filter pipeline handled this correctly, so the two pipelines disagreed. The constraint copy now uses the resolved key and additionally falls back to the INameResolver for renames beyond separators (e.g. [JsonPropertyName]) — full parity with the operation filter.

Fixed — a dot in a header name dropped its rules (all pipelines)

A dot is legal in an HTTP header name, but [FromHeader(Name = "X.Trace.Id")] was mistaken for a flattened nested [FromQuery] dot-path (#209/#211): the name was truncated to the last segment (Id), matched no property, and the rules disappeared. Header-bound parameters are now exempt from the dot-path logic in all three parameter pipelines — FluentValidationOperationFilter, FluentValidationDocumentFilter and the ASP.NET Core FluentValidationOperationTransformer. This one affected the default pipeline too, not just the document filter. NSwag is unaffected (it has no dot-path parameter logic).

Fixed — case-sensitive parameter lookup in the document filter

The document filter matched document parameters to their ApiExplorer descriptions case-sensitively, so options such as DescribeAllParametersInCamelCase made it skip both required-marking and the constraint copy. The lookup is now case-insensitive, matching the operation filter.

Compatibility

Patch release — no public API changes. The constraint-copy fallback in the ASP.NET Core transformer was also aligned with the other two pipelines, so all three now resolve property names identically.


Full changelog: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/blob/master/CHANGELOG.md

7.2.0

Choose a tag to compare

@avgalex avgalex released this 12 Jul 20:42
b83b7cc

Added — the document-filter pipeline is now a supported opt-in (UseDocumentFilter)

services.AddFluentValidationRulesToSwagger(
    configureRegistration: options => options.UseDocumentFilter = true);

The document filter processes the whole OpenAPI document at once — component schemas, operation parameters and request bodies — and performs the unused-query-schema cleanup once at the end. Because there is no per-operation state, the shared-DTO bug class (#223, #226) cannot occur in this pipeline, on any target framework — including net8.0/net9.0, where the 7.1.11 fix could not fully apply (the ReplaceSchemaId healing API only exists in Swashbuckle 10.1.0+).

Full parity with the default pipeline

Previously the filter was experimental with significant gaps. Now it matches the default schema + operation filter pipeline:

  • Required-marking with the whole-dot-path check (#209)
  • Request bodies and encoding.contentType for [FromForm] uploads (#216)
  • Multiple validators per type, allOf/oneOf/anyOf traversal
  • $ref preservation for properties untouched by rules (#198, net10.0)
  • Every operation of a multi-verb path is processed (previously only the first)
  • On net10.0 the cleanup also clears Swashbuckle's internal reserved-ids for processed container types, so custom document filters running afterwards can regenerate them

The #209 and #216 logic is shared between both pipelines via internal components, so they cannot drift apart.

Robustness

A throwing validator no longer fails the whole document generation; the filter falls back to ServiceProviderValidatorRegistry like the sibling filters and honors an injected IFluentValidationRuleProvider (new optional constructor parameter, appended last — source-compatible). Behavior note: constructing the filter directly with neither validatorRegistry nor serviceProvider now throws ArgumentNullException instead of silently applying no rules; the DI path is unaffected.

Compatibility

  • The default pipeline is unchangedUseDocumentFilter defaults to false. A future major version may switch the default.
  • ExperimentalUseDocumentFilter still works as an [Obsolete] alias forwarding to UseDocumentFilter.
  • samples/MinimalApi now runs on the document-filter pipeline; the README documents the option and its caveats.

Full changelog: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/blob/master/CHANGELOG.md

7.1.11

Choose a tag to compare

@avgalex avgalex released this 12 Jul 12:38
92c1962

Fixed — Issue #226: a DTO shared between [FromQuery] and a request body lost its rules, and the document could contain a dangling $ref (net10.0)

When the same DTO was bound as a flattened [FromQuery]/[AsParameters] container by one endpoint and as a request body ([FromBody]/[FromForm]) by another, the per-operation schema cleanup left the type "reserved-but-removed" inside Swashbuckle's SchemaRepository. The body operation then emitted a $ref to a component that no longer exists — an invalid document — and the FluentValidation constraints never reached it. Reproduces with the default RemoveUnusedQuerySchemas = true.

app.MapGet("/search", ([FromQuery] HelloRequest request) => ...); // processed first: cleanup runs
app.MapPost("/hello", ([FromBody] HelloRequest request) => ...);  // before: $ref to a missing component, no rules
                                                                  // after:  component regenerated, rules applied
  • Root cause: the Issue #180 cleanup could remove the side-effect component schema, but Swashbuckle's internal reserved-ids map was unreachable via public API, so GenerateSchema kept returning a bare $ref without regenerating the component. The 7.1.10 fix (#223) recovered the schema for reading constraint values only; it could not restore the document's component.
  • Fix (net10.0 target, Swashbuckle 10.x): the cleanup now heals the repository state using SchemaRepository.ReplaceSchemaId (public API since Swashbuckle 10.1.0) before removing a side-effect schema — the reservation is cleared together with the component, so the next operation binding the same type regenerates a full component and the rules reach the real document object.
  • net8.0/net9.0 pin Swashbuckle 8.1.1, where ReplaceSchemaId does not exist — they keep the 7.1.10 throwaway-recovery behavior (parameters path). The recovery also remains as a safety net on all targets.
  • No public API changes.

Thanks to @jgarciadelanoceda for suggesting the ReplaceSchemaId approach (added in domaindrivendev/Swashbuckle.AspNetCore#3708).


Full changelog: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/blob/master/CHANGELOG.md

7.1.10

Choose a tag to compare

@avgalex avgalex released this 12 Jul 10:22
55c62b0

Fixed — Issue #223: a [FromQuery] DTO shared by several endpoints lost its rules on every endpoint after the first

When the same [FromQuery]/[AsParameters] DTO was bound by more than one endpoint, only the first operation received FluentValidation constraints (minLength, pattern, maximum, ...) — every subsequent endpoint silently lost them. Reproduces with the default RemoveUnusedQuerySchemas = true.

app.MapGet("/hello1", ([FromQuery] HelloRequest request) => ...); // constraints emitted
app.MapGet("/hello2", ([FromQuery] HelloRequest request) => ...); // before: constraints lost — after: emitted
  • Root cause: FluentValidationOperationFilter runs once per operation. The Issue #180 cleanup removes the temporary [FromQuery] container schema from SchemaRepository.Schemas, but Swashbuckle also keeps the type in its internal reserved-ids map, which the cleanup cannot touch. For the 2nd+ endpoint GenerateSchema then returns a bare $ref with no Properties, so the "has properties" guard skipped all rules.
  • Fix: SwashbuckleSchemaProvider.GetSchemaForType detects this reserved-but-removed state and recovers the concrete schema by generating it into a throwaway SchemaRepository. Fully isolated — the shared repository and its reserved-id state are never mutated — so the Issue #180 cleanup and all other operation-filter behavior (required marking #209, nested [FromQuery] #211/#213, request bodies and encoding.contentType #216) are preserved.
  • Follow-up hardening is tracked in #226: body-bound shared DTOs, and a ReplaceSchemaId-based state-healing cleanup on the net10.0 target (needs Swashbuckle >= 10.1.0).

Thanks to @jgarciadelanoceda for the report and the ReplaceSchemaId discussion.


Full changelog: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/blob/master/CHANGELOG.md

7.1.9

Choose a tag to compare

@avgalex avgalex released this 09 Jul 07:16
59dd105

Fixed — Issue #222: numeric bounds from small integer types were silently dropped

Validation bounds whose value is a small integer type (short/byte/ushort/uint/ulong/sbyte) produced no minimum/maximum in the generated schema. The Between/Comparison rules matched, but IsNumeric did not recognize the boxed value, so the bound was skipped — even though the property is emitted as "type": "integer".

RuleFor(x => x.Quantity).InclusiveBetween((short)1, (short)99);
// before: { "type": "integer", "format": "int16" }
// after:  { "type": "integer", "format": "int16", "minimum": 1, "maximum": 99 }
  • IsNumeric in the shared core (MicroElements.OpenApi.FluentValidation) now recognizes all integer primitives — sbyte/byte/short/ushort/int/uint/long/ulong — in addition to float/double/decimal/BigInteger. NumericToDecimal already converted them all.
  • One change in the shared core fixes all three providers: Swashbuckle, NSwag, and the native Microsoft.AspNetCore.OpenApi transformer.
  • No overflow risk: decimal comfortably covers ulong.MaxValue, and BigInteger keeps its dedicated branch.
  • Purely additive — no behavior change for the already-supported numeric types.

Thanks to @send0xx for the precise report and the proposed fix.


Full changelog: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/blob/master/CHANGELOG.md

7.1.8

Choose a tag to compare

@avgalex avgalex released this 01 Jul 19:16
2e8c074

🔒 Security fix — Issue #220

Closes the transitive high-severity advisory GHSA-v5pm-xwqc-g5wc / CVE-2026-49451 (CVSS 7.5, CWE-674 Uncontrolled Recursion — a circular $ref schema could stack-overflow the OpenAPI reader; availability / process-termination only).

  • Bumped Swashbuckle.AspNetCore.SwaggerGen 10.0.010.2.1 on the net10.0 target, which resolves the transitive Microsoft.OpenApi from the vulnerable 2.3.0 to the patched 2.7.5.
  • The net8.0 / net9.0 targets use Swashbuckle 8.1.1Microsoft.OpenApi v1 and were never in the advisory range — left unchanged.
  • No public API or OpenAPI output changes — dependency + version bump only.

📎 IFormFile media type & file size validation — Issue #216

Stable rollup of the work previously shipped as 7.1.8-beta.1 / 7.1.8-beta.2:

  • New File-level FluentValidation rules in MicroElements.OpenApi.FluentValidation.FileUpload: .FileContentType(params string[]), .MaxFileSize(long), .MinFileSize(long), .FileSizeBetween(long, long) on IRuleBuilder<T, IFormFile>.
  • Swashbuckle, NSwag, and Microsoft.AspNetCore.OpenApi emit multipart/form-data encoding.contentType for file parts and append the allowed types / size limits to the file property description.
  • Purely additive / opt-in — output only changes when the new rules are used.

Full changelog: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/blob/master/CHANGELOG.md

7.1.8-beta.2

7.1.8-beta.2 Pre-release
Pre-release

Choose a tag to compare

@avgalex avgalex released this 21 Jun 10:16

Pre-release for #216 — tracks PR #218.

Adds to 7.1.8-beta.1: Microsoft.AspNetCore.OpenApi now emits encoding.contentType for the file part (the backend Scalar uses), so the allowed media types are machine-readable in the document, not only in the description:

multipart/form-data:
  schema: { $ref: "#/components/schemas/UploadProductImageRequest" }   # net10
  encoding:
    File:
      contentType: "image/jpeg, image/png"

Works on net9 (inline form schema) and net10 (resolves the whole-body $ref component to find the part name). Backend matrix is now content-type+size on all three backends, both as description and encoding.contentType.

Pre-release built from branch feature/issue-216-media-types (not yet merged to master).

7.1.8-beta.1

7.1.8-beta.1 Pre-release
Pre-release

Choose a tag to compare

@avgalex avgalex released this 20 Jun 17:40

Pre-release for #216 — media type (content type) & file size validation for IFormFile uploads. Tracks PR #218.

New File-level FluentValidation rules (MicroElements.OpenApi.FluentValidation.FileUpload):

RuleFor(x => x.File)
    .NotNull()
    .FileContentType("image/jpeg", "image/png")
    .MaxFileSize(2 * 1024 * 1024);

Also: .MinFileSize(long), .FileSizeBetween(long, long). They enforce at runtime and drive OpenAPI.

Backend content type + size in description encoding.contentType
Swashbuckle ✅ (net8/9 = OAS 3.0; net10 = OAS 3.1)
NSwag ✅ via FluentValidationOperationProcessor (encodingType — NSwag limitation)
Microsoft.AspNetCore.OpenApi ❌ not emitted

Additive / opt-in — no existing document output changes. See CHANGELOG and PR #218.

Pre-release built from branch feature/issue-216-media-types (not yet merged to master).

7.1.7

Choose a tag to compare

@avgalex avgalex released this 20 Jun 12:55
ad34293

Stable release 7.1.7 — promotes 7.1.7-beta.3 to stable.

Fixed

  • Nested [FromQuery] fixes (#209 + #211) now also apply to the native Microsoft.AspNetCore.OpenApi transformer and the experimental Swashbuckle DocumentFilter (Issue #213)
    • FluentValidationOperationTransformer now follows the same reachability + ancestor-required rules as the Swashbuckle OperationFilter
    • The experimental FluentValidationDocumentFilter no longer copies value constraints onto a flattened nested parameter whose nested validation is not wired from the root validator
    • GetMethodInfo now resolves the action method from ControllerActionDescriptor (MVC controllers), not only minimal-API endpoint metadata
    • NSwag is unaffected (it has no [FromQuery] parameter flattening)
  • A validator for a nested type bound via [FromQuery] was reflected in the OpenAPI document even when it was not wired into the root validator via SetValidator/ChildRules (Issue #211)
    • Nested rules are now applied only when the SetValidator/ChildRules chain from the action's root [FromQuery] validator actually reaches the leaf container
    • Behavioral change: when no validator is registered for the root [FromQuery] type, a flattened nested parameter is left unconstrained — matching runtime
  • A required leaf property inside an optional nested type bound via [FromQuery] was wrongly marked as a required parameter (Issue #209)
    • A flattened nested parameter is now marked required only when every ancestor segment of the dot-path is required
    • Value constraints (e.g. minLength) still apply to an optional nested parameter when it is provided

Full changelog: CHANGELOG.md

7.1.7-beta.3

7.1.7-beta.3 Pre-release
Pre-release

Choose a tag to compare

@avgalex avgalex released this 17 Jun 20:25
241530b
  • Fixed: The nested [FromQuery] fixes (#209 + #211) now also apply to the native Microsoft.AspNetCore.OpenApi transformer and the experimental Swashbuckle DocumentFilter (Issue #213)
    • FluentValidationOperationTransformer (package MicroElements.AspNetCore.OpenApi.FluentValidation) previously set a nested parameter required from the leaf validator alone — ignoring both whether the SetValidator/ChildRules chain reaches the leaf (#211) and whether every ancestor of the dot-path is required (#209). It now follows the same reachability + ancestor-required rules as the Swashbuckle OperationFilter
    • The experimental FluentValidationDocumentFilter no longer copies value constraints onto a flattened nested parameter whose nested validation is not wired from the root validator (#211)
    • GetMethodInfo now resolves the action method from ControllerActionDescriptor (MVC controllers), not only minimal-API endpoint metadata, so the dot-path root type can be resolved for controller actions
    • NSwag is unaffected (it has no [FromQuery] parameter flattening)
  • Fixed: A validator for a nested type bound via [FromQuery] was reflected in the OpenAPI document even when it was not wired into the root validator via SetValidator/ChildRules (Issue #211)
    • FluentValidationOperationFilter resolved the leaf container's validator directly from the registry (by ModelMetadata.ContainerType), so a nested NotEmpty() marked the flattened parameter (e.g. RequiredSubType.SubProperty) as required even though FluentValidation never validates an unwired child object — the OpenAPI doc claimed required, but the API accepted requests without it
    • Fix: for a flattened nested parameter, nested rules are now applied only when the SetValidator/ChildRules chain from the action's root [FromQuery] validator actually reaches the leaf container; otherwise the parameter is left unconstrained, matching runtime behavior
    • When the root container type cannot be resolved, prior behavior is preserved (no regression for existing nested-parameter scenarios)
    • Behavioral change: when no validator is registered for the root [FromQuery] type (only a leaf/child validator is registered), a flattened nested parameter is now left unconstrained — matching runtime, where no validation runs without a root validator
  • Fixed: A required leaf property inside an optional nested type bound via [FromQuery] was wrongly marked as a required parameter (Issue #209)
    • The 7.1.1 fix (Issue #162) made nested [FromQuery] validation match the leaf property name, but FluentValidationOperationFilter then set required based solely on the leaf type, ignoring whether the ancestor segment of the dot-path was optional
    • Because two nested properties of the same leaf type share one schema/validator (e.g. OptionalSubType.SubProperty and RequiredSubType.SubProperty), a NotEmpty() on the leaf marked both flattened parameters as required
    • Fix: a flattened nested parameter is now marked required only when every ancestor segment of the dot-path is required — resolved from the action's root [FromQuery] type, combining the native schema required (e.g. the C# required modifier) with FluentValidation NotNull/NotEmpty rules
    • Value constraints (e.g. minLength) still apply to an optional nested parameter when it is provided
    • When the root container type cannot be resolved, prior behavior is preserved (no regression for existing nested-parameter scenarios)