17 Sep 2026
Validation is one of those things every application needs, but few implement consistently. Some endpoints validate, some do not. Some use attributes, some use manual checks, some rely on the database to throw an exception. Brand Website V3 standardizes validation with FluentValidation validators and enforces them automatically through endpoint filters. Every request DTO has a corresponding validator. Every validator is auto-discovered and runs before the endpoint handler executes.
// (Example from any module's Features assembly)
public class UpsertBlogPostCommandValidator : AbstractValidator<UpsertBlogPostCommand>
{
public UpsertBlogPostCommandValidator()
{
RuleFor(request => request.Title).NotEmpty();
RuleFor(request => request.CoverPhotoUrl).NotEmpty();
}
}
Clean and expressive. The validator declares what the request must satisfy. Title must not be empty. CoverPhotoUrl must not be empty. These are business rules, not just data annotations. FluentValidation gives you the full power of a fluent API for complex validation scenarios.
// Blog.Api/UpsertBlogPost/UpsertBlogPostEndpoint.cs
builder.MapPut("/", UpsertBlogPostAsync)
.RequirePermission(nameof(Blog), (int)Permissions.ManagePosts)
.AddEndpointFilter<EndpointValidationFilter<UpsertBlogPostRequest>>();
One line. The EndpointValidationFilter<TRequest> runs the corresponding FluentValidation validator before the endpoint handler executes. If validation fails, it returns 400 Bad Request automatically. The handler never sees an invalid request.
Each route group also registers an ApiExceptionFilter for unhandled exceptions:
var routeGroup = routeBuilder
.MapGroup(ApiRoutes.BlogPosts)
.AddEndpointFilter<ApiExceptionFilter>();
This catches any exceptions that slip through (database errors, unexpected nulls) and returns structured error responses. Combined with the validation filter, you get a robust, layered error handling strategy.
.AddEndpointFilter<EndpointValidationFilter<UpsertBlogPostRequest>>();
RuleFor(request => request.Title).NotEmpty();
.AddEndpointFilter<ApiExceptionFilter>();
var validator = new UpsertBlogPostCommandValidator();
var result = await validator.ValidateAsync(command);
Assert.IsTrue(result.IsValid);
Brand Website V3 enforces FluentValidation across all request DTOs with automatic endpoint filters. Every endpoint validates, every failure is consistent. See the full pattern at kiss-code.com.
I continuously build, learn and experiment with innovative technology. Allow me to share what I learn, with you.