ValidationModules.AspNetCore 1.0.0-rc1012

This is a prerelease version of ValidationModules.AspNetCore.
dotnet add package ValidationModules.AspNetCore --version 1.0.0-rc1012
                    
NuGet\Install-Package ValidationModules.AspNetCore -Version 1.0.0-rc1012
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ValidationModules.AspNetCore" Version="1.0.0-rc1012" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ValidationModules.AspNetCore" Version="1.0.0-rc1012" />
                    
Directory.Packages.props
<PackageReference Include="ValidationModules.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ValidationModules.AspNetCore --version 1.0.0-rc1012
                    
#r "nuget: ValidationModules.AspNetCore, 1.0.0-rc1012"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ValidationModules.AspNetCore@1.0.0-rc1012
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ValidationModules.AspNetCore&version=1.0.0-rc1012&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ValidationModules.AspNetCore&version=1.0.0-rc1012&prerelease
                    
Install as a Cake Tool

<picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ipjohnson/ValidationModules/main/assets/logo-dark.svg"><img src="https://raw.githubusercontent.com/ipjohnson/ValidationModules/main/assets/logo.svg" alt="" width="32" height="32"></picture> ValidationModules

NuGet build coverage License: MIT

Compile-time validation for .NET. Attributes on your models become straight-line C# at build time, so a validation pass is a handful of if statements: ~32 ns and 56 B for a clean pass over a flat model — 23 ns and 0 B on the boolean fast path — where FluentValidation takes 179 ns and 664 B for the same work. No reflection, no expression trees, no regex compiled at runtime, and a Native AOT publish is verified on every release, not just supported.

Install

dotnet add package ValidationModules.Runtime
dotnet add package ValidationModules.SourceGenerator

Web apps also want the ASP.NET Core integration:

dotnet add package ValidationModules.AspNetCore

Requires .NET 8.0 or later. The packages ship both net8.0 and net10.0 assemblies, so a project on either LTS release gets one built against its own framework.

Declare, then validate

public record Pet {
    [Required]
    [StringLength(min: 1, max: 100)]
    public string Name { get; init; }

    [Pattern("^[a-zA-Z0-9-]*$")]
    public string? Sku { get; init; }

    [ValidateNested]
    public Address? Home { get; init; }

    [ItemCount(min: 1, max: 10), ValidateNested]
    public IReadOnlyList<Toy> Toys { get; init; } = [];
}

The generator emits a validator per type and one registration call for the assembly:

services.AddMyAppValidators();                 // named after your assembly

var validator = provider.GetRequiredService<IValidatorFor<Pet>>();
var result = validator.Validate(pet);

foreach (var error in result.Errors) {
    Console.WriteLine($"{error.Field}: {error.Code}");
}
// name             required
// home.postalCode  required
// toys[3].name     required

Rules as code

For the type you cannot edit, and the rule that is not a per-property fact, write a rules class — full C#, read at build time and never run:

public sealed class OrderRules : IValidationRulesFor<Order> {
    public static void Describe(ValidationRules<Order> rules, Order x) {
        rules.Require(x.Number).Length(4, 12);

        if (x.International) {
            rules.Require(x.CustomsCode);
        }

        var total = x.Lines?.Sum(l => l.Price * l.Qty) ?? 0m;
        rules.Ensure(total <= x.CreditLimit);   // message: "total <= creditLimit."
    }
}

Locals, if/else and helpers transcribe into the generated validator and run there; vocabulary calls expand into the same checks the attributes produce. The declaration layer — attributes and rules classes alike — is build-time-only: what ships is generated validators plus the small reporting runtime, and under trimming or Native AOT a rules class disappears entirely. See Rule classes.

Performance

Scenario ValidationModules FluentValidation DataAnnotations
Clean pass, flat model 32 ns · 56 B 179 ns · 664 B 958 ns · 2,696 B
Clean pass, boolean fast path¹ 23 ns · 0 B
Five failures, flat model 169 ns · 1,072 B 2,404 ns · 9,904 B 1,582 ns · 4,136 B
Clean pass, nested graph 110 ns · 56 B 1,817 ns · 5,224 B 581 ns · top level only²
1,000-element collection 15.4 µs · 56 B 236 µs · 826 KB

Measured with BenchmarkDotNet (Apple M3 Pro, .NET 10.0.10, FluentValidation 12.1.1) on identical models carrying the same rules; every cross-engine row is a full pass that materializes a result on both sides. Allocations are counted, not timed, and are exact — the 56 B is the result object, and the pass itself allocates nothing at any nesting depth or collection size. ¹The generated IsValid returns at the first failure and builds no report; it is measured on its own row because neither competitor has a boolean-only API to pair it with. ²DataAnnotations does not descend into nested objects or collection elements. A parity check refuses to run the suite unless all three engines find the same failure counts, FluentValidation runs with CascadeMode.Stop and the same [GeneratedRegex] instances as the generated code, and every validator is constructed once in setup. Full tables with error terms are in benchmarks/RESULTS.md, the methodology in benchmarks/README.md; reproduce with ./scripts/benchmark.sh --comparative.

Native AOT

ValidationModules.Runtime carries IsAotCompatible and escalates the trim/AOT warnings (IL2026, IL3050, and friends) to errors, so the compiler enforces the constraint. scripts/verify-aot.sh then publishes a real AOT binary and runs it — paths, nested descents, error codes, the ASP.NET Core filter — on every release. There is no separate AOT mode, because there is no runtime code generation for AOT to take away.

ASP.NET Core

builder.Services.AddMyAppValidators();

app.MapPost("/orders", (CreateOrder order) => Results.Ok())
   .Validate<CreateOrder>();

A failure answers with RFC 9457 before the handler runs, carrying the field paths and stable codes. The type argument is named rather than inferred, which keeps the request path free of reflection — website/guide/aspnetcore.md covers why, and what the response looks like.

Documentation

The docs site publishes to https://ipjohnson.github.io/ValidationModules/ and lives in website/:

cd website && npm install && npm run dev

Design records, for the reasoning behind the surface:

  • IMPLEMENTATION-PLAN.md — what is being built and why. A specification, not a discussion document.
  • API-SURFACE.md — the exact public surface and the verification log behind the claims.

Packages

Package Ships as Referenced by
ValidationModules.Runtime lib/ application code
ValidationModules.SourceGenerator analyzers/dotnet/cs application code, PrivateAssets=all
ValidationModules.AspNetCore lib/ web applications
ValidationModules.SourceGenerator.Impl source-only framework authors
ValidationModules.FluentValidation lib/ planned adapter
ValidationModules.Testing lib/ planned conformance suite

ValidationModules.Runtime depends only on Microsoft.Extensions.DependencyInjection.Abstractions, framework-matched per TFM. It does not reference DependencyModules.Runtime — only the generated module needs DM types, and that lands in the consumer's assembly, which already references DM.

Status

Release candidate for 1.0.0. Built so far:

Stage
1 Runtime — contracts, context, error model, constraint attributes, naming done
2 Generator, no profiles done
3 Profiles deferred past 1.0.0
4 Impl packaging for framework authors done
5 Hardened integration substantially done
6 FluentValidation adapter and conformance suite not started
ASP.NET Core integration done

Also built since the plan was written: the rules-class front end (IValidationRulesFor<T> — redesigned 2026-08-29 to the read-never-run transcription model, docs/active-rules-redesign.md) and a DataAnnotations front end (§18). Profiles and overlays are deferred past 1.0.0 with their declaration surfaces withdrawn; docs/deferred-features.md records how each returns additively.

Building

dotnet build --configuration Release
dotnet test  --configuration Release

The public API is pinned by a snapshot at tests/ValidationModules.Runtime.Tests/Snapshots/PublicApiTests.RuntimeApi.verified.txt — also the quickest way to read the surface. Accept an intended change with:

UPDATE_SNAPSHOTS=1 dotnet test tests/ValidationModules.Runtime.Tests

License

MIT.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-rc1012 0 8/29/2026
1.0.0-rc1011 0 8/29/2026
1.0.0-rc1010 0 8/29/2026
1.0.0-rc1009 19 8/28/2026
1.0.0-rc1008 37 8/28/2026
1.0.0-rc1007 61 8/27/2026
1.0.0-rc1006 61 8/16/2026
1.0.0-rc1005 62 8/16/2026
1.0.0-rc1004 54 8/16/2026