Validation
When you build an API, you’re creating a contract. You’re telling the outside world, “If you send me data in this specific format, I will perform this action for you.” But what happens when the data you receive doesn’t follow the rules? What if it’s incomplete, incorrectly formatted, or just plain malicious?
This is where validation comes in. It’s the gatekeeper of your API, the vigilant guard that inspects all incoming data to ensure it’s valid and safe before it ever touches your business logic. A robust validation strategy is not just a feature; it’s a fundamental requirement for a secure, reliable, and user-friendly application.
SliceFlow embraces this principle by integrating a powerful and expressive validation library, FluentValidation, directly into its endpoint-centric design.
The Problem with Manual Validation
Imagine writing code like this in every single one of your endpoints:
if (request.Permissions == null || !request.Permissions.Any()){ return BadRequest("Permissions list cannot be empty.");}if (request.UserId == Guid.Empty){ return BadRequest("A valid UserId must be provided.");}// ... and so on for every property ...
This approach clutters your business logic with repetitive boilerplate code, is prone to errors, and is hard to maintain. SliceFlow provides a much cleaner, more powerful way.
The SliceFlow Way: Clean, Declarative Rules
With SliceFlow (leveraging FastEndpoints and FluentValidation), you define your validation rules in a separate, dedicated class. This keeps your endpoint logic clean and focused on its primary task, while your validation rules become a clear, readable, and reusable definition of what constitutes a valid request.
Let’s look at the endpoint for assigning permissions to a user.
1. The Request: This is the data we expect from the client.
public record AssignPermissionRequest( [property: BindFrom("id")] Guid UserId, IEnumerable<string> Permissions);
2. The Validator: This is the set of rules for that request. The class name follows a simple convention (RequestName
+ Validator
), which allows SliceFlow to automatically discover and apply it.
public class AssignPermissionRequestValidator : Validator<AssignPermissionRequest>{ public AssignPermissionRequestValidator() { // Rule: The list of permissions cannot be empty. RuleFor(x => x.Permissions).NotEmpty(); }}
The beauty of this is its readability. The code reads almost like plain English: “Create a rule for the Permissions
property, and ensure it is not empty.”
The Automatic Gatekeeper
Here’s where the magic happens. You, the developer, do not need to write any code to execute this validator.
When a request comes in to the AssignPermissions
endpoint, SliceFlow’s pipeline automatically detects that a AssignPermissionRequestValidator
exists. It runs the validation before your endpoint’s ExecuteAsync
method is ever called.
If the validation fails (for example, the client sends an empty permissions
array), the pipeline stops immediately and automatically sends back a standardized 400 Bad Request
response, complete with a ProblemDetails
body that clearly outlines the errors.
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "Permissions": [ "'Permissions' must not be empty." ] }}
Your core business logic is never even touched. The invalid request is politely but firmly turned away at the gate with a clear, standardized error response.
Beyond the Basics: Business Rule Validation
Sometimes, validation requires more than just checking if a field is empty. You might need to check if a user exists in the database, or if a list of IDs corresponds to real entities. This is business rule validation, and it happens inside your endpoint logic.
SliceFlow provides a consistent pattern for this, so even your business validation errors produce the same clean, structured response as the automatic validation.
In our AssignPermissions
endpoint, we need to verify two things:
- Does the
UserId
from the request correspond to an actual user? - Are the permission strings in the request valid, known permissions in our system?
The pattern is simple: check a condition, and if it fails, add a structured error and throw.
// 1. Check if the user existsvar user = await db.Users.FindAsync(req.UserId);if (user is null){ AddError(r => r.UserId, $"User with id {req.UserId} could not be found.");}
// 2. Check if the provided permissions are validvar validPermissions = await db.Permissions.Where(...).ToListAsync();var invalidPermissions = req.Permissions.Except(validPermissions.Select(p => p.Name));if (invalidPermissions.Any()){ AddError(r => r.Permissions, "One or multiple invalid permissions have been provided.");}
// At the end of our checks, this one line will throw a formatted error if we added anyThrowIfAnyErrors();
If ThrowIfAnyErrors()
is triggered, it produces the exact same error format that the client would receive for an automatic validation failure. This consistency is a massive benefit for your API consumers.
By combining automatic, declarative validation for request shapes with a clean pattern for business rule validation, SliceFlow ensures that your API is robust, secure, and easy to work with, both for you and for the developers who use it.