Apariencia
Ejemplo completo: correo obligatorio y único
Índice · Anterior: formularios
Este ejemplo es un programa de consola para .NET 10 que referencia platform.rules y platform.validators. El servicio simulado hace visible cuándo se ejecuta la consulta. Las reglas se crean una vez y reciben un contexto en cada ejecución.
csharp
using Greencore.platform.rules;
using Greencore.platform.validators;
var required = ValidationRule.For("text.required", new RequiredRule<string?>(), "customer.email.required", "El correo es obligatorio.");
var format = ValidationRule.For("email.format", new EmailRule(), "customer.email.invalid", "El correo no es válido.");
var unique = ValidationRule.ForAsync<string?, CustomerContext>(
"email.unique",
async (email, context, token) => !await context.Data.EmailExistsAsync(email!, context.ExcludedCustomerId, token),
"customer.email.duplicate",
"El correo ya está registrado.");
var plan = Validation.For<Customer, CustomerContext>()
.Field(x => x.Email, field => field.Sequence(sequence => sequence
.Add(required)
.Add(format)
.Add(unique)))
.Build();
var data = new ExampleData();
foreach (var email in new string?[] { null, "incorrecto", "[email protected]", "[email protected]" })
{
var candidate = new Customer(null, email);
var context = new CustomerContext(data, candidate.Id);
var report = await plan.ValidateAsync(candidate, context);
Console.WriteLine($"Correo: {email ?? "(null)"}; válido: {report.IsValid}");
foreach (var diagnostic in report.ForField((Customer x) => x.Email, DiagnosticView.Summary))
Console.WriteLine($" {diagnostic.Code}: {diagnostic.Message}");
}
public sealed record Customer(Guid? Id, string? Email);
public sealed record CustomerContext(ICustomerData Data, Guid? ExcludedCustomerId);
public interface ICustomerData
{
ValueTask<bool> EmailExistsAsync(string email, Guid? excludedCustomerId, CancellationToken cancellationToken);
}
public sealed class ExampleData : ICustomerData
{
private readonly Guid _existingId = Guid.Parse("11111111-1111-1111-1111-111111111111");
public ValueTask<bool> EmailExistsAsync(string email, Guid? excludedCustomerId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine($"Consulta de unicidad: {email}");
var exists = excludedCustomerId != _existingId && string.Equals(email, "[email protected]", StringComparison.OrdinalIgnoreCase);
return ValueTask.FromResult(exists);
}
}Qué sucede en cada entrada
| Entrada | Presencia | Formato | Consulta | Resultado |
|---|---|---|---|---|
null | Falla | Omitido | Omitida | customer.email.required |
incorrecto | Cumple | Falla | Omitida | customer.email.invalid |
[email protected] | Cumple | Cumple | Existe | customer.email.duplicate |
[email protected] | Cumple | Cumple | No existe | Válido |
unique recibe string?, porque está dentro del campo Email. La secuencia garantiza que solo se consulta después de superar presencia y formato. El operador ! en email! informa al compilador de esa garantía de composición; no agrega una validación en tiempo de ejecución.
ExcludedCustomerId permite excluir la propia fila durante una edición. La exclusión debe formar parte del contexto de la ejecución o de una condición sobre el candidato completo. No captures un identificador cambiante en un catálogo global de reglas.
El doble de datos usa una comparación concreta solo para demostrar el flujo. La aplicación real debe definir normalización y equivalencia del correo de manera consistente con su persistencia.
Plan local y plan de envío
Para validar mientras se escribe sin consultar servicios, puedes construir un plan local con presencia y formato. El envío utiliza el plan contextual completo. Ambos reutilizan las mismas definiciones required y format.
La inclusión de un plan no transforma automáticamente reglas posteriores en dependientes. Si agregas una regla contextual sobre el candidato completo, usa Sequence(s => s.Include(localPlan).Add(candidateRule)). Si validas por campo, conserva la secuencia de ese campo como en el ejemplo.
Decisiones frecuentes
| Necesidad | API apropiada |
|---|---|
| Comprobar un valor sin mensajes | IRule<T> |
| Condición breve y propia de la aplicación | ValidationRule.For con predicado |
| Consulta externa con servicios por ejecución | ValidationRule.ForAsync<T, C> |
| Evitar consultas cuando faltan datos | Sequence |
| Acumular errores independientes | Varios Add |
| Validar un objeto nullable | OptionalObject o RequiredObject |
| Explicar un fallo con valores observados | WithEvidence o WithEvidenceAsync |
| Mostrar errores junto a un control | ForField |
| Descartar respuestas de ediciones antiguas | FormValidationSession |
| Cargar un selector dependiente | Options con DependsOn |
Errores que conviene evitar
- Dar por hecho que
EmailRuleexige presencia: aceptanull. - Usar
Addpara condiciones que requieren que otra regla haya pasado. - Empezar un callback de
FieldconThen: ese builder todavía no tiene una rama previa. - Registrar una regla de
Customerdentro de un campo cuyo valor esstring?. - Usar
Validateen un árbol que contiene hojas asíncronas. - Reutilizar un candidato mutable mientras una consulta sigue en curso.
- Tratar
Failedcomo un error funcional del campo. - Autorizar el envío con el último estado visual en lugar de validar la instantánea actual.
- Omitir dependencias que el loader de opciones utiliza.
El repositorio también contiene un ejemplo ejecutable más amplio. Se ejecuta desde la raíz con:
powershell
dotnet run --project examples/platform.validators/examples.platform.validators.csproj