Custom Error Response
Albatross.Hosting includes a global exception handler that serves as a fallback for unhandled exceptions. It converts them into consistent ProblemDetails HTTP error responses. Errors should be handled explicitly in controller actions using ActionResult — the global handler exists only as a safety net for unexpected failures.
Features
- Global Exception Handling - Fallback handler converts unhandled exceptions to
ProblemDetailsresponses - ProblemDetails Support - Returns RFC 7807 compliant error responses with
application/problem+jsoncontent type - ArgumentException Handling - Automatically returns 400 Bad Request for argument validation errors
- Logging Control - Option to suppress logging for expected exceptions like ArgumentException
Handling Errors with ActionResult
Errors should be handled explicitly by returning an ActionResult. This makes the error response intentional, self-documenting, and gives full control over the status code and response body.
[HttpGet("{id}")]
public ActionResult<Item> GetItem(int id) {
if (id <= 0) {
return BadRequest(new { message = "Id must be positive" });
}
var item = _repository.Find(id);
if (item == null) {
return NotFound(new { message = $"Item {id} not found" });
}
return item;
}
[HttpPost]
public ActionResult CreateItem([FromBody] Item item) {
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
// Or use the Problem method for ProblemDetails format
return Problem(
detail: "Validation failed",
statusCode: 400,
title: "Bad Request"
);
}
Global Exception Handler (Fallback)
The global exception handler is a fallback that catches any unhandled exception that propagates out of a controller action. It is not intended as a primary error handling mechanism.
Albatross.Hosting uses ASP.NET Core's IApplicationBuilder.UseExceptionHandler middleware. When an unhandled exception occurs:
- The
ExceptionHandlerMiddlewarecatches the exception - The
GlobalExceptionHandler.Handlemethod is invoked - The handler writes a
ProblemDetailsresponse to theHttpContext
Fallback Response Format
The fallback handler returns errors using the standard ProblemDetails format with content type application/problem+json:
{
"status": 500,
"title": "An error occurred while processing your request",
"detail": "System.InvalidOperationException: The exception message",
"traceId": "00-abc123..."
}
| Property | Description |
|---|---|
status |
HTTP status code |
title |
Generic error title |
detail |
The exception type's full name followed by the exception message |
traceId |
Request trace identifier for debugging |
Status Code Mapping
| Exception Type | Status Code |
|---|---|
ArgumentException (and derived types) |
400 Bad Request |
| All other exceptions | 500 Internal Server Error |
Suppressing ArgumentException Logging
By default, ASP.NET Core logs all unhandled exceptions as errors. For public-facing APIs where ArgumentException indicates invalid client input rather than a server error, you can suppress this logging:
public class Program {
public static Task Main(string[] args) {
return new Setup(args, supressUnhandledArgumentExceptionLogging: true)
.ConfigureWebHost<MyStartup>()
.RunAsync();
}
}
This prevents ArgumentException from being logged as errors, reducing noise in your logs while still returning appropriate 400 responses to clients.
Best Practices
| Scenario | Recommended Approach |
|---|---|
| Known error conditions | Return ActionResult |
| Validation errors | Return BadRequest() |
| Not found errors | Return NotFound() |
| Unexpected errors | Let exceptions propagate (fallback returns 500) |
Avoid Anti-Patterns
- Don't rely on the global handler for expected errors - Handle errors explicitly with
ActionResult - Don't use exceptions for flow control - Exceptions should be exceptional
- Don't expose sensitive information - Be careful what you include in error messages
Sample Code
See the ExceptionTestCaseController in the Sample.WebApi project for working examples:
// Return ProblemDetails using ControllerBase.Problem()
[HttpGet("send-via-controllerBase.problem-method")]
public ActionResult UseProblem() {
return Problem(
detail: "error detail",
title: "test",
statusCode: 500,
type: "ExceptionType"
);
}
// Throw generic exception (fallback returns 500 with ProblemDetails)
[HttpGet("send-by-throwing-exception")]
public void ThrowException() {
throw new Exception("This is a test exception");
}
// Throw ArgumentException (fallback returns 400 with ProblemDetails)
[HttpGet("send-by-throwing-argument-exception")]
public void ThrowArgumentException() {
throw new ArgumentException("This is a test exception");
}