micro-elements - MicroElements.Swashbuckle.FluentValidation 7.2.2

Swagger ISchemaFilter that uses FluentValidation validators instead System.ComponentModel based attributes.

PM> Install-Package MicroElements.Swashbuckle.FluentValidation -Version 7.2.2 -Source https://www.myget.org/F/micro-elements/api/v3/index.json

Copy to clipboard

> nuget.exe install MicroElements.Swashbuckle.FluentValidation -Version 7.2.2 -Source https://www.myget.org/F/micro-elements/api/v3/index.json

Copy to clipboard

> dotnet add package MicroElements.Swashbuckle.FluentValidation --version 7.2.2 --source https://www.myget.org/F/micro-elements/api/v3/index.json

Copy to clipboard
<PackageReference Include="MicroElements.Swashbuckle.FluentValidation" Version="7.2.2" />
Copy to clipboard
source https://www.myget.org/F/micro-elements/api/v3/index.json

nuget MicroElements.Swashbuckle.FluentValidation  ~> 7.2.2
Copy to clipboard

> choco install MicroElements.Swashbuckle.FluentValidation --version 7.2.2 --source https://www.myget.org/F/micro-elements/api/v2

Copy to clipboard
Import-Module PowerShellGet
Register-PSRepository -Name "micro-elements" -SourceLocation "https://www.myget.org/F/micro-elements/api/v2"
Install-Module -Name "MicroElements.Swashbuckle.FluentValidation" -RequiredVersion "7.2.2" -Repository "micro-elements" 
Copy to clipboard

MicroElements.Swashbuckle.FluentValidation

Use FluentValidation rules instead of ComponentModel attributes to define swagger schema.

Note: For WebApi see: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation.WebApi

Statuses

License NuGetVersion NuGetDownloads MyGetVersion

Build and publish AppVeyor Coverage Status

Gitter

Supporting the project

MicroElements.Swashbuckle.FluentValidation is developed and supported by @petriashev for free in his spare time. If you find MicroElements.Swashbuckle.FluentValidation useful, please consider financially supporting the project via OpenCollective which will help keep the project going 🙏.

Usage

1. Minimal API

MinimalApi.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
        <PackageReference Include="MicroElements.Swashbuckle.FluentValidation" Version="7.1.6" />
        <PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
    </ItemGroup>
    
</Project>

Program.cs

using FluentValidation;
using FluentValidation.AspNetCore;
using MicroElements.Swashbuckle.FluentValidation.AspNetCore;

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;

// Asp.Net stuff
services.AddControllers();
services.AddEndpointsApiExplorer();

// Add Swagger
services.AddSwaggerGen();

// Add FV
services.AddFluentValidationAutoValidation();
services.AddFluentValidationClientsideAdapters();

// Add FV validators
services.AddValidatorsFromAssemblyContaining<Program>();

// Add FV Rules to swagger
services.AddFluentValidationRulesToSwagger();

var app = builder.Build();

// Use Swagger
app.UseSwagger();
app.UseSwaggerUI();

app.MapControllers();

app.Run();

2. AspNetCore WebApi

Reference packages in your web project

<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
<PackageReference Include="MicroElements.Swashbuckle.FluentValidation" Version="7.1.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />

Change Startup.cs

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Asp.net stuff
    services.AddControllers();
    
    // HttpContextValidatorRegistry requires access to HttpContext
    services.AddHttpContextAccessor();

    // Register FV validators
    services.AddValidatorsFromAssemblyContaining<Startup>(lifetime: ServiceLifetime.Scoped);

    // Add FV to Asp.net
    services.AddFluentValidationAutoValidation();

    // Add swagger
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
    });

    // [Optional] Add INameResolver (SystemTextJsonNameResolver will be registered by default)
    // services.AddSingleton<INameResolver, CustomNameResolver>();

    // Adds FluentValidationRules staff to Swagger. (Minimal configuration)
    services.AddFluentValidationRulesToSwagger();

    // [Optional] Configure generation options for your needs. Also can be done with services.Configure<SchemaGenerationOptions>
    // services.AddFluentValidationRulesToSwagger(options =>
    // {
    //     options.SetNotNullableIfMinLengthGreaterThenZero = true;
    //     options.UseAllOffForMultipleRules = true;
    // });

    // Adds logging
    services.AddLogging(builder => builder.AddConsole());
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

    // Adds swagger
    app.UseSwagger();

    // Adds swagger UI
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    });
}

Version compatibility

MicroElements.Swashbuckle.FluentValidation Swashbuckle.AspNetCore FluentValidation
[1.1.0, 2.0.0) [3.0.0, 4.0.0) >=7.2.0
[2.0.0, 3.0.0) [4.0.0, 5.0.0) >=8.1.3
[3.0.0, 3.1.0) [5.0.0, 5.2.0) >=8.3.0
[3.1.0, 4.2.1) [5.2.0, 6.0.0) >=8.3.0
[4.2.0, 5.0.0) [5.5.1, 7.0.0) [9.0.0, 10)
[5.0.0, 6.0.0) [6.3.0, 7.0.0) [10.0.0, 12)
[7.0.0, 8.0.0) [8.0.0, 11.0.0) [11.0.0, 13)

.NET 8/9 use Swashbuckle 8.x (Microsoft.OpenApi 1.x); .NET 10 uses Swashbuckle 10.x (Microsoft.OpenApi 2.x).

Sample application

See sample project: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/tree/master/samples/SampleWebApi

Supported validators

  • INotNullValidator (NotNull)
  • INotEmptyValidator (NotEmpty)
  • ILengthValidator (for strings: Length, MinimumLength, MaximumLength, ExactLength) (for arrays: MinItems, MaxItems)
  • IRegularExpressionValidator (Email, Matches)
  • IComparisonValidator (GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual)
  • IBetweenValidator (InclusiveBetween, ExclusiveBetween)

File uploads (media types & size) — Issue #216

Validation rules written on nested IFormFile members (e.g. RuleFor(x => x.File.ContentType) / RuleFor(x => x.File.Length)) are not reflected in the OpenAPI document: FluentValidation names them File.ContentType / File.Length, which never match the flat File schema property, and Must(...) carries no introspectable metadata. Use the dedicated File-level rules instead:

using MicroElements.OpenApi.FluentValidation.FileUpload;

public class UploadProductImageRequestValidator : AbstractValidator<UploadProductImageRequest>
{
    public UploadProductImageRequestValidator()
    {
        RuleFor(x => x.File)
            .NotNull()                                    // required
            .FileContentType("image/jpeg", "image/png")   // allowed media types
            .MaxFileSize(2 * 1024 * 1024);                // 2 MB
    }
}

These rules enforce the constraints at runtime and drive the OpenAPI output:

multipart/form-data:
  schema:
    properties:
      File:
        type: string
        format: binary
        description: "Allowed content types: image/jpeg, image/png. Maximum file size: 2097152 bytes."
  encoding:
    File:
      contentType: "image/jpeg, image/png"

Available rules: .FileContentType(params string[]), .MaxFileSize(long), .MinFileSize(long), .FileSizeBetween(long, long).

Backend support:

Backend size & content types in description machine-readable encoding.contentType
Swashbuckle ✅ ✅ (net8/9 = OpenAPI 3.0; net10 = OpenAPI 3.1)
NSwag ✅ ✅ via FluentValidationOperationProcessor (serialized as encodingType — a known NSwag limitation)
Microsoft.AspNetCore.OpenApi ✅ ✅ (net9 + net10)

The issue scenario — making the generated OpenAPI document reflect the allowed content types and size limit — works on all three backends, both via the file part description and as the machine-readable encoding.contentType.

Notes:

  • File size has no standard OpenAPI/JSON-Schema byte keyword, so it is documented in description only (annotation, not enforced by consumers; enforcement stays server-side via FluentValidation).
  • NSwag requires registering the operation processor: settings.OperationProcessors.Add(serviceProvider.GetService<FluentValidationOperationProcessor>()) (see the NSwag sample).
  • Microsoft.AspNetCore.OpenApi: on net10 the file part is emitted as a $ref to a shared IFormFile component, so the size/content-type description is shared across all IFormFile endpoints (differing per-endpoint rules would accumulate there) — but encoding.contentType is per-operation and unaffected.

Extensibility

You can register FluentValidationRule in ServiceCollection.

User defined rule name replaces default rule with the same. Full list of default rules can be get by FluentValidationRules.CreateDefaultRules()

List or default rules:

  • Required
  • NotEmpty
  • Length
  • Pattern
  • Comparison
  • Between

Example of rule:

new FluentValidationRule("Pattern")
{
    Matches = propertyValidator => propertyValidator is IRegularExpressionValidator,
    Apply = context =>
    {
        var regularExpressionValidator = (IRegularExpressionValidator)context.PropertyValidator;
        context.Schema.Properties[context.PropertyKey].Pattern = regularExpressionValidator.Expression;
    }
},

Samples

Swagger Sample model and validator

public class Sample
{
    public string PropertyWithNoRules { get; set; }

    public string NotNull { get; set; }
    public string NotEmpty { get; set; }
    public string EmailAddress { get; set; }
    public string RegexField { get; set; }

    public int ValueInRange { get; set; }
    public int ValueInRangeExclusive { get; set; }

    public float ValueInRangeFloat { get; set; }
    public double ValueInRangeDouble { get; set; }
}

public class SampleValidator : AbstractValidator<Sample>
{
    public SampleValidator()
    {
        RuleFor(sample => sample.NotNull).NotNull();
        RuleFor(sample => sample.NotEmpty).NotEmpty();
        RuleFor(sample => sample.EmailAddress).EmailAddress();
        RuleFor(sample => sample.RegexField).Matches(@"(\d{4})-(\d{2})-(\d{2})");

        RuleFor(sample => sample.ValueInRange).GreaterThanOrEqualTo(5).LessThanOrEqualTo(10);
        RuleFor(sample => sample.ValueInRangeExclusive).GreaterThan(5).LessThan(10);

        // WARNING: Swashbuckle implements minimum and maximim as int so you will loss fraction part of float and double numbers
        RuleFor(sample => sample.ValueInRangeFloat).InclusiveBetween(1.1f, 5.3f);
        RuleFor(sample => sample.ValueInRangeDouble).ExclusiveBetween(2.2, 7.5f);
    }
}

Swagger Sample model screenshot

SwaggerSample

Validator with Include

public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        RuleFor(customer => customer.Surname).NotEmpty();
        RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");

        Include(new CustomerAddressValidator());
    }
}

internal class CustomerAddressValidator : AbstractValidator<Customer>
{
    public CustomerAddressValidator()
    {
        RuleFor(customer => customer.Address).Length(20, 250);
    }
}

Nested objects (SetValidator / ChildRules)

Rules for a nested object are applied to the child component schema, and the parent property keeps a $ref to it. Both a standalone child validator (SetValidator) and inline ChildRules are supported (inline ChildRules $ref preservation was fixed in 7.1.6, see #198):

public class CreateUserRequest
{
    public CreateUserParams User { get; set; }
}

public class CreateUserParams
{
    public string Email { get; set; }
    public string Name { get; set; }
}

public class CreateUserRequestValidator : AbstractValidator<CreateUserRequest>
{
    public CreateUserRequestValidator()
    {
        // Inline child rules — no separate validator class required
        RuleFor(x => x.User)
            .NotEmpty()
            .ChildRules(user =>
            {
                user.RuleFor(u => u.Email).NotEmpty().EmailAddress();
                user.RuleFor(u => u.Name).NotEmpty().MaximumLength(510);
            });

        // Equivalent with a standalone validator:
        // RuleFor(x => x.User).NotEmpty().SetValidator(new CreateUserParamsValidator());
    }
}

The Email/Name constraints (and required) end up on the CreateUserParams component, while the parent stays a reference:

"CreateUserRequest": {
  "required": [ "user" ],
  "type": "object",
  "properties": {
    "user": { "$ref": "#/components/schemas/CreateUserParams" }
  }
}

Get params bounded to validatable models

MicroElements.Swashbuckle.FluentValidation updates swagger schema for operation parameters bounded to validatable models.

Nested [FromQuery] parameters

When a [FromQuery] model has nested objects, ASP.NET Core flattens them into dot-path parameters (e.g. RequiredSubType.SubProperty). The validation rules for such a nested parameter are reflected in the OpenAPI document only when they are actually enforced at runtime:

  • The nested validator must be wired from the root validator via SetValidator/ChildRules (since 7.1.7). FluentValidation never auto-validates a child object just because a validator for it is registered in DI — so an unwired nested validator no longer leaks required/length/pattern constraints onto the parameter.
  • A nested parameter is marked required only when every ancestor segment of the dot-path is required (see #209).

Note: if there is no validator registered for the root [FromQuery] type (only a leaf/child validator), the flattened nested parameter is left unconstrained — matching the default runtime, where no validation runs without a root validator. If you instead validate the child manually in the controller (e.g. new SubValidator().Validate(filter.Child)), those constraints cannot be detected statically and so are not reflected in the schema — register/wire a validator for the root type if you want them documented.

Document filter pipeline (UseDocumentFilter)

Since 7.2.0 the library offers a supported alternative pipeline: a single document filter instead of the default schema + operation filters.

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

The document filter processes the whole document at once — component schemas, operation parameters (including required-marking, #209), request bodies and encoding.contentType (#216) — and performs the unused-query-schema cleanup once at the end.

Why opt in: the default pipeline runs per operation, so a DTO shared between several endpoints (or between [FromQuery] and [FromBody] bindings) used to hit per-operation state bugs (#223, #226); those were fixed, but the net8.0/net9.0 targets keep a narrower guarantee (the healing API only exists in Swashbuckle 10+). Under the document filter this class of issue cannot occur, on any target framework.

Caveats:

  • The default pipeline is unchanged — UseDocumentFilter is opt-in. A future major version may make it the default.
  • On net10.0 the cleanup also clears Swashbuckle's internal reserved-ids for the container types it processed; transitively registered child schemas are not covered. On net8.0/net9.0, a custom IDocumentFilter registered after this one that regenerates a removed [FromQuery] container type may still observe a reserved-but-removed schema state.
  • ExperimentalUseDocumentFilter still works as an obsolete alias and forwards to UseDocumentFilter.

The samples/MinimalApi sample runs on this pipeline.

Defining rules dynamically from database

See BlogValidator in sample.

Common problems and workarounds

Error: System.InvalidOperationException: 'Cannot resolve 'IValidator<T>' from root provider because it requires scoped service 'TDependency'

Workarounds in order or preference:

Workaround 1 (Use HttpContextServiceProviderValidatorFactory) by @WarpSpideR

public void ConfigureServices(IServiceCollection services)
{
    // HttpContextServiceProviderValidatorFactory requires access to HttpContext
    services.AddHttpContextAccessor();

    services
        .AddMvc()
        // Adds fluent validators to Asp.net
        .AddFluentValidation(c =>
        {
            c.RegisterValidatorsFromAssemblyContaining<Startup>();
            // Optionally set validator factory if you have problems with scope resolve inside validators.
            c.ValidatorFactoryType = typeof(HttpContextServiceProviderValidatorFactory);
        });

Workaround 2 (Use ScopedSwaggerMiddleware)

Replace UseSwagger for UseScopedSwagger:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app
        .UseMvc()
        // Use scoped swagger if you have problems with scoped services in validators
        .UseScopedSwagger();

Workaround 3 (Set ValidateScopes to false)

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        // Needed for using scoped services (for example DbContext) in validators
        .UseDefaultServiceProvider(options => options.ValidateScopes = false)
        .UseStartup<Startup>()
        .Build();

Problem: I cant use several validators of one type

Example: You split validator into several small validators but AspNetCore uses only one of them.

Workaround: Hide dependent validators with internal and use Include to include other validation rules to one "Main" validator.

Problem: I'm using FluentValidation or FluentValidation.DependencyInjectionExtensions instead of FluentValidation.AspNetCore

If you are using the more basic FluentValidation or FluentValidation.DependencyInjectionExtensions libraries, then they will not automatically register IValidatorFactory and you will get an error at runtime: "ValidatorFactory is not provided. Please register FluentValidation." In that case you must register it manually (see issue 97 for more details):

services.TryAddTransient<IValidatorFactory, ServiceProviderValidatorFactory>();
services.AddFluentValidationRulesToSwagger();

Problem: Newtonsoft.Json DefaultNamingStrategy, SnakeCaseNamingStrategy does not work


Startup.cs:

    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNamingPolicy = new NewtonsoftJsonNamingPolicy(new SnakeCaseNamingStrategy());
        //options.JsonSerializerOptions.DictionaryKeyPolicy = new NewtonsoftJsonNamingPolicy(new SnakeCaseNamingStrategy());
    })


    /// <summary>
    /// Allows use Newtonsoft <see cref="NamingStrategy"/> as System.Text <see cref="JsonNamingPolicy"/>.
    /// </summary>
    public class NewtonsoftJsonNamingPolicy : JsonNamingPolicy
    {
        private readonly NamingStrategy _namingStrategy;

        /// <summary>
        /// Creates new instance of <see cref="NewtonsoftJsonNamingPolicy"/>.
        /// </summary>
        /// <param name="namingStrategy">Newtonsoft naming strategy.</param>
        public NewtonsoftJsonNamingPolicy(NamingStrategy namingStrategy)
        {
            _namingStrategy = namingStrategy;
        }

        /// <inheritdoc />
        public override string ConvertName(string name)
        {
            return _namingStrategy.GetPropertyName(name, false);
        }
    }

Credits

Initial version of this project was based on Mujahid Daud Khan answer on StackOverflow: https://stackoverflow.com/questions/44638195/fluent-validation-with-swagger-in-asp-net-core/49477995#49477995

$# Changes in 7.2.2

  • Fixed: MicroElements.AspNetCore.OpenApi.FluentValidation did not apply FluentValidation rules to a [FromForm] DTO bound by an MVC controller action (Issue #232, follow-up to the Swashbuckle-only #170 fix)
    • Root cause: MVC ApiExplorer flattens a controller's [FromForm] complex parameter into one form field per property, so Microsoft.AspNetCore.OpenApi builds the request body as an inline schema from those descriptions and never asks for the DTO's JsonTypeInfo — FluentValidationSchemaTransformer therefore never saw the type. Minimal API form bodies are not flattened and were never affected
    • Fix: FluentValidationOperationTransformer now applies the DTO's rules to the inline form schema for both multipart/form-data and application/x-www-form-urlencoded. The flattening is detected via ModelMetadata.ContainerType on the form-bound parameter descriptions, so a minimal API body — which the schema transformer already owns — is never touched twice
    • Property keys are taken verbatim from the schema, because a flattened form field is named after the model binder (Name, not name); the validator-side match stays name-insensitive
    • An action binding more than one form parameter composes its body as an allOf of one property bag per parameter; every bag is now constrained, and each bag is paired with the parameter that owns its fields — including the parameters the action binds directly (a loose scalar, array or IFormFile), which get a bag of their own but no rules. Without that pairing a DTO could be pulled into a foreign bag by a single coinciding field name and lose the constraints on its own bag
    • encoding.contentType (Issue #216) is now also emitted when the file part sits inside such an allOf bag
    • Dotted keys (Inner.City, ApiExplorer's binding path for a nested complex property) are skipped: the name matcher ignores separators, so such a key would otherwise inherit the rules of an unrelated root property named InnerCity. Nested form fields remain unsupported, matching the Swashbuckle form path
    • Issue #216 encoding.contentType is unchanged for the shapes it already covered, and now also consults every validator in multi-validator mode (IsOneValidatorForType = false) instead of only the first
    • Known limitations on this path (unchanged by this fix): a renamed form field ([FromForm(Name = "...")], or a property renamed by the INameResolver) gets no constraints, and on net10 a form part rendered as a $ref (IFormFile, enums) cannot receive property-level constraints
    • Thanks to @bux for the report

Changes in 7.2.1

  • Fixed: the document-filter pipeline (UseDocumentFilter = true) did not apply FluentValidation constraints to aliased operation parameters such as [FromHeader(Name = "X-Correlation-Id")] (Issue #230). The constraint copy now uses the resolved schema property key (kebab-case/camelCase/PascalCase aliases) and 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 ([FromHeader(Name = "X.Trace.Id")] — legal in HTTP) was treated as a nested [FromQuery] dot-path (#209/#211) and silently dropped the header's validation rules. Header-bound parameters are now exempt from the dot-path logic in all three parameter pipelines: FluentValidationOperationFilter, FluentValidationDocumentFilter and the ASP.NET Core FluentValidationOperationTransformer. NSwag is unaffected (it has no dot-path parameter logic)
  • Fixed: the document filter matched document parameters to ApiExplorer descriptions case-sensitively, so options like DescribeAllParametersInCamelCase made it skip required-marking and constraint copying. The lookup is now case-insensitive, matching the operation filter
  • Thanks to @jgarciadelanoceda for the report and the repro

Changes in 7.2.0

  • Added: the document-filter pipeline is promoted from experimental to a supported opt-in (RegistrationOptions.UseDocumentFilter, default false). The document filter processes the whole document at once and performs the unused-query-schema cleanup once at the end, so per-operation shared-DTO state issues (#223/#226) cannot occur in this pipeline — on all target frameworks, including net8.0/net9.0 where the 7.1.11 healing API is unavailable
    • Full functional parity with the default schema + operation filter pipeline: required-marking with the whole-dot-path check (#209), request bodies and encoding.contentType for [FromForm] (#216), multi-validator support, allOf/oneOf/anyOf traversal, $ref preservation for unmodified properties (#198, net10.0), and every operation of a multi-verb path is now processed (previously only the first)
    • The #209 and #216 logic is extracted into internal components shared by both pipelines, so they cannot drift apart
    • Robustness: the filter no longer fails the whole document generation on a throwing validator (top-level try/catch), falls back to ServiceProviderValidatorRegistry like the sibling filters, honors an injected IFluentValidationRuleProvider (new optional constructor parameter, appended last — source-compatible), and had its dead code, logging and nullability issues cleaned up. Behavior note: constructing FluentValidationDocumentFilter with neither validatorRegistry nor serviceProvider now throws ArgumentNullException (matching the sibling filters) instead of silently producing a filter that applies no rules; the DI registration path is unaffected
    • On net10.0 the cleanup also heals Swashbuckle's reserved-ids for processed container types (SchemaRepository.ReplaceSchemaId), so custom document filters running afterwards can regenerate them
    • ExperimentalUseDocumentFilter still works as an [Obsolete] alias forwarding to UseDocumentFilter
    • The default pipeline is unchanged; a future major version may switch the default
    • samples/MinimalApi now runs on the document-filter pipeline; README documents the option and its caveats

Changes in 7.1.11

  • Fixed: a DTO shared between a flattened [FromQuery]/[AsParameters] binding and a request body ([FromBody]/[FromForm]) in the same document could lose its FluentValidation rules, and the emitted document could contain a $ref to a removed component (Issue #226). net10.0 target only
    • Root cause: the Issue #180 cleanup left the container type "reserved-but-removed" in Swashbuckle's SchemaRepository (component removed, internal reserved-id kept). The 7.1.10 fix (#223) recovered the schema for reading constraint values, but a later [FromBody]/[FromForm] operation binding the same type made Swashbuckle emit a $ref to a component that no longer exists (confirmed empirically), and rules applied on the recovered throwaway instance never reached the document
    • Fix (net10.0 / Swashbuckle 10.x): the Issue #180 cleanup now heals the repository state via 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. SwashbuckleSchemaProvider tracks Type → schemaId for every GetSchemaForType call (including the Issue #209 ancestor walk) to make the healing possible
    • net8.0/net9.0 keep the 7.1.10 behavior: they pin Swashbuckle 8.1.1 where ReplaceSchemaId does not exist, and bumping the minimum would force the Microsoft.OpenApi 2.x breaking change on consumers. The throwaway recovery from #223 remains as a safety net on all targets
    • Thanks to @jgarciadelanoceda for suggesting the ReplaceSchemaId approach

Changes in 7.1.10

  • Fixed: the same [FromQuery]/[AsParameters] DTO shared by more than one endpoint lost its FluentValidation rules on every endpoint after the first (Issue #223)
    • FluentValidationOperationFilter runs once per operation. The Issue #180 cleanup removes the temporary container schema from SchemaRepository.Schemas, but Swashbuckle keeps the type in its internal reserved-ids map, which the cleanup does not touch. For the 2nd+ endpoint, GetSchemaForType → GenerateSchema then returns a bare $ref (no Properties) and the schema is no longer in Schemas, so the "has properties" guard was skipped and no rules were applied. Only reproduces with the default RemoveUnusedQuerySchemas = true
    • Fix: in SwashbuckleSchemaProvider.GetSchemaForType, when the returned schema has no properties and the id is absent from the shared repository (the reserved-but-removed state), the concrete schema is recovered by generating it into a throwaway SchemaRepository. Fully isolated — it never mutates the shared repository or its reserved-id state — so the Issue #180 cleanup and all other OperationFilter behavior (required marking #209, nested [FromQuery] #211/#213, request bodies, #216) are preserved

Full release notes can be found at: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/blob/master/CHANGELOG.md

  • .NETFramework 10.0
    • MicroElements.OpenApi.FluentValidation (>= 7.2.2)
    • Swashbuckle.AspNetCore.SwaggerGen (>= 10.2.1)
  • .NETFramework 8.0
    • MicroElements.OpenApi.FluentValidation (>= 7.2.2)
    • Swashbuckle.AspNetCore.SwaggerGen (>= 8.1.1)
  • .NETFramework 9.0
    • MicroElements.OpenApi.FluentValidation (>= 7.2.2)
    • Swashbuckle.AspNetCore.SwaggerGen (>= 8.1.1)
  • .NETFramework 8.0: 8.0.0.0
  • .NETFramework 9.0: 9.0.0.0
  • .NETFramework 10.0: 10.0.0.0

Owners

avgalex Alexey Petryashev

Authors

alexey.petriashev, MicroElements

Project URL

https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation

License

MIT

Tags

swagger swashbuckle FluentValidation aspnetcore

Info

7980 total downloads
6 downloads for version 7.2.2
Download (177.61 KB)
Found on the current feed only

Package history

Version Size Last updated Downloads Mirrored?
7.2.2 177.61 KB Fri, 21 Aug 2026 06:19:46 GMT 6
7.2.1 177.17 KB Mon, 13 Jul 2026 11:11:13 GMT 7
7.2.0 176.68 KB Sun, 12 Jul 2026 20:40:45 GMT 6
7.1.11 169.17 KB Sun, 12 Jul 2026 12:36:00 GMT 6
7.1.10 168.64 KB Sun, 12 Jul 2026 10:16:22 GMT 4
7.1.9 168.96 KB Thu, 09 Jul 2026 07:20:36 GMT 7
7.1.8 169.4 KB Wed, 01 Jul 2026 19:15:28 GMT 14
7.1.8-beta.2 169.32 KB Sun, 21 Jun 2026 10:40:51 GMT 18
7.1.8-beta.1 169.38 KB Sat, 20 Jun 2026 17:47:11 GMT 12
7.1.7 163.88 KB Sat, 20 Jun 2026 12:54:05 GMT 16
7.1.7-beta.3 163.93 KB Wed, 17 Jun 2026 20:24:30 GMT 12
7.1.7-beta.2 162.41 KB Wed, 17 Jun 2026 13:00:49 GMT 21
7.1.7-beta.1 160.47 KB Mon, 15 Jun 2026 17:28:37 GMT 23
7.1.6 156.55 KB Tue, 02 Jun 2026 16:35:35 GMT 14
7.1.6-beta.1 156.11 KB Tue, 02 Jun 2026 13:24:52 GMT 16
7.1.5-beta.2 156.34 KB Sat, 16 May 2026 12:15:37 GMT 15
7.1.5-beta 155.83 KB Wed, 01 Apr 2026 12:10:42 GMT 23
7.1.4 155.87 KB Sun, 29 Mar 2026 16:07:39 GMT 27
7.1.4-beta 155.89 KB Tue, 24 Mar 2026 12:10:43 GMT 37
7.1.3 155.65 KB Tue, 24 Mar 2026 08:01:03 GMT 28
7.1.2 154.2 KB Mon, 09 Mar 2026 21:49:02 GMT 27
7.1.1 152.61 KB Mon, 09 Mar 2026 19:22:05 GMT 25
7.1.0 141.87 KB Mon, 09 Mar 2026 15:09:15 GMT 20
7.1.0-beta.2 141.94 KB Fri, 06 Mar 2026 08:23:00 GMT 26
7.1.0-beta.1 136.6 KB Thu, 26 Feb 2026 20:30:45 GMT 27
7.0.6 141.71 KB Tue, 03 Mar 2026 13:52:39 GMT 31
7.0.5 136.26 KB Thu, 26 Feb 2026 20:33:55 GMT 22
7.0.4 136.07 KB Tue, 17 Feb 2026 17:08:59 GMT 29
7.0.4-beta.3 136.35 KB Sat, 14 Feb 2026 15:10:05 GMT 27
7.0.4-beta.2 136.32 KB Sat, 14 Feb 2026 14:49:36 GMT 25
7.0.4-beta.1 136.06 KB Sat, 14 Feb 2026 13:15:33 GMT 27
7.0.3 135.83 KB Sat, 14 Feb 2026 13:04:04 GMT 26
7.0.2 134.4 KB Tue, 23 Dec 2025 10:43:03 GMT 26
7.0.1 134.52 KB Mon, 08 Dec 2025 07:47:11 GMT 33
7.0.0 130.87 KB Sun, 07 Dec 2025 19:21:58 GMT 47
6.1.1 83.14 KB Mon, 01 Sep 2025 08:29:13 GMT 100
6.1.0 83.15 KB Mon, 01 Sep 2025 07:36:07 GMT 99
6.0.0 38.37 KB Sat, 23 Dec 2023 19:44:23 GMT 103
6.0.0-beta.3 38.37 KB Mon, 01 May 2023 13:55:43 GMT 99
6.0.0-beta.2 38.7 KB Sun, 12 Feb 2023 20:28:39 GMT 98
6.0.0-beta.1 39.05 KB Sat, 23 Jul 2022 20:03:41 GMT 115
5.7.0 43.32 KB Fri, 10 Jun 2022 10:38:09 GMT 100
5.6.0 43.32 KB Fri, 10 Jun 2022 10:24:11 GMT 103
5.5.0 43.38 KB Sun, 01 May 2022 19:40:08 GMT 94
5.4.0 43.58 KB Mon, 28 Mar 2022 18:35:16 GMT 113
5.3.0 43.57 KB Sun, 19 Sep 2021 15:22:18 GMT 98
5.2.0 43.48 KB Sat, 31 Jul 2021 14:50:34 GMT 99
5.1.0 43.46 KB Thu, 17 Jun 2021 15:59:54 GMT 109
5.1.0-rc.2 43.47 KB Thu, 03 Jun 2021 20:35:12 GMT 113
5.1.0-rc.1 42.49 KB Tue, 01 Jun 2021 08:14:50 GMT 103
5.0.0 41.65 KB Sun, 30 May 2021 09:25:09 GMT 103
5.0.0-rc.2 41.67 KB Tue, 18 May 2021 14:25:52 GMT 99
5.0.0-rc.1 41.64 KB Tue, 18 May 2021 10:35:39 GMT 99
4.3.0 28.73 KB Sun, 02 May 2021 18:37:25 GMT 109
4.3.0-rc.1 29.02 KB Thu, 25 Mar 2021 16:20:24 GMT 100
4.2.0 28.92 KB Sun, 21 Mar 2021 14:21:00 GMT 99
4.1.0 26.27 KB Thu, 18 Feb 2021 19:18:07 GMT 109
4.1.0-rc.1 26.28 KB Fri, 08 Jan 2021 14:04:28 GMT 104
4.0.0 23.81 KB Fri, 01 Jan 2021 18:15:58 GMT 100
4.0.0-rc.2 22.58 KB Sat, 18 Jul 2020 11:34:07 GMT 96
4.0.0-rc.1 22.91 KB Mon, 15 Jun 2020 07:44:40 GMT 124
3.2.0 22.43 KB Thu, 16 Jul 2020 08:55:46 GMT 102
3.1.1 21.06 KB Tue, 28 Apr 2020 19:50:38 GMT 102
3.1.0 21.08 KB Tue, 28 Apr 2020 19:44:12 GMT 94
3.0.0 21 KB Fri, 06 Mar 2020 18:33:53 GMT 108
3.0.0-rc.6 20.78 KB Wed, 05 Feb 2020 19:14:35 GMT 106
3.0.0-rc.5 20.83 KB Fri, 24 Jan 2020 16:14:36 GMT 114
3.0.0-rc.4 19.93 KB Sun, 29 Dec 2019 21:20:26 GMT 101
3.0.0-rc.3 18.63 KB Thu, 28 Nov 2019 20:13:09 GMT 107
3.0.0-rc.2 18.46 KB Sun, 13 Oct 2019 19:01:50 GMT 97
3.0.0-rc.1 18.29 KB Mon, 30 Sep 2019 21:32:44 GMT 98
3.0.0-beta.1 18.08 KB Thu, 25 Apr 2019 21:47:21 GMT 115
3.0.0-alpha.1 17.97 KB Tue, 23 Apr 2019 20:36:02 GMT 103
2.3.0 18.92 KB Thu, 14 Nov 2019 20:19:50 GMT 107
2.2.1 18.67 KB Thu, 14 Nov 2019 19:23:42 GMT 111
2.2.0 18.3 KB Wed, 28 Aug 2019 20:28:53 GMT 113
2.1.1 17.83 KB Thu, 25 Apr 2019 21:48:11 GMT 102
2.1.0 17.73 KB Mon, 08 Apr 2019 12:24:34 GMT 94
2.0.1 17.79 KB Mon, 08 Apr 2019 12:06:38 GMT 94
2.0.0 16.5 KB Wed, 13 Mar 2019 19:08:14 GMT 125
2.0.0-beta.5 16.29 KB Sun, 24 Feb 2019 07:31:07 GMT 98
2.0.0-beta.4 16.31 KB Wed, 13 Feb 2019 20:13:52 GMT 96
2.0.0-beta.3 16.36 KB Mon, 11 Feb 2019 20:23:30 GMT 107
2.0.0-beta.2 16.81 KB Mon, 21 Jan 2019 20:37:24 GMT 95
2.0.0-beta.1 16.76 KB Mon, 12 Nov 2018 20:40:04 GMT 114
1.2.0 16.09 KB Tue, 22 Jan 2019 07:33:31 GMT 99
1.1.0 16.1 KB Mon, 21 Jan 2019 22:00:43 GMT 106
1.0.0 16.5 KB Wed, 26 Sep 2018 20:08:53 GMT 119
1.0.0-rc.1 16.52 KB Fri, 21 Sep 2018 20:37:01 GMT 106
1.0.0-beta.3 16.5 KB Wed, 19 Sep 2018 19:48:11 GMT 113
1.0.0-beta.2 15.93 KB Mon, 10 Sep 2018 16:27:12 GMT 102
1.0.0-beta.1 14.27 KB Fri, 24 Aug 2018 17:32:30 GMT 104
0.8.2 14.05 KB Fri, 29 Jun 2018 09:29:45 GMT 91
0.8.1 14.02 KB Wed, 20 Jun 2018 23:33:00 GMT 95
0.8.0 13.96 KB Tue, 12 Jun 2018 22:26:34 GMT 106
0.8.0-beta.3 13.97 KB Tue, 12 Jun 2018 22:21:17 GMT 102
0.8.0-beta.2 13.83 KB Mon, 11 Jun 2018 10:57:57 GMT 93
0.8.0-beta.1 13.24 KB Fri, 11 May 2018 10:39:26 GMT 107
0.7.0 13.1 KB Fri, 11 May 2018 10:24:33 GMT 103
0.6.0 10.27 KB Wed, 04 Apr 2018 19:32:20 GMT 109
0.5.0 10.24 KB Fri, 30 Mar 2018 20:04:37 GMT 96
0.4.0 9.81 KB Thu, 29 Mar 2018 23:07:36 GMT 104
0.3.0 8.46 KB Thu, 29 Mar 2018 21:28:37 GMT 99
0.2.0 6.73 KB Sun, 25 Mar 2018 15:14:15 GMT 106
0.0.2 6.73 KB Fri, 23 Mar 2018 20:19:30 GMT 99