FastValidate 1.1.0

dotnet add package FastValidate --version 1.1.0
                    
NuGet\Install-Package FastValidate -Version 1.1.0
                    
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="FastValidate" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FastValidate" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="FastValidate" />
                    
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 FastValidate --version 1.1.0
                    
#r "nuget: FastValidate, 1.1.0"
                    
#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 FastValidate@1.1.0
                    
#: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=FastValidate&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=FastValidate&version=1.1.0
                    
Install as a Cake Tool

FastValidate

NuGet CI .NET License: MIT

Zero-reflection validation for .NET, powered by a Roslyn source generator.

Decorate your properties with validation attributes and FastValidate generates a compile-time Validate() extension method for you — no reflection, no runtime attribute scanning. That makes it safe for Native AOT and trimmed apps, where reflection-based validators typically break or need extra trimmer annotations.

Why

Most attribute-based validators (including reflection-driven ones) walk a type's properties at runtime with GetProperties() / GetCustomAttributes(). That's slow, allocates, and is exactly the kind of code Native AOT trimming can strip or fail to resolve. FastValidate moves that work to compile time: the generator reads your attributes once, at build, and emits plain, boring, fast C# that calls straight into the validation logic. Nothing to reflect over at runtime.

Measured against a reflection-based equivalent (FastValidate.Benchmarks, BenchmarkDotNet, Apple M3 Pro / .NET 10, 4 validated properties):

Mean Allocated
Reflection-based EntityValidator.ValidateEntity<T>() 2,028.6 ns 952 B
FastValidate entity.Validate() 578.4 ns 192 B

~3.5x faster, ~5x less memory allocated. Run it yourself: dotnet run -c Release --project FastValidate.Benchmarks.

Installation

dotnet add package FastValidate

That's it — one package. It contains both the attributes/validator library and the source generator.

Usage

using FastValidate;

public class SignupRequest
{
    [EmailValidation]
    public string Email { get; set; } = string.Empty;

    [PhoneNumberValidation(Region.Spain)]
    public string Phone { get; set; } = string.Empty;

    [PasswordValidation(MinLength = 10, RequireSpecialChar = true)]
    public string Password { get; set; } = string.Empty;

    [LengthValidation(2, 50)]
    public string DisplayName { get; set; } = string.Empty;
}

No base class, no partial keyword, no marker attribute on the class itself — just decorate the properties you want validated. At build time, FastValidate generates a Validate() extension method for SignupRequest:

var request = new SignupRequest { Email = "not-an-email", Phone = "612345678", Password = "weak", DisplayName = "A" };

List<ValidationResult> errors = request.Validate();
// errors: one entry per failing property (PropertyName + ErrorMessage),
// empty when everything is valid. Every property is checked — it doesn't
// stop at the first failure.

foreach (var error in errors)
    Console.WriteLine($"{error.PropertyName}: {error.ErrorMessage}");

You can inspect the generated code yourself: enable <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> in your .csproj and look under obj/generated.

Attributes

Attribute Property type Notes
[EmailValidation] string
[PhoneNumberValidation(Region)] string Region.USA, .UK, .Spain, .Ireland
[PostalCodeValidation(Region)] string Same regions as phone
[DateValidation] string Parsable date
[UrlValidation] string Absolute http/https URLs
[CreditCardValidation] string Luhn checksum
[PasswordValidation] string MinLength, RequireUppercase, RequireLowercase, RequireDigit, RequireSpecialChar (named args, all optional)
[IPAddressValidation] string IPv4 or IPv6
[RangeValidation(min, max)] any numeric Inclusive range
[LengthValidation(min, max)] string Inclusive length range
[RegexValidation("pattern")] string Pattern embedded at compile time — no runtime registration
[CustomValidation("name")] string Pattern registered at runtime via Validator.AddCustomValidation — see below
[EqualsValidation(nameof(Other))] any Must equal another property on the same type (e.g. confirm password)
[ValidateObject] a type with its own FastValidate attributes Recurses, prefixing nested errors (Address.PostalCode)
[ValidateCollection] IEnumerable<T> of such a type Recurses per item, prefixing with the index (Items[2].Sku)

Applying an attribute to an incompatible property type (e.g. [EmailValidation] on an int) is a compile error (FV001), not a silent no-op or a runtime surprise. [EqualsValidation] referencing a property that doesn't exist, or one with a different type, is likewise a compile error (FV002).

Custom rules

Prefer [RegexValidation] when the pattern is a compile-time constant — it needs no setup:

public class Product
{
    [RegexValidation(@"^[A-Z]{3}-\d{4}$")]
    public string Code { get; set; } = string.Empty;
}

Use [CustomValidation] when the pattern needs to be registered at startup (e.g. loaded from configuration):

Validator.AddCustomValidation("product-code", @"^[A-Z]{3}-\d{4}$");

public class Product
{
    [CustomValidation("product-code")]
    public string Code { get; set; } = string.Empty;
}

Cross-property and nested validation

public class AccountCreation
{
    [PasswordValidation]
    public string Password { get; set; } = string.Empty;

    [EqualsValidation(nameof(Password))]
    public string ConfirmPassword { get; set; } = string.Empty;
}

public class AddressInfo
{
    [PostalCodeValidation(Region.Spain)]
    public string PostalCode { get; set; } = string.Empty;
}

public class CustomerProfile
{
    [EmailValidation]
    public string Email { get; set; } = string.Empty;

    [ValidateObject]
    public AddressInfo Address { get; set; } = new();

    [ValidateCollection]
    public List<AddressInfo> ShippingAddresses { get; set; } = new();
}

// customerProfile.Validate() reports errors as "Address.PostalCode",
// "ShippingAddresses[1].PostalCode", etc. — one Validate() call, full graph.

Calling validators directly

Every attribute is backed by a plain static method on Validator, callable without any generated code:

ValidationResult result = Validator.IsValidEmail("user@example.com");
if (!result.IsValid)
    Console.WriteLine(result.ErrorMessage);

Requirements

  • Any SDK-style .NET project (the generator targets netstandard2.0, so it works from .NET Framework 4.6.1+ through the latest .NET)

Development

git clone https://github.com/AdrianBailador/FastValidate.git
cd FastValidate
dotnet test

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

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.1.0 91 8/15/2026
1.0.0 104 8/15/2026

Added DateValidation, RegexValidation (compile-time inline pattern), EqualsValidation (cross-property, e.g. confirm password, with FV002 diagnostic), ValidateObject and ValidateCollection (nested/recursive validation with prefixed property names). Added a BenchmarkDotNet comparison against reflection-based validation (~3.5x faster, ~5x less allocation).