✓
Home›Tutorials›.NET & ASP.NET Core›ASP.NET Core 10 Validation and Error Handling: Consistent Problem Details
.NET & ASP.NET Core4 min read

ASP.NET Core 10 Validation and Error Handling: Consistent Problem Details

Build a .NET 10 Minimal API that validates input automatically and returns safe, consistent problem-details responses for client and server failures.

ASP.NET Core 10 Validation and Error Handling: Consistent Problem Details

Welcome to Lesson 5 of the .NET & ASP.NET Core track. In the previous lesson, you secured a Minimal API with JWT bearer authentication. Now you will prevent invalid input from reaching business code and return useful, consistent errors without exposing implementation details.

This lesson continues the API created in Lesson 4: authentication and authorization with JWT bearer tokens. You can also review the persistent-data setup in Lesson 3: EF Core with SQLite.

What you will build

You will add data-annotation validation to a request model, enable the .NET 10 Minimal API validation pipeline, and configure problem-details responses for unhandled failures and empty error responses. A malformed request should produce a structured 400 response before its endpoint handler runs; an unexpected server fault should produce a safe 500 response in production.

Prerequisites

  • .NET 10 SDK and the Minimal API from the preceding lessons.
  • A working authenticated endpoint or a route you can safely extend.
  • Basic familiarity with HTTP status codes and JSON.

Why validation and exception handling are separate

Validation answers “is this client input acceptable?” and normally maps to a 400 response. Exception handling answers “did the server fail unexpectedly?” and normally maps to a 500 response. Do not throw exceptions for predictable invalid input, and do not return raw exception text to clients.

In .NET 10 Minimal APIs, AddValidation() enables automatic validation for supported parameters before the handler runs. AddProblemDetails() registers the default problem-details service, which can generate standardized error payloads for supported API error paths.

Configure the application

Replace the relevant parts of Program.cs with this working example. The endpoint remains protected, validation happens before business logic, and the development exception page stays limited to development.

using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
builder.Services.AddValidation();
builder.Services.AddProblemDetails();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();
app.UseAuthentication();
app.UseAuthorization();

app.MapPost("/todos", (CreateTodoRequest request) =>
{
    var todo = new Todo(Guid.NewGuid(), request.Title.Trim(), request.DueDate);
    return Results.Created($"/todos/{todo.Id}", todo);
}).RequireAuthorization();

app.Run();

public sealed record CreateTodoRequest(
    [property: Required]
    [property: StringLength(120, MinimumLength = 3)]
    string Title,
    [property: FutureDate]
    DateOnly DueDate);

public sealed record Todo(Guid Id, string Title, DateOnly DueDate);

public sealed class FutureDateAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(
        object? value,
        ValidationContext validationContext)
    {
        return value is DateOnly dueDate && dueDate > DateOnly.FromDateTime(DateTime.UtcNow)
            ? ValidationResult.Success
            : new ValidationResult("DueDate must be in the future.");
    }
}

Understand the response contract

Send a JSON request with a missing title or a past due date. The validation system returns 400 with error details instead of entering the route handler. Clients should use the HTTP status and problem-details fields, not parse a human sentence. Exact property ordering and wording are not a stable client contract; document any error codes you deliberately add.

curl --request POST "https://localhost:5001/todos"   --header "Authorization: Bearer YOUR_DEVELOPMENT_TOKEN"   --header "Content-Type: application/json"   --data '{"title":"","dueDate":"2020-01-01"}'

For an unexpected exception in production, UseExceptionHandler() works with the problem-details service to generate a safe error response when it can negotiate a supported JSON content type. Log the exception on the server with request correlation data; do not put stack traces, connection strings, tokens, or SQL text in the client payload.

Common mistakes

  • Forgetting AddValidation(): annotations alone do not enable automatic Minimal API validation. Invalid input reaches the handler.
  • Using the developer exception page in production: it can reveal sensitive request and stack information.
  • Calling external services before input validation: validate boundary data first so bad requests do not consume scarce work.
  • Making clients depend on prose: use status codes and a documented, stable error field when machine behavior needs to branch.
  • Returning 500 for bad input: reserve 500 responses for server failures the client cannot correct directly.

Validate your work

  1. Run the application in Development and submit an invalid POST /todos request. Confirm it returns 400 and the handler does not create a todo.
  2. Submit a valid title and future date with a valid token. Confirm a 201 response and a Location header.
  3. Temporarily throw an exception inside the handler in a non-development environment. Confirm the client receives a safe error response, while server logs hold the diagnostic detail. Remove the temporary throw afterwards.
  4. Test an unauthenticated request separately. It should be rejected by authorization, which is different from validation.

Production checklist

  • Enable AddValidation() and keep request DTOs focused on input shape.
  • Use AddProblemDetails(), UseExceptionHandler() outside Development, and structured server logging.
  • Validate business rules close to the domain when they require database state; annotations cannot safely replace every business invariant.
  • Use integration tests for invalid, valid, unauthorized, and unexpected-failure paths.
  • Review returned error fields as part of your public API contract.

Next lesson

Next, you will add automated tests for the API: endpoint behavior, authentication boundaries, and database-backed paths. Continue following the .NET & ASP.NET Core learning track for the complete sequence.

Sources