Pitasoft.Validation
6.0.5
dotnet add package Pitasoft.Validation --version 6.0.5
NuGet\Install-Package Pitasoft.Validation -Version 6.0.5
<PackageReference Include="Pitasoft.Validation" Version="6.0.5" />
<PackageVersion Include="Pitasoft.Validation" Version="6.0.5" />
<PackageReference Include="Pitasoft.Validation" />
paket add Pitasoft.Validation --version 6.0.5
#r "nuget: Pitasoft.Validation, 6.0.5"
#:package Pitasoft.Validation@6.0.5
#addin nuget:?package=Pitasoft.Validation&version=6.0.5
#tool nuget:?package=Pitasoft.Validation&version=6.0.5
Pitasoft.Validation
Pitasoft.Validation is a flexible and high-performance .NET validation library built on top of Pitasoft.Error.
It supports Data Annotations, fluent rules, custom checkers, nested validators, collection validation, and mixed sync/async validation flows.
English
Overview
The library is organized around two ideas:
- Checkers define how validation is performed.
- Validators orchestrate one or more checkers and expose the consumer-facing API.
Validation results are returned as ErrorCollection, so Pitasoft.Error semantics are part of the public contract:
- property-level errors use the property name as key
- object-level errors use
string.Empty - nested validation rewrites keys with prefixes such as
Address.City - collection validation rewrites keys such as
Items[0].Name
Features
- Data Annotations validation through
DataAnnotationsChecker<T> - Fluent synchronous rules with
RuleChecker<T> - Fluent synchronous and asynchronous rules with
RuleCheckerAsync<T> - Custom
IChecker<T>andICheckerAsync<T>implementations - Child validators for nested object graphs
- Collection validation with indexed error paths
- Conditional rules with
When/Unless - Async conditional rules with
WhenAsync/UnlessAsync - Per-property
StopOnFirstFailure - Error codes on rules
- Partial sync validation support through
AsyncRuleBehavior.Skip - Direct object extension methods for quick validation scenarios
- Nullable-aware modern C# API
Installation
dotnet add package Pitasoft.Validation
Pitasoft.Error is required and installed transitively by the package.
Supported frameworks
.NET 8.NET 9.NET 10
Public building blocks
Validators
| Type | Role |
|---|---|
Validator<T> |
Main synchronous validator |
ValidatorAsync<T> |
Main asynchronous validator |
IValidator<T> |
Consumer contract for sync validation |
IValidatorAsync<T> |
Consumer contract for async validation |
IValidatorBuilder<T> |
Construction contract for registering checkers |
Checkers
| Type | Role |
|---|---|
IChecker<T> |
Base sync checker contract |
ICheckerAsync<T> |
Async checker contract |
CheckerBase<T> |
Base class for sync checkers |
CheckerAsyncBase<T> |
Base class for strict async-only checkers |
DataAnnotationsChecker<T> |
Validation from ValidationAttribute metadata |
RuleChecker<T> |
Fluent sync rule checker |
RuleCheckerAsync<T> |
Fluent sync+async rule checker |
Rule and async behavior types
| Type | Role |
|---|---|
RuleBase<T> |
Base rule abstraction |
DelegateRule<T> |
Sync delegate-based rule |
DelegateRuleAsync<T> |
Async delegate-based rule |
IAsyncRuleBehavior |
Contract for configurable sync behavior when async rules exist |
AsyncRuleBehavior |
Throw or Skip |
AsyncRuleInSyncValidationException |
Thrown when sync validation reaches async-only behavior |
AsyncRuleRegistrationException |
Thrown when registering an async rule in RuleChecker<T> |
How the model works
IChecker<T> and ICheckerAsync<T>
A checker is a validation strategy.
IChecker<T> exposes:
GetProperties()Check(instance, propertyName, displayName)CheckObject(instance)
ICheckerAsync<T> extends that model with:
CheckAsync(instance, propertyName, displayName, cancellationToken)CheckObjectAsync(instance, cancellationToken)
Use a checker when you want a reusable validation component or a custom integration point.
IValidator<T> and IValidatorAsync<T>
A validator is the consumer-facing facade.
IValidator<T> exposes:
ValidateObjectValidatePropertiesValidateProperty
IValidatorAsync<T> adds:
ValidateObjectAsyncValidatePropertiesAsyncValidatePropertyAsync
Use a validator when the rest of your application needs “the validator for type T”.
Sync and async semantics
This library uses a flexible, explicit sync/async model:
Validator<T>is the synchronous validator.ValidatorAsync<T>is the asynchronous validator.RuleChecker<T>only accepts synchronous rules.RuleCheckerAsync<T>accepts both synchronous and asynchronous rules.CheckerAsyncBase<T>is a strict async-only base class: sync entry points always throw.
When synchronous validation reaches async-capable behavior, AsyncRuleBehavior controls what happens:
Throw
throwsAsyncRuleInSyncValidationExceptionSkip
skips async-only rules and performs partial synchronous validation
That means:
- async validators can run sync and async checkers
- sync validators remain sync-first
- partial sync validation is intentional, never silent magic
Project contents
The Pitasoft.Validation project is organized into these areas:
| Folder / file area | Purpose |
|---|---|
DataAnnotations/ |
Data Annotations integration |
Rules/ |
Rule abstractions and rule checkers |
Helpers/ |
Child validators, collection validators, builders, caches |
Extensions/ |
Convenience APIs for validators, objects, and rule checkers |
Validator{T}.cs / ValidatorAsync{T}.cs |
Main validator implementations |
IChecker*.cs / IValidator*.cs |
Public contracts |
Excepcions/ |
Public exceptions related to async registration/execution |
Typical execution flow
This is the usual runtime flow:
- Your application calls a validator such as
Validator<User>orValidatorAsync<User>. - The validator resolves the registered checkers for the requested object or property.
- Each checker evaluates its own rules or delegated validation strategy.
- All errors are merged into a single
ErrorCollection. - Child validators and collection validators rewrite keys to preserve the object path.
Conceptually:
Validator= orchestration facadeChecker= validation strategyRule= smallest executable validation unit
Getting started
1. Data Annotations
using System.ComponentModel.DataAnnotations;
using Pitasoft.Validation;
using Pitasoft.Validation.DataAnnotations;
public class User
{
[Required]
[StringLength(50, MinimumLength = 2)]
public string? Name { get; set; }
[Range(18, 99)]
public int Age { get; set; }
[Required]
[EmailAddress]
public string? Email { get; set; }
}
var validator = new Validator<User>(new DataAnnotationsChecker<User>());
var errors = validator.ValidateObject(user);
You can also register Data Annotations on an existing validator:
using Pitasoft.Validation.Extensions;
var validator = new Validator<User>()
.RegisterDataAnnotationsChecker();
Or validate directly from the object:
using Pitasoft.Validation.Extensions;
var errors = user.ValidateWithAttributes();
2. Fluent synchronous rules
using Pitasoft.Validation.Rules;
var checker = new RuleChecker<User>();
checker.For(u => u.Name)
.StopOnFirstFailure()
.NotNullOrEmpty("Name is required")
.MinLength(2, "Name is too short")
.MaxLength(50, "Name is too long");
checker.For(u => u.Age)
.Range(18, 99, "Age must be between 18 and 99");
checker.For(u => u.Email)
.NotNullOrEmpty("Email is required")
.Email("Invalid email format");
var validator = new Validator<User>(checker);
var errors = validator.ValidateObject(user);
3. Reusable validator classes
using Pitasoft.Validation.Rules;
public sealed class UserValidator : Validator<User>
{
public UserValidator()
{
var checker = new RuleChecker<User>();
checker.For(u => u.Name)
.StopOnFirstFailure()
.NotNullOrEmpty("Name is required", code: "NAME_REQUIRED")
.MinLength(2, "Name is too short", code: "NAME_MIN")
.MaxLength(50, "Name is too long", code: "NAME_MAX");
checker.For(u => u.Age)
.Range(18, 99, "Age must be between 18 and 99", code: "AGE_RANGE");
RegisterChecker(checker);
}
}
Useful validator extensions:
using Pitasoft.Validation.Extensions;
var validator = new UserValidator();
var errors = validator.ValidateObject(user);
bool isValid = validator.IsValid(user);
bool ok = validator.TryValidate(user, out var richErrors);
4. Fluent asynchronous rules
using Pitasoft.Validation.Rules;
public sealed class UserValidator : ValidatorAsync<User>
{
public UserValidator(IUserRepository repo)
{
var checker = new RuleCheckerAsync<User>();
checker.For(u => u.Name)
.StopOnFirstFailure()
.NotNullOrEmpty("Name is required")
.MustAsync(async (u, ct) => !await repo.ExistsAsync(u.Name!, ct),
"Name is already taken",
code: "NAME_TAKEN");
checker.For(u => u.Email)
.NotNullOrEmpty("Email is required")
.Email("Invalid email format");
RegisterChecker(checker);
}
}
Usage:
using Pitasoft.Validation.Extensions;
var validator = new UserValidator(repo);
var errors = await validator.ValidateObjectAsync(user, cancellationToken);
bool isValid = await validator.IsValidAsync(user, cancellationToken);
var (ok, richErrors) = await validator.TryValidateAsync(user, cancellationToken);
5. Conditional and async conditional rules
checker.For(u => u.Status)
.NotNullOrEmpty("Status is required")
.Unless(u => string.IsNullOrWhiteSpace(u.Status), b =>
b.Must(u => u.Status is "Active" or "Pending" or "Inactive",
"Status is invalid"));
checker.For(u => u.Name)
.WhenAsync(async (u, ct) => await repo.MustCheckNameAsync(u.Id, ct), b =>
b.MustAsync(async (u, ct) => !await repo.ExistsAsync(u.Name!, ct),
"Name is already taken"));
6. Child validators
using Pitasoft.Validation.Rules;
var addressChecker = new RuleCheckerAsync<Address>();
addressChecker.For(a => a.City).NotNullOrEmpty("City is required");
addressChecker.For(a => a.Street).NotNullOrEmpty("Street is required");
var addressValidator = new ValidatorAsync<Address>(addressChecker);
var userValidator = new ValidatorAsync<User>();
userValidator.RegisterChildValidator(u => u.Address!, addressValidator);
Errors are rewritten with the property prefix:
CitybecomesAddress.City- object-level child errors become
Address
7. Collection validation with ForEach
var validator = new ValidatorAsync<User>();
validator.ForEach(u => u.Emails, (RuleChecker<string> itemChecker) =>
{
itemChecker.For(e => e.Length)
.GreaterThan(5, "Email item is too short");
});
Async item validation:
validator.ForEach(u => u.Orders, (RuleCheckerAsync<Order> itemChecker) =>
{
itemChecker.For(o => o.Number)
.NotNullOrEmpty("Order number is required")
.MustAsync(async (o, ct) => !await repo.OrderExistsAsync(o.Number!, ct),
"Order number already exists");
});
Errors are rewritten as indexed paths such as:
Orders[0].NumberEmails[1].Length
8. Custom async checkers
Use CheckerAsyncBase<T> when you want a strict async-only checker:
using Pitasoft.Error;
public sealed class UniqueEmailChecker : CheckerAsyncBase<User>
{
public override IEnumerable<string> GetProperties() => [nameof(User.Emails)];
public override async Task<ErrorCollection?> CheckAsync(
User? instance,
string propertyName,
string displayName,
CancellationToken cancellationToken = default)
{
if (instance is null) return null;
await Task.Delay(10, cancellationToken);
ErrorCollection? errors = null;
foreach (var email in instance.Emails)
{
if (email == "taken@example.com")
{
(errors ??= new ErrorCollection()).Add(propertyName,
$"Email '{email}' is already registered");
}
}
return errors is { HasErrors: true } ? errors : null;
}
}
9. Object-level extension methods
For quick use without explicitly creating validator types:
using Pitasoft.Validation.Extensions;
var errors1 = user.ValidateWithAttributes();
var errors2 = user.ValidateWithChecker(checker);
var errors3 = user.ValidateWithValidator(v =>
{
v.For(u => u.Name).NotNullOrEmpty("Name is required");
v.For(u => u.Age).Range(18, 99, "Invalid age");
});
var errors4 = await user.ValidateWithValidatorAsync(v =>
{
v.For(u => u.Name)
.NotNullOrEmpty("Name is required")
.MustAsync(async (u, ct) => !await repo.ExistsAsync(u.Name!, ct),
"Duplicated name");
}, cancellationToken);
10. Validating specific properties
Property-level validation is useful for UI forms, step-by-step workflows, or incremental validation.
var validator = new UserValidator(repo);
// Sync
var nameErrors = validator.ValidateProperty(user, nameof(User.Name));
var subsetErrors = validator.ValidateProperties(user, nameof(User.Name), nameof(User.Email));
// Async
var asyncNameErrors = await validator.ValidatePropertyAsync(user, nameof(User.Name), cancellationToken);
var asyncSubsetErrors = await validator.ValidatePropertiesAsync(
user,
new[] { nameof(User.Name), nameof(User.Email) },
cancellationToken);
With lambda expressions:
using Pitasoft.Validation.Extensions;
var syncErrors = validator.ValidateProperty(user, u => u.Name);
var asyncErrors = await validator.ValidatePropertyAsync(user, u => u.Email, cancellationToken);
Fluent rule catalog
The fluent builder currently includes these main categories:
- nullability:
NotNull,Null,NotNullOrEmpty - string rules:
MinLength,MaxLength,Length,LengthBetween,Matches,Email,StartsWith,EndsWith,Url - equality and sets:
Equal,NotEqual,In,NotIn - comparisons:
Range,ExclusiveBetween,GreaterThan,GreaterThanOrEqual,LessThan,LessThanOrEqual - collections:
NotEmpty,MinItems,MaxItems - composition:
When,Unless,WhenAsync,UnlessAsync,StopOnFirstFailure - custom delegates:
Must,MustAsync
Error formatting
Error messages support placeholders:
{0}= display name{1}= current property value
This is handled when rule failures are added to ErrorCollection.
Error key examples
Typical keys returned by the library:
| Scenario | Example key |
|---|---|
| Property validation | Name |
| Object-level validation | string.Empty |
| Child validator property | Address.City |
| Child validator object error | Address |
| Collection item property | Orders[0].Number |
| Collection item object error | Orders[0]. |
Behavior notes
RuleChecker<T>rejects async rule registration and throwsAsyncRuleRegistrationExceptionRuleCheckerAsync<T>can run both sync and async rules- sync calls on async-capable flows may throw
AsyncRuleInSyncValidationException AsyncRuleBehavior.Skipenables partial sync validation when that behavior is intentionalCheckerAsyncBase<T>is stricter thanRuleCheckerAsync<T>: sync entry points always throw
When to use what
- Use
DataAnnotationsChecker<T>when your model already has attributes - Use
RuleChecker<T>for synchronous business rules - Use
RuleCheckerAsync<T>when any rule needs async I/O - Use
Validator<T>/ValidatorAsync<T>for application-facing validation services - Use
CheckerAsyncBase<T>for custom async domain validators that should never run synchronously
Practical recommendations
- Prefer
ValidatorAsync<T>whenever the validation pipeline might need I/O. - Use
AsyncRuleBehavior.Skiponly for intentional partial sync validation scenarios such as UI pre-validation. - Keep child validators focused on a single aggregate or nested type.
- Use
ForEachfor item-level collection validation instead of manual loops in application code. - Treat
ErrorCollectionshape as public behavior if other layers depend on keys.
Español
Resumen
La librería se apoya en dos conceptos:
- Checkers: definen cómo se valida
- Validators: orquestan uno o varios checkers y exponen la API de consumo
Todos los resultados se devuelven como ErrorCollection, así que la semántica de Pitasoft.Error forma parte del contrato público:
- los errores de propiedad usan el nombre de la propiedad
- los errores de objeto usan
string.Empty - la validación anidada reescribe claves como
Address.City - las colecciones reescriben claves como
Items[0].Name
Características
- Validación mediante Data Annotations con
DataAnnotationsChecker<T> - Reglas fluentes síncronas con
RuleChecker<T> - Reglas fluentes síncronas y asíncronas con
RuleCheckerAsync<T> - Checkers personalizados con
IChecker<T>eICheckerAsync<T> - Validación de hijos con
RegisterChildValidator - Validación de colecciones con
ForEach - Reglas condicionales
When/Unless - Reglas condicionales asíncronas
WhenAsync/UnlessAsync StopOnFirstFailurepor propiedad- Códigos de error opcionales
- Validación parcial sync mediante
AsyncRuleBehavior.Skip - Métodos de extensión para validar directamente objetos
Instalación
dotnet add package Pitasoft.Validation
Pitasoft.Error es una dependencia necesaria y llega de forma transitiva.
Frameworks soportados
.NET 8.NET 9.NET 10
Elementos públicos principales
Validators
| Tipo | Función |
|---|---|
Validator<T> |
Validador síncrono principal |
ValidatorAsync<T> |
Validador asíncrono principal |
IValidator<T> |
Contrato de consumo sync |
IValidatorAsync<T> |
Contrato de consumo async |
IValidatorBuilder<T> |
Contrato de construcción para registrar checkers |
Checkers
| Tipo | Función |
|---|---|
IChecker<T> |
Contrato base de checker sync |
ICheckerAsync<T> |
Contrato base de checker async |
CheckerBase<T> |
Clase base para checkers síncronos |
CheckerAsyncBase<T> |
Clase base para checkers async estrictos |
DataAnnotationsChecker<T> |
Adaptador de ValidationAttribute |
RuleChecker<T> |
Checker fluente sync |
RuleCheckerAsync<T> |
Checker fluente sync + async |
Tipos de reglas y comportamiento async
| Tipo | Función |
|---|---|
RuleBase<T> |
Abstracción base de regla |
DelegateRule<T> |
Regla sync basada en delegado |
DelegateRuleAsync<T> |
Regla async basada en delegado |
IAsyncRuleBehavior |
Contrato para controlar la validación sync cuando existen reglas async |
AsyncRuleBehavior |
Throw o Skip |
AsyncRuleInSyncValidationException |
Excepción al alcanzar comportamiento async desde una ruta sync |
AsyncRuleRegistrationException |
Excepción al intentar registrar una regla async en RuleChecker<T> |
Cómo funciona el modelo
IChecker<T> e ICheckerAsync<T>
Un checker es una estrategia de validación.
IChecker<T> expone:
GetProperties()Check(instance, propertyName, displayName)CheckObject(instance)
ICheckerAsync<T> añade:
CheckAsync(instance, propertyName, displayName, cancellationToken)CheckObjectAsync(instance, cancellationToken)
Úsalo cuando quieras una pieza reutilizable de validación o una integración personalizada.
IValidator<T> e IValidatorAsync<T>
Un validator es la fachada orientada al consumidor.
IValidator<T> expone:
ValidateObjectValidatePropertiesValidateProperty
IValidatorAsync<T> añade:
ValidateObjectAsyncValidatePropertiesAsyncValidatePropertyAsync
Úsalo cuando tu aplicación necesite “el validador de T”.
Semántica sync/async
El modelo sync/async de la librería es explícito:
Validator<T>es el validador síncronoValidatorAsync<T>es el validador asíncronoRuleChecker<T>solo acepta reglas síncronasRuleCheckerAsync<T>acepta reglas síncronas y asíncronasCheckerAsyncBase<T>es estrictamente async: las entradas sync siempre lanzan excepción
Cuando una validación síncrona encuentra comportamiento async, AsyncRuleBehavior decide:
Throw
lanzaAsyncRuleInSyncValidationExceptionSkip
omite reglas async y hace validación sync parcial
Esto implica que:
- un validador async puede ejecutar checkers sync y async
- un flujo sync sigue siendo sync-first
- la validación parcial sync es intencional y configurable
Contenido del proyecto
El proyecto Pitasoft.Validation se organiza así:
| Carpeta / área | Propósito |
|---|---|
DataAnnotations/ |
Integración con Data Annotations |
Rules/ |
Reglas, delegados y rule checkers |
Helpers/ |
Builders, validadores de hijos, colecciones y cachés |
Extensions/ |
Métodos de extensión de conveniencia |
Validator{T}.cs / ValidatorAsync{T}.cs |
Implementaciones principales |
IChecker*.cs / IValidator*.cs |
Contratos públicos |
Excepcions/ |
Excepciones públicas relacionadas con reglas async |
Flujo típico de ejecución
El flujo habitual en tiempo de ejecución es:
- La aplicación invoca un validator como
Validator<User>oValidatorAsync<User>. - El validator resuelve los checkers registrados para el objeto o la propiedad.
- Cada checker evalúa sus reglas o su estrategia de validación.
- Todos los errores se agregan en un único
ErrorCollection. - Los validadores hijos y de colecciones reescriben las claves para conservar la ruta completa.
Conceptualmente:
Validator= fachada de orquestaciónChecker= estrategia de validaciónRule= unidad mínima ejecutable
Primeros pasos
1. Data Annotations
using System.ComponentModel.DataAnnotations;
using Pitasoft.Validation;
using Pitasoft.Validation.DataAnnotations;
public class User
{
[Required]
[StringLength(50, MinimumLength = 2)]
public string? Name { get; set; }
[Range(18, 99)]
public int Age { get; set; }
[Required]
[EmailAddress]
public string? Email { get; set; }
}
var validator = new Validator<User>(new DataAnnotationsChecker<User>());
var errors = validator.ValidateObject(user);
También puedes registrar Data Annotations sobre un validator existente:
using Pitasoft.Validation.Extensions;
var validator = new Validator<User>()
.RegisterDataAnnotationsChecker();
O validar directamente desde el objeto:
using Pitasoft.Validation.Extensions;
var errors = user.ValidateWithAttributes();
2. Reglas fluentes síncronas
using Pitasoft.Validation.Rules;
var checker = new RuleChecker<User>();
checker.For(u => u.Name)
.StopOnFirstFailure()
.NotNullOrEmpty("Name is required")
.MinLength(2, "Name is too short")
.MaxLength(50, "Name is too long");
checker.For(u => u.Age)
.Range(18, 99, "Age must be between 18 and 99");
checker.For(u => u.Email)
.NotNullOrEmpty("Email is required")
.Email("Invalid email format");
var validator = new Validator<User>(checker);
var errors = validator.ValidateObject(user);
3. Validators reutilizables
using Pitasoft.Validation.Rules;
public sealed class UserValidator : Validator<User>
{
public UserValidator()
{
var checker = new RuleChecker<User>();
checker.For(u => u.Name)
.StopOnFirstFailure()
.NotNullOrEmpty("Name is required", code: "NAME_REQUIRED")
.MinLength(2, "Name is too short", code: "NAME_MIN")
.MaxLength(50, "Name is too long", code: "NAME_MAX");
checker.For(u => u.Age)
.Range(18, 99, "Age must be between 18 and 99", code: "AGE_RANGE");
RegisterChecker(checker);
}
}
Extensiones útiles del validator:
using Pitasoft.Validation.Extensions;
var validator = new UserValidator();
var errors = validator.ValidateObject(user);
bool isValid = validator.IsValid(user);
bool ok = validator.TryValidate(user, out var richErrors);
4. Reglas fluentes asíncronas
using Pitasoft.Validation.Rules;
public sealed class UserValidator : ValidatorAsync<User>
{
public UserValidator(IUserRepository repo)
{
var checker = new RuleCheckerAsync<User>();
checker.For(u => u.Name)
.StopOnFirstFailure()
.NotNullOrEmpty("Name is required")
.MustAsync(async (u, ct) => !await repo.ExistsAsync(u.Name!, ct),
"Name is already taken",
code: "NAME_TAKEN");
checker.For(u => u.Email)
.NotNullOrEmpty("Email is required")
.Email("Invalid email format");
RegisterChecker(checker);
}
}
Uso:
using Pitasoft.Validation.Extensions;
var validator = new UserValidator(repo);
var errors = await validator.ValidateObjectAsync(user, cancellationToken);
bool isValid = await validator.IsValidAsync(user, cancellationToken);
var (ok, richErrors) = await validator.TryValidateAsync(user, cancellationToken);
5. Reglas condicionales y condiciones async
checker.For(u => u.Status)
.NotNullOrEmpty("Status is required")
.Unless(u => string.IsNullOrWhiteSpace(u.Status), b =>
b.Must(u => u.Status is "Active" or "Pending" or "Inactive",
"Status is invalid"));
checker.For(u => u.Name)
.WhenAsync(async (u, ct) => await repo.MustCheckNameAsync(u.Id, ct), b =>
b.MustAsync(async (u, ct) => !await repo.ExistsAsync(u.Name!, ct),
"Name is already taken"));
6. Validadores hijos
using Pitasoft.Validation.Rules;
var addressChecker = new RuleCheckerAsync<Address>();
addressChecker.For(a => a.City).NotNullOrEmpty("City is required");
addressChecker.For(a => a.Street).NotNullOrEmpty("Street is required");
var addressValidator = new ValidatorAsync<Address>(addressChecker);
var userValidator = new ValidatorAsync<User>();
userValidator.RegisterChildValidator(u => u.Address!, addressValidator);
Las claves se reescriben con prefijo:
Citypasa aAddress.City- los errores de objeto del hijo pasan a
Address
7. Validación de colecciones con ForEach
var validator = new ValidatorAsync<User>();
validator.ForEach(u => u.Emails, (RuleChecker<string> itemChecker) =>
{
itemChecker.For(e => e.Length)
.GreaterThan(5, "Email item is too short");
});
Validación async de items:
validator.ForEach(u => u.Orders, (RuleCheckerAsync<Order> itemChecker) =>
{
itemChecker.For(o => o.Number)
.NotNullOrEmpty("Order number is required")
.MustAsync(async (o, ct) => !await repo.OrderExistsAsync(o.Number!, ct),
"Order number already exists");
});
Las claves se reescriben como:
Orders[0].NumberEmails[1].Length
8. Checkers async personalizados
Usa CheckerAsyncBase<T> cuando quieras un checker estrictamente async:
using Pitasoft.Error;
public sealed class UniqueEmailChecker : CheckerAsyncBase<User>
{
public override IEnumerable<string> GetProperties() => [nameof(User.Emails)];
public override async Task<ErrorCollection?> CheckAsync(
User? instance,
string propertyName,
string displayName,
CancellationToken cancellationToken = default)
{
if (instance is null) return null;
await Task.Delay(10, cancellationToken);
ErrorCollection? errors = null;
foreach (var email in instance.Emails)
{
if (email == "taken@example.com")
{
(errors ??= new ErrorCollection()).Add(propertyName,
$"Email '{email}' is already registered");
}
}
return errors is { HasErrors: true } ? errors : null;
}
}
9. Métodos de extensión sobre objetos
Para validar rápido sin crear tipos de validator explícitos:
using Pitasoft.Validation.Extensions;
var errors1 = user.ValidateWithAttributes();
var errors2 = user.ValidateWithChecker(checker);
var errors3 = user.ValidateWithValidator(v =>
{
v.For(u => u.Name).NotNullOrEmpty("Name is required");
v.For(u => u.Age).Range(18, 99, "Invalid age");
});
var errors4 = await user.ValidateWithValidatorAsync(v =>
{
v.For(u => u.Name)
.NotNullOrEmpty("Name is required")
.MustAsync(async (u, ct) => !await repo.ExistsAsync(u.Name!, ct),
"Duplicated name");
}, cancellationToken);
10. Validación de propiedades concretas
La validación por propiedad es útil en formularios, asistentes y validación incremental.
var validator = new UserValidator(repo);
// Sync
var nameErrors = validator.ValidateProperty(user, nameof(User.Name));
var subsetErrors = validator.ValidateProperties(user, nameof(User.Name), nameof(User.Email));
// Async
var asyncNameErrors = await validator.ValidatePropertyAsync(user, nameof(User.Name), cancellationToken);
var asyncSubsetErrors = await validator.ValidatePropertiesAsync(
user,
new[] { nameof(User.Name), nameof(User.Email) },
cancellationToken);
Con expresiones lambda:
using Pitasoft.Validation.Extensions;
var syncErrors = validator.ValidateProperty(user, u => u.Name);
var asyncErrors = await validator.ValidatePropertyAsync(user, u => u.Email, cancellationToken);
Catálogo fluente de reglas
El builder fluente incluye principalmente:
- nulabilidad:
NotNull,Null,NotNullOrEmpty - strings:
MinLength,MaxLength,Length,LengthBetween,Matches,Email,StartsWith,EndsWith,Url - igualdad y conjuntos:
Equal,NotEqual,In,NotIn - comparación:
Range,ExclusiveBetween,GreaterThan,GreaterThanOrEqual,LessThan,LessThanOrEqual - colecciones:
NotEmpty,MinItems,MaxItems - composición:
When,Unless,WhenAsync,UnlessAsync,StopOnFirstFailure - delegados personalizados:
Must,MustAsync
Formato de mensajes
Los mensajes de error soportan placeholders:
{0}= display name{1}= valor actual de la propiedad
Ejemplos de claves de error
Claves típicas devueltas por la librería:
| Escenario | Clave de ejemplo |
|---|---|
| Validación de propiedad | Name |
| Validación de objeto | string.Empty |
| Propiedad de un validador hijo | Address.City |
| Error de objeto de un hijo | Address |
| Propiedad de un item de colección | Orders[0].Number |
| Error de objeto de un item | Orders[0]. |
Notas de comportamiento
RuleChecker<T>rechaza el registro de reglas async y lanzaAsyncRuleRegistrationExceptionRuleCheckerAsync<T>puede ejecutar reglas sync y async- una llamada sync sobre un flujo async puede lanzar
AsyncRuleInSyncValidationException AsyncRuleBehavior.Skiphabilita validación sync parcialCheckerAsyncBase<T>es más estricto queRuleCheckerAsync<T>: sus entradas sync siempre lanzan excepción
Cuándo usar cada tipo
- Usa
DataAnnotationsChecker<T>si ya tienes atributos en el modelo - Usa
RuleChecker<T>para reglas de negocio síncronas - Usa
RuleCheckerAsync<T>si alguna regla necesita I/O async - Usa
Validator<T>/ValidatorAsync<T>como servicio de validación para la aplicación - Usa
CheckerAsyncBase<T>para validadores de dominio async que no deban ejecutarse de forma síncrona
Recomendaciones prácticas
- Prefiere
ValidatorAsync<T>cuando la validación pueda necesitar I/O. - Usa
AsyncRuleBehavior.Skipsolo cuando quieras validación sync parcial de forma explícita. - Mantén los validadores hijos centrados en un único tipo anidado o agregado.
- Usa
ForEachpara validar colecciones en vez de replicar bucles manuales en la aplicación. - Trata la forma de
ErrorCollectioncomo comportamiento público si otras capas dependen de sus claves.
| 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 is compatible. 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
- Pitasoft.Error (>= 5.3.14)
-
net8.0
- Pitasoft.Error (>= 5.3.14)
-
net9.0
- Pitasoft.Error (>= 5.3.14)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on Pitasoft.Validation:
| Package | Downloads |
|---|---|
|
Pitasoft.Validation.Rules
Implement rules to validate objects |
|
|
Pitasoft.FluentValidation
Provides integration with FluentValidation for Pitasoft.Validation |
|
|
Pitasoft.Validation.AspNetCore
ASP.NET Core integration for Pitasoft.Validation. Provides Minimal API endpoint filters and MVC action filters to validate request parameters using IValidator<T>, IValidatorAsync<T>, IChecker<T>, and ICheckerAsync<T>. |
|
|
Pitasoft.Validation.DependencyInjectionExtensions
Microsoft.Extensions.DependencyInjection integration for Pitasoft.Validation. Provides extension methods to register IValidator<T> and IValidatorAsync<T> implementations into an IServiceCollection, both manually and via assembly scanning. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.0.5 | 232 | 4/1/2026 |
| 6.0.4 | 187 | 3/31/2026 |
| 6.0.3 | 118 | 3/25/2026 |
| 6.0.2 | 109 | 3/25/2026 |
| 6.0.1 | 115 | 3/23/2026 |
| 5.1.5 | 118 | 3/17/2026 |
| 5.1.4 | 116 | 3/17/2026 |
| 5.1.3 | 140 | 3/5/2026 |
| 5.1.2 | 125 | 3/2/2026 |
| 5.1.1 | 119 | 3/2/2026 |
| 5.0.1 | 120 | 2/25/2026 |
| 4.3.0 | 488 | 11/20/2023 |
| 4.2.0 | 303 | 10/23/2023 |
| 4.1.0 | 766 | 11/18/2022 |
| 4.0.2 | 978 | 7/26/2022 |
| 4.0.0 | 833 | 7/26/2022 |
| 3.0.0 | 633 | 7/22/2022 |
| 2.0.0 | 673 | 6/26/2021 |
| 0.0.1 | 116 | 3/23/2026 |