Claude
Skills
Sign in
Back

dotnet-input-validation

Included with Lifetime
$97 forever

Validating HTTP request inputs. .NET 10 AddValidation, FluentValidation, ProblemDetails.

General

What this skill does


# dotnet-input-validation

Comprehensive input validation patterns for .NET APIs. Covers the .NET 10 built-in validation system, FluentValidation for complex business rules, Data Annotations for simple models, endpoint filters for Minimal API integration, ProblemDetails error responses, and security-focused validation techniques.

**Scope boundary:** This skill owns practical validation framework guidance -- when to use each framework, how to configure and integrate them, and security-focused input handling tips. OWASP security principles (injection categories, threat modeling) -- see [skill:dotnet-security-owasp]. Architectural validation strategy (where validation fits in clean architecture, vertical slices) -- see [skill:dotnet-architecture-patterns]. Options pattern validation with `ValidateDataAnnotations()` -- see [skill:dotnet-csharp-configuration].

**Out of scope:** Blazor form validation (EditForm, DataAnnotationsValidator) -- see [skill:dotnet-blazor-auth]. OWASP injection prevention principles -- see [skill:dotnet-security-owasp]. Architectural patterns for validation placement -- see [skill:dotnet-architecture-patterns]. Options pattern ValidateDataAnnotations -- see [skill:dotnet-csharp-configuration].

Cross-references: [skill:dotnet-security-owasp] for OWASP injection prevention, [skill:dotnet-architecture-patterns] for architectural validation strategy, [skill:dotnet-minimal-apis] for Minimal API pipeline integration, [skill:dotnet-csharp-configuration] for Options pattern validation.

---

## Validation Framework Decision Tree

Choose the validation framework based on project requirements:

1. **.NET 10 Built-in Validation (`AddValidation`)** -- default for new .NET 10+ projects. Source-generator-based, AOT-compatible, auto-discovers types from Minimal API handlers. Best for: greenfield projects targeting .NET 10+.
2. **FluentValidation** -- when validation rules are complex (cross-property, conditional, database-dependent). Rich fluent API with testable validator classes. Best for: complex business rules, domain validation.
3. **Data Annotations** -- when models need simple declarative validation (`[Required]`, `[Range]`). Widely understood, works with MVC model binding and `IValidatableObject` for cross-property checks. Best for: simple DTOs, shared models.
4. **MiniValidation** -- lightweight Data Annotations runner without MVC model binding overhead. Best for: micro-services with simple validation (see [skill:dotnet-architecture-patterns] for details).

General guidance: prefer .NET 10 built-in validation for new projects. Use FluentValidation when rules outgrow annotations. Do not mix multiple frameworks in the same request DTO -- pick one per model type and stay consistent.

---

## .NET 10 Built-in Validation

.NET 10 introduces `Microsoft.Extensions.Validation` with source-generator-based validation that integrates directly into the Minimal API pipeline. It auto-discovers validatable types from endpoint handler parameters and runs validation via an endpoint filter.

### Setup

```csharp
// <PackageReference Include="Microsoft.Extensions.Validation" Version="10.*" />
builder.Services.AddValidation();

var app = builder.Build();
// Validation runs automatically via endpoint filter for Minimal API handlers
```

`AddValidation()` scans for types annotated with `[ValidatableType]` and generates validation logic at compile time using source generators, ensuring Native AOT compatibility.

### Defining Validatable Types

```csharp
[ValidatableType]
public partial class CreateProductRequest
{
    [Required]
    [StringLength(200, MinimumLength = 1)]
    public required string Name { get; set; }

    [Range(0.01, 1_000_000)]
    public decimal Price { get; set; }

    [Required]
    [RegularExpression(@"^[A-Z]{2,4}-\d{4,8}$", ErrorMessage = "SKU format: AA-0000")]
    public required string Sku { get; set; }
}
```

The `partial` keyword is required because the source generator emits validation logic into the same type. The `[ValidatableType]` attribute triggers code generation at compile time -- no reflection at runtime.

### How It Works

1. Source generator discovers `[ValidatableType]` classes and emits `IValidatableObject`-like validation logic.
2. `AddValidation()` registers an endpoint filter that inspects Minimal API handler parameters.
3. When a request arrives, the filter validates parameters before the handler executes.
4. On failure, returns a `ValidationProblem` response automatically.

**Gotcha:** `AddValidation()` integrates with Minimal APIs via endpoint filters. MVC controllers use their own model validation pipeline and do not participate in this filter-based system. For controllers, Data Annotations and `ModelState.IsValid` remain the standard approach.

---

## FluentValidation

FluentValidation provides a fluent API for building strongly-typed validation rules. It excels at complex business validation with cross-property rules, conditional logic, and database-dependent checks.

### Validator Definition

```csharp
// <PackageReference Include="FluentValidation" Version="11.*" />
// <PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.*" />
public sealed class CreateOrderValidator : AbstractValidator<CreateOrderRequest>
{
    public CreateOrderValidator()
    {
        RuleFor(x => x.CustomerId)
            .NotEmpty()
            .MaximumLength(50);

        RuleFor(x => x.OrderDate)
            .LessThanOrEqualTo(DateOnly.FromDateTime(DateTime.UtcNow))
            .WithMessage("Order date cannot be in the future");

        RuleFor(x => x.Lines)
            .NotEmpty()
            .WithMessage("Order must have at least one line item");

        RuleForEach(x => x.Lines)
            .ChildRules(line =>
            {
                line.RuleFor(l => l.ProductId).NotEmpty();
                line.RuleFor(l => l.Quantity).GreaterThan(0);
                line.RuleFor(l => l.UnitPrice).GreaterThan(0);
            });

        // Conditional rule
        When(x => x.ShippingMethod == ShippingMethod.Express, () =>
        {
            RuleFor(x => x.ShippingAddress)
                .NotNull()
                .WithMessage("Express shipping requires an address");
        });
    }
}
```

### DI Registration with Assembly Scanning

```csharp
// Registers all AbstractValidator<T> implementations from the assembly
builder.Services.AddValidatorsFromAssemblyContaining<Program>(ServiceLifetime.Scoped);
```

### Manual Validation Pattern (Recommended)

FluentValidation's ASP.NET pipeline auto-validation is deprecated. Use manual validation in endpoint handlers or endpoint filters instead:

```csharp
app.MapPost("/api/orders", async (
    CreateOrderRequest request,
    IValidator<CreateOrderRequest> validator,
    AppDbContext db) =>
{
    var result = await validator.ValidateAsync(request);
    if (!result.IsValid)
    {
        return TypedResults.ValidationProblem(result.ToDictionary());
    }

    var order = new Order { CustomerId = request.CustomerId };
    db.Orders.Add(order);
    await db.SaveChangesAsync();
    return TypedResults.Created($"/api/orders/{order.Id}", order);
});
```

### FluentValidation Endpoint Filter

For reusable validation across multiple endpoints, create a generic endpoint filter (see also [skill:dotnet-minimal-apis] for filter pipeline details):

```csharp
public sealed class FluentValidationFilter<T>(IValidator<T> validator) : IEndpointFilter
    where T : class
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var argument = context.Arguments.OfType<T>().FirstOrDefault();
        if (argument is null)
            return TypedResults.BadRequest("Request body is required");

        var result = await validator.ValidateAsync(argument);
        if (!result.IsValid)
            return TypedResults.ValidationProblem(result.ToDictionary());

        return await next

Related in General