ValidationModules.Runtime
0.1.0-alpha.1
See the version list below for details.
dotnet add package ValidationModules.Runtime --version 0.1.0-alpha.1
NuGet\Install-Package ValidationModules.Runtime -Version 0.1.0-alpha.1
<PackageReference Include="ValidationModules.Runtime" Version="0.1.0-alpha.1" />
<PackageVersion Include="ValidationModules.Runtime" Version="0.1.0-alpha.1" />
<PackageReference Include="ValidationModules.Runtime" />
paket add ValidationModules.Runtime --version 0.1.0-alpha.1
#r "nuget: ValidationModules.Runtime, 0.1.0-alpha.1"
#:package ValidationModules.Runtime@0.1.0-alpha.1
#addin nuget:?package=ValidationModules.Runtime&version=0.1.0-alpha.1&prerelease
#tool nuget:?package=ValidationModules.Runtime&version=0.1.0-alpha.1&prerelease
ValidationModules
Compile-time validation for .NET. Rules are declared as attributes and flattened into straight-line C# by a source generator at build time. No reflection, no expression trees, no regex compiled at runtime — Native AOT is a hard requirement rather than a supported configuration.
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; } = [];
}
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
Why
FluentValidation compiles expression trees at runtime. Under Native AOT Expression.Compile()
falls back to the LINQ interpreter rather than throwing, so it works — but property access is
interpreted and you carry IL2026/IL3050 trim warnings. For a workload where AOT is a requirement,
that is the gap this fills.
ValidationModules.Runtime carries IsAotCompatible and escalates IL2026;IL2055;IL2067;IL2072; IL2075;IL2087;IL3050 to errors, so the constraint is enforced by the compiler rather than by
review.
Status
Pre-1.0, and under construction. Built so far:
| Stage | ||
|---|---|---|
| 1 | Runtime — contracts, context, error model, constraint attributes, naming | done |
| 2 | Generator, no profiles | done |
| 3 | Profiles | not started |
| 4 | Impl packaging for framework authors |
done |
| 5 | Hardened integration | substantially done |
| 6 | FluentValidation adapter and conformance suite | not started |
Also built since the plan was written, and not in its staging: a declarative rule-class front end
(IValidationRulesFor<T>, API-SURFACE.md §19) and a DataAnnotations front end (§18).
Two known gaps remain:
- Profiles are not built, and their declaration surface shipped ahead of them —
FromProfile/UntilProfile/Profileson every constraint, andIValidationProfilein the runtime. Using one isVM0019, an error, because the arguments are ignored rather than inert: a rule written to apply only from V2 would be enforced under V1 as well. Removing that diagnostic is the first thing Stage 3 does. - VM0007 is declared and never reported.
[ValidateNested]on a type with no rules of its own descends into nothing and says nothing.DiagnosticCatalogueTestsrecords it as the one dead descriptor and fails in both directions.
Documentation
The docs site lives in website/ and publishes to
https://ipjohnson.github.io/ValidationModules/:
cd website && npm install && npm run dev
Dead internal links fail the build, so a rename cannot rot a link silently.
Design
IMPLEMENTATION-PLAN.md— what is being built and why. A specification, not a discussion document.API-SURFACE.md— the exact public surface, the reasoning behind each decision, and the verification log behind the claims.
The single most consequential decision is in API-SURFACE.md §13.1: ValidationContext is a
readonly struct rather than a ref struct, carrying its own path rather than indexing into shared
storage. That is what lets IAsyncValidatorFor<T> take the same context as the synchronous side,
and what makes a context safe to hold across an await or hand to a concurrent branch.
Packages
| Package | Ships as | Referenced by |
|---|---|---|
ValidationModules.Runtime |
lib/ |
application code |
ValidationModules.SourceGenerator |
analyzers/dotnet/cs |
application code, PrivateAssets=all |
ValidationModules.SourceGenerator.Impl |
source-only | framework authors |
ValidationModules.FluentValidation |
lib/ |
optional adapter |
ValidationModules.Testing |
lib/ |
conformance suite |
ValidationModules.Runtime depends only on Microsoft.Extensions.DependencyInjection.Abstractions,
framework-matched per TFM. It does not reference DependencyModules.Runtime — the library is
DependencyModules-shaped in its ergonomics, but only the generated module needs DM types, and that
lands in the consumer's assembly, which already references DM.
Building
dotnet build --configuration Release
dotnet test --configuration Release
dotnet test --configuration Release --collect:"XPlat Code Coverage" --settings coverlet.runsettings
The public API is pinned by a snapshot at
tests/ValidationModules.Runtime.Tests/Snapshots/PublicApiTests.RuntimeApi.verified.txt — one file
listing every public type and member, which is also the quickest way to read the surface. To accept
an intended change:
UPDATE_SNAPSHOTS=1 dotnet test tests/ValidationModules.Runtime.Tests
Benchmarks
./scripts/benchmark.sh # ValidationModules alone, JIT and Native AOT
./scripts/benchmark.sh --quick # the same, fast enough to run after a change
./scripts/benchmark.sh --comparative # against FluentValidation and DataAnnotations
Two suites. The default one measures this library on its own and is what a change should be checked
against; the comparative one is opt-in, because its numbers move when FluentValidation changes as
well as when this does. benchmarks/README.md covers what each measures, and the four choices the
comparative suite makes in FluentValidation's favour so the comparison stays honest.
License
MIT.
| Product | Versions 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. |
-
net10.0
-
net8.0
NuGet packages (2)
Showing the top 2 NuGet packages that depend on ValidationModules.Runtime:
| Package | Downloads |
|---|---|
|
Hardened.Requests.Runtime
Execution pipeline implementation for Hardened: filters, serialization, parameter validation and error handling. |
|
|
ValidationModules.AspNetCore
ASP.NET Core integration for ValidationModules: an endpoint filter that validates minimal API arguments before the handler runs, an RFC 9457 ProblemDetails mapping for ValidationResult, and an exception handler for ValidationException. No reflection, Native AOT safe. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-rc1006 | 162 | 8/16/2026 |
| 1.0.0-rc1005 | 54 | 8/16/2026 |
| 1.0.0-rc1004 | 52 | 8/16/2026 |
| 1.0.0-rc1003 | 188 | 8/14/2026 |
| 1.0.0-rc1002 | 55 | 8/14/2026 |
| 0.1.0-alpha.1 | 106 | 8/13/2026 |