Quick Start
This document shows a complete, working example from scratch: defining a model, creating a validator, registering it in DI and using it in an ASP.NET Core endpoint. The goal is to have validation working in less than 10 minutes.
1. Install the Packages
dotnet add package Vali-Validation
dotnet add package Vali-Validation.AspNetCore
2. Define the Model
// Models/CreateProductRequest.cs
public class CreateProductRequest
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Stock { get; set; }
public string Category { get; set; } = string.Empty;
public string? ImageUrl { get; set; }
}
3. Create the Validator
Validators inherit from AbstractValidator<T>. All rules are defined in the constructor.
// Validators/CreateProductRequestValidator.cs
using Vali_Validation.Core.Validators;
public class CreateProductRequestValidator : AbstractValidator<CreateProductRequest>
{
private static readonly string[] ValidCategories =
new[] { "Electronics", "Clothing", "Food", "Books", "Other" };
public CreateProductRequestValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("The product name is required.")
.MinimumLength(3)
.WithMessage("The name must have at least 3 characters.")
.MaximumLength(200)
.WithMessage("The name cannot exceed 200 characters.");
RuleFor(x => x.Description)
.MaximumLength(1000)
.WithMessage("The description cannot exceed 1000 characters.");
RuleFor(x => x.Price)
.GreaterThan(0)
.WithMessage("The price must be greater than 0.")
.LessThan(100000)
.WithMessage("The price cannot exceed 100,000.");
RuleFor(x => x.Stock)
.GreaterThanOrEqualTo(0)
.WithMessage("The stock cannot be negative.");
RuleFor(x => x.Category)
.NotEmpty()
.WithMessage("The category is required.")
.In(ValidCategories)
.WithMessage("The category must be one of: Electronics, Clothing, Food, Books, Other.");
RuleFor(x => x.ImageUrl)
.Url()
.WithMessage("The image URL is not valid.")
.When(x => x.ImageUrl != null);
}
}
4. Register in DI
// Program.cs
using Vali_Validation.Core.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Registers all IValidator<T> from the current assembly
builder.Services.AddValidationsFromAssembly(typeof(Program).Assembly);
// If using AspNetCore:
builder.Services.AddValiValidationProblemDetails();
var app = builder.Build();
// Middleware that converts ValidationException to HTTP 400
app.UseValiValidationExceptionHandler();
5. Use in an Endpoint
Option A: Minimal API with endpoint filter
// Program.cs (continued)
app.MapPost("/products", async (
CreateProductRequest request,
IValidator<CreateProductRequest> validator,
IProductRepository repository) =>
{
// Validate manually
var result = await validator.ValidateAsync(request);
if (!result.IsValid)
{
return Results.ValidationProblem(result.Errors.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.ToArray()));
}
var product = await repository.CreateAsync(request);
return Results.Created($"/products/{product.Id}", product);
});
Or using the endpoint filter (cleaner):
app.MapPost("/products", async (
CreateProductRequest request,
IProductRepository repository) =>
{
var product = await repository.CreateAsync(request);
return Results.Created($"/products/{product.Id}", product);
})
.WithValiValidation<CreateProductRequest>(); // Automatic validation before the handler
Option B: MVC Controller
[ApiController]
[Route("api/[controller]")]
[ValiValidate] // Automatically validates all arguments with a registered IValidator<T>
public class ProductsController : ControllerBase
{
private readonly IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateProductRequest request)
{
// If we get here, validation has already passed
var product = await _repository.CreateAsync(request);
return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}
}
6. Run and Test
Run the application:
dotnet run
Test with invalid data:
curl -X POST http://localhost:5000/products \
-H "Content-Type: application/json" \
-d '{"name": "A", "price": -5, "stock": -1, "category": "Unknown"}'
HTTP 400 response:
{
"type": "https://tools.ietf.org/html/rfc7807",
"title": "Validation Failed",
"status": 400,
"errors": {
"Name": ["The name must have at least 3 characters."],
"Price": ["The price must be greater than 0."],
"Stock": ["The stock cannot be negative."],
"Category": ["The category must be one of: Electronics, Clothing, Food, Books, Other."]
}
}
Test with valid data:
curl -X POST http://localhost:5000/products \
-H "Content-Type: application/json" \
-d '{
"name": "Laptop Pro 15",
"description": "High-performance laptop",
"price": 1299.99,
"stock": 50,
"category": "Electronics",
"imageUrl": "https://example.com/laptop.jpg"
}'
HTTP 201 response:
{
"id": 1,
"name": "Laptop Pro 15",
"price": 1299.99,
"category": "Electronics"
}
Summary of What You Learned
In this example you saw:
- Defining a validator by inheriting from
AbstractValidator<T>and usingRuleForin the constructor - Chained rules with custom messages via
WithMessage - Conditionals with
Whenfor optional rules - DI registration with
AddValidationsFromAssembly - Usage in endpoints manually or automatically with
WithValiValidation<T> - Standard error format
application/problem+json
Next Steps
- Validators in depth — AbstractValidator, Include, global CascadeMode, ValidateParallelAsync
- Basic Rules — Complete catalog of all available rules
- Modifiers — WithMessage, When/Unless, StopOnFirstFailure and more