Request Validation
Sharkable integrates with FluentValidation to provide automatic request validation for ISharkEndpoint endpoints. When enabled, incoming parameters are validated against registered IValidator<T> implementations, and invalid requests return a 400 response with a UnifiedResult error body.
Enable
builder.Services.AddShark(opt =>
{
opt.EnableValidation = true;
});
Create a validator
Define a validator by extending AbstractValidator<T>:
using FluentValidation;
public class CreateUserRequest
{
public string Name { get; set; }
public string Email { get; set; }
}
public class CreateUserValidator : AbstractValidator<CreateUserRequest>
{
public CreateUserValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
RuleFor(x => x.Email).NotEmpty().EmailAddress();
}
}
Register the validator
Non-AOT mode (auto-scan)
When EnableValidation = true, Sharkable scans the registered assemblies for IValidator<T> implementations and registers them automatically.
AOT mode (manual registration)
In AOT mode, assembly scanning doesn't work. Register validators explicitly:
builder.Services.AddSingleton<IValidator<CreateUserRequest>, CreateUserValidator>();
How it works
Once enabled and registered, any ISharkEndpoint parameter that has a corresponding IValidator<T> registered in DI is automatically validated before the handler runs.
public class UserEndpoint : ISharkEndpoint
{
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapPost("create", (CreateUserRequest request) =>
{
// If request is invalid, the validation filter short-circuits
// and returns a 400 UnifiedResult error before reaching here.
return Results.Ok("valid!");
});
}
}
Successful response
{
"statusCode": 200,
"data": "valid!",
"errorMessage": null
}
Validation error response
{
"statusCode": 400,
"data": null,
"errorMessage": "'Name' must not be empty.; 'Email' must not be empty."
}
AOT example (full)
using System.Text.Json.Serialization;
using FluentValidation;
using Sharkable;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddShark([typeof(Program).Assembly], opt =>
{
opt.EnableValidation = true;
});
// Manually register validators for AOT
builder.Services.AddSingleton<IValidator<CreateUserRequest>, CreateUserValidator>();
// Register JsonSerializerContext for AOT serialization
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
var app = builder.Build();
app.UseShark();
app.Run();
public record CreateUserRequest(string Name, string Email);
public class CreateUserValidator : AbstractValidator<CreateUserRequest>
{
public CreateUserValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
RuleFor(x => x.Email).NotEmpty().EmailAddress();
}
}
[JsonSerializable(typeof(CreateUserRequest))]
internal partial class AppJsonContext : JsonSerializerContext { }