.NET Core / Web API71 min total · 19 parts
Building REST APIs with ASP.NET Core: Routing, Middleware, and Dependency Injection
Part 14 of 19 · ~1 min
Error Handling and ProblemDetails
An unhandled exception's raw message making it back to a caller leaks internals — a connection string embedded in an error, an internal type name, a stack trace — and hands the client nothing structured to actually act on. Bench centralizes this into exception-handling middleware:
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage(); // full detail — local development only
}
else
{
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/problem+json";
await context.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = 500,
Title = "Something went wrong on Bench's end.",
// deliberately no exception message or stack trace sent to the client
});
});
});
}
[ApiController]'s own validation failures already come back shaped as ProblemDetails, at no extra cost — carrying that same shape over to unhandled exceptions means a caller who already wrote code to read a 400 doesn't need a separate code path for a 500. A bad CreateReservationRequest and something genuinely broken on Bench's end read like siblings on the wire, not like two unrelated APIs happened to share a domain.
The reservation conflict from a few chapters back deserves a proper ProblemDetails body too, once it's caught correctly:
if (conflict) return Conflict(new ProblemDetails
{
Status = 409,
Title = "Reservation conflict",
Detail = "This tool is already booked for an overlapping window."
});
That's what a 409 should have looked like for Marcus and Yuki, and it's the shape Bench's kiosk UI is actually built to display — a real message, not a silent success screen.