ProblemDetails: the standard error body
“What is ProblemDetails, and how does ASP.NET Core use it?”
One agreed JSON shape for every error, so the portal and support read all errors the same way.
See it
1/5Before: each error had its own JSON. The portal needed a parser for each; some 500s had no body.
In plain words
When an API call fails, the client needs to know what went wrong, in a shape it can read. If every endpoint invents its own error JSON, the portal needs a different parser for each. ProblemDetails is a web standard (RFC 9457) for that shape: a JSON object with type (a link that names the kind of error), title (a short summary), status (the HTTP status code), detail (this case, in words) and instance (which request), sent with the content type application/problem+json. You may add your own fields, called extensions. ASP.NET Core uses it in three places. (1) [ApiController] marks a controller class as a web API and turns on automatic checks (). With it, a request that fails validation gets a 400 : the same shape plus an errors object, field name to messages. (2) In a controller with [ApiController], return NotFound() and the other error results get a ProblemDetails body too. (3) builder.Services.AddProblemDetails() makes the exception handler (the middleware that catches an unhandled exception and answers 500) answer in this shape, and, together with app.UseStatusCodePages(), also the error responses that would otherwise be empty, like a URL that matches no route. Every ProblemDetails the framework creates (the cases above) gets a traceId, the id of this request in the logs. One you build by hand in your own code does not, unless you add it. What must never go in it: stack traces, SQL, server names. Those stay in the logs, found by the id.
- ProblemDetails is RFC 9457, which replaced RFC 7807: a standard JSON error body with
type,title,status,detailandinstance, plus extension members, served asapplication/problem+json. - In ASP.NET Core,
[ApiController]returns with anerrorsdictionary for invalid models, and a ProblemDetails body for error results like . - makes the exception handler produce the same shape, and with the empty error responses too, such as an unknown URL.
- , the built-in class that writes the body, writes all of these, and lets me add fields to every one the framework creates.
- A good field to add is a correlation id, an id that tags every log line of a request. The framework already adds a
traceIdby default. - The client gets a safe title and an id; the exception details go to the logs only.
In the lending API
Turning it on in Program.cs, the model rule behind the 400, and the real 400 body from a run on .NET 10. Tap Next line.
Lending.sln · where these 3 files sit
- src/Api/
- Models/
- Models/
[ApiController] answers 400 with errors: which field, which message.[ApiController] then answer bad input with a 400 , and with a 404 ProblemDetails, by themselves.What they ask next
Interviewers check that you can explain every line. Tap a question to open it.
How do you add your own field to every ProblemDetails?
In
AddProblemDetails(options => options.CustomizeProblemDetails = ctx => ...).ctx =>is a short inline function that .NET calls for each ProblemDetails it creates: validation 400s, , the exception handler and status-code pages. A ProblemDetails you build by hand is not covered. is the dictionary of extra fields, and is a small dictionary that lives for one request, where the correlation middleware puts the id. See .builder.Services.AddProblemDetails(o => { o.CustomizeProblemDetails = ctx => ctx.ProblemDetails .Extensions["correlationId"] = ctx.HttpContext .Items["CorrelationId"]; });Should
detailhold the exception message?No. Exception messages can carry SQL, server names, file paths or customer data. Use a safe sentence you wrote yourself. In Development you may show more; in Production, never.
Traps
- Don't say
ReturnSayUse ProblemDetails, the standard, so every client and tool reads errors the same way.{ "error": "something" }in our own shape.