Skip to content

Introduction

Compile-time generated validation for your requests, with no runtime rule registration.

2 min read
Immediate.Validations GitHub repository Immediate.Validations NuGet version Immediate.Validations latest GitHub release Immediate.Validations license

Immediate.Validations generates validation code at compile time. You annotate a type with [Validate], declare that it implements IValidationTarget<T>, and decorate its properties with validator attributes; the source generator emits a Validate method that runs those checks in order and returns a ValidationResult. There is no runtime rule registry or expression-tree traversal, and nothing to register in the container. Custom-validator metadata such as DefaultMessage may still be accessed reflectively. Mistakes — a missing attribute, a validator applied to a type it cannot handle, a behavior pipeline that forgot validation — are reported as compiler diagnostics rather than as surprises in production.

Installation

dotnet add package Immediate.Validations

A minimal example

GetUserQuery.cs
using Immediate.Validations.Shared;

[Validate]
public sealed partial record Query : IValidationTarget<Query>
{
	[GreaterThan(0)]
	public required int Id { get; init; }

	[NotEmpty]
	public required string Name { get; init; }
}
var result = Query.Validate(new Query { Id = 0, Name = "" });

// result.IsValid == false
// result.Errors[0] => { PropertyName = "Id",   ErrorMessage = "'Id' must be greater than '0'." }
// result.Errors[1] => { PropertyName = "Name", ErrorMessage = "'Name' must not be empty." }

The type must be partial — the generator writes the other half.

What it replaces

If you are coming from FluentValidation, the mental shift is that rules live on the type as attributes rather than in a separate AbstractValidator<T> class, and they are resolved by the compiler rather than by assembly scanning. Anything attributes cannot express goes in an AdditionalValidations method on the type itself, which gives you the same imperative freedom without a second class.

If you are coming from System.ComponentModel.DataAnnotations, the surface will look familiar, but validation is executed by generated code instead of Validator.TryValidateObject’s reflection-based traversal, and nested objects and collections are recursed into automatically. The generated validation path supports trimming and AOT; preserve any reflectively accessed custom-validator metadata when trimming.

Where to go next