Lesson 21 / 26

Validation and Error Responses

Reject bad input with 400 and return consistent ProblemDetails errors.

Never trust input

Treat every request body as untrusted. Check required fields and ranges, and return 400 Bad Request with a clear message. ProblemDetails (RFC 9457) is the standard JSON shape for errors, so clients can parse them uniformly.

Validate, protect, verify

Bad input is rejected, protected routes check identity, and tests prove the behaviour.

Four stages: validate, authenticate, authorize, test.
Figure 7.1 — Validate, authenticate, authorize, test.

Manual validation and a global handler

Results.ValidationProblem returns a 400 with field errors. AddProblemDetails plus UseExceptionHandler turns unhandled exceptions into a safe 500 response without a stack trace.

builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();

app.MapPost("/books", (Book b) =>
{
    var errors = new Dictionary<string, string[]>();
    if (string.IsNullOrWhiteSpace(b.Title)) errors["title"] = ["Title is required."];
    if (b.Price < 0) errors["price"] = ["Price cannot be negative."];
    return errors.Count > 0 ? Results.ValidationProblem(errors) : Results.Ok(b);
});

Do not leak internals

Return friendly messages and a correlation ID, not exception text, SQL or file paths. Log the details on the server where attackers cannot read them.

Quick check: Which status code fits "your JSON has an invalid field"?

  • 500 Internal Server Error
  • 200 OK
  • 400 Bad Request
  • 301 Moved Permanently
Answer

400 Bad Request — 400 means the client sent something invalid. 500 is reserved for server faults.