Skip to content

️ Validation Attributes

v1.24.0 β€” The [Validate] source generator creates a .Validate() extension method for any record or class decorated with System.ComponentModel.DataAnnotations attributes, returning Result<T> β€” fully composable with the rest of the pipeline.

Quick Setup

using REslava.Result.AspNetCore;

[Validate]
public record CreateProductRequest(
    [Required] string Name,
    [Range(0.01, double.MaxValue)] decimal Price,
    [StringLength(500)] string? Description
);

Generated Extension Method

The generator emits this code at compile time β€” no runtime reflection overhead:

// Auto-generated in Generated.ValidationExtensions namespace
public static Result<CreateProductRequest> Validate(this CreateProductRequest instance)
{
    var context = new ValidationContext(instance);
    var results = new List<ValidationResult>();
    if (Validator.TryValidateObject(instance, context, results, validateAllProperties: true))
        return Result<CreateProductRequest>.Ok(instance);

    var errors = results
        .Select(r => (IError)new ValidationError(
            r.ErrorMessage ?? "Validation failed",
            r.MemberNames.FirstOrDefault()))
        .ToList();
    return Result<CreateProductRequest>.Fail(errors);
}

Pipeline Integration

// Minimal API β€” validate and respond in one line
app.MapPost("/api/products", (CreateProductRequest request) =>
    request.Validate().ToIResult());

// MVC Controller β€” validate then call service
return (await request.Validate()
    .BindAsync(r => _service.CreateAsync(r)))
    .ToActionResult();

// Chain further operations
Result<ProductDto> result = await request.Validate()
    .BindAsync(r => _service.CreateAsync(r))
    .Map(p => p.ToDto());

Supported Annotations

All 20+ System.ComponentModel.DataAnnotations types work automatically because the generator delegates to Validator.TryValidateObject:

[Required], [Range], [StringLength], [MinLength], [MaxLength], [RegularExpression], [EmailAddress], [Phone], [Url], [Compare], [CreditCard], [DataType], and any custom class inheriting from ValidationAttribute.

Error Details

Each ValidationError carries the field name and message:

result.Errors.OfType<ValidationError>().ToList().ForEach(e =>
    Console.WriteLine($"{e.FieldName}: {e.Message}"));
// β†’ Name: The Name field is required.
// β†’ Price: The field Price must be between 0.01 and 1.7976931348623157E+308.