Skip to content

Error responses

When a deadline expires or a guard refuses a call, NResilience throws one of four exceptions. Unhandled, each becomes a 500 - the response that means "this service is broken", for failures that mean "this service's dependency is broken" or "come back later". Every service that cares writes the same try/catch, endpoint after endpoint.

NResilience.AspNetCore provides the mapping as an IExceptionHandler, so one registration covers every endpoint. It is opt-in.

Turn it on

csharp
builder.Services.AddResilienceExceptionHandler();
builder.Services.AddProblemDetails();
// ...

app.UseExceptionHandler();

AddProblemDetails() is required by the parameterless UseExceptionHandler() overload. An exception this handler does not recognize is reported unhandled, so it composes with your own handlers, registered in any order, and with MVC's exception filters - the chain of responsibility is the reason it is a handler and not a middleware.

What it maps

ExceptionResponseType
DeadlineExceededException504urn:nresilience:deadline-exceeded
AttemptTimeoutException504urn:nresilience:attempt-timeout
CallRejectedException, reason BudgetExhausted503urn:nresilience:retry-budget-exhausted
CallRejectedException, any other reason503urn:nresilience:dependency-unavailable
RateLimitedException503urn:nresilience:rate-limited

Retry-After is set when the exception carried a hint, rounded up to whole seconds. The status codes are the exception's, not the caller's: RateLimitedException defaults to 503, not 429, because a limiter in this process refusing to start a call is not the caller's fault. All four are configurable on ResilienceExceptionHandlerOptions; see AddResilienceExceptionHandler.

Read the response

The body is a problem document - type, title, status, detail from the exception's own message, instance from the request path:

json
{
  "type": "urn:nresilience:dependency-unavailable",
  "title": "Dependency Unavailable",
  "status": 503,
  "detail": "The call was rejected: DependencyUnavailable.",
  "instance": "/orders"
}

detail is the exception's own message, which names no dependency, host, or credential. What it never includes by default is how many times this service tried: IncludeAttemptDetails is off, because a public caller has no business seeing internal retry structure. Turn it on behind a gateway, or where the caller is your own dashboard.

CAUTION

IncludeAttemptDetails discloses retry structure - attempt count and elapsed time - to whoever receives the body. It is off by default; leave it off for any response a public caller can see.

Once the response has started, the status cannot be changed. The handler declines to handle the exception, and the framework aborts the connection - appending a problem document to a half-written body would produce garbage.

Go deeper

Released under the MIT License.