DotNetApiErrors 1.0.0
dotnet add package DotNetApiErrors --version 1.0.0
NuGet\Install-Package DotNetApiErrors -Version 1.0.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DotNetApiErrors" Version="1.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DotNetApiErrors" Version="1.0.0" />
<PackageReference Include="DotNetApiErrors" />
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DotNetApiErrors --version 1.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: DotNetApiErrors, 1.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DotNetApiErrors@1.0.0
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DotNetApiErrors&version=1.0.0
#tool nuget:?package=DotNetApiErrors&version=1.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
DotNetApiErrors
Global error handling middleware for consistent API error responses, proper exception logging, and error code management.
Problems Solved
- Try/catch everywhere: Repetitive error handling code in every controller
- No global exception handling: Exceptions bubble up as 500 errors without context
- Inconsistent error responses: Different error formats across endpoints
- Stack traces leaked: Stack traces exposed to clients in production
- No error codes: Clients can't programmatically handle specific errors
- Swallowed exceptions: Exceptions logged but not rethrown, losing context
- Logging without rethrowing: Errors logged but exception chain broken
- Retry on non-retriable errors: Retrying errors that will never succeed
- Exception to HTTP status mapping: Wrong HTTP status codes for exceptions
- Domain vs system exceptions: No distinction between business and system errors
Installation
dotnet add package DotNetApiErrors
Quick Start
1. Global Exception Handler
Problem: Exceptions return generic 500 errors without context or error codes.
using DotNetApiErrors;
// ✅ GOOD: Add global exception handler
app.UseGlobalExceptionHandler(options =>
{
// Map domain exceptions to HTTP status codes
options.MapException<ValidationException>(HttpStatusCode.BadRequest, "VALIDATION_ERROR");
options.MapException<NotFoundException>(HttpStatusCode.NotFound, "NOT_FOUND");
options.MapException<UnauthorizedException>(HttpStatusCode.Unauthorized, "UNAUTHORIZED");
// Hide stack traces in production
options.HideStackTraceInProduction = true;
options.IncludeDetails = true;
});
2. Consistent Error Response Format
Problem: Different endpoints return errors in different formats.
// ✅ GOOD: All errors return consistent format
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input provided",
"details": {
"field": "email",
"reason": "Invalid email format"
}
}
}
3. Domain Exceptions
Problem: No distinction between business logic errors and system errors.
// ✅ GOOD: Use domain exceptions
public class OrderService
{
public async Task<Order> GetOrderAsync(int orderId)
{
var order = await _repository.FindAsync(orderId);
if (order == null)
{
throw new NotFoundException($"Order {orderId} not found");
}
return order;
}
public async Task CreateOrderAsync(CreateOrderRequest request)
{
if (request.Items.Count == 0)
{
throw new ValidationException(
"Order must have at least one item",
new Dictionary<string, string[]>
{
["Items"] = new[] { "At least one item is required" }
}
);
}
// Create order...
}
}
4. Exception Extensions
Problem: Exceptions logged but not rethrown, or retried when they shouldn't be.
// ✅ GOOD: Log and rethrow properly
try
{
await ProcessOrderAsync();
}
catch (Exception ex)
{
// Log and rethrow to maintain exception chain
ex.LogAndRethrow(logger, rethrow: true);
}
// ✅ GOOD: Check if exception is retriable
if (exception.IsRetriable())
{
await RetryOperationAsync();
}
else
{
throw; // Don't retry non-retriable errors
}
Real-World Example
// Program.cs
var app = builder.Build();
// Configure global exception handler
app.UseGlobalExceptionHandler(options =>
{
// Map business exceptions
options.MapException<ValidationException>(HttpStatusCode.BadRequest, "VALIDATION_ERROR");
options.MapException<NotFoundException>(HttpStatusCode.NotFound, "NOT_FOUND");
options.MapException<UnauthorizedException>(HttpStatusCode.Unauthorized, "UNAUTHORIZED");
options.MapException<PaymentException>(HttpStatusCode.PaymentRequired, "PAYMENT_REQUIRED");
// Production settings
options.HideStackTraceInProduction = true;
options.IncludeDetails = true;
});
// Controller
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
[HttpGet("{id}")]
public async Task<ActionResult<Order>> GetOrder(int id)
{
// Domain exception automatically converted to proper HTTP response
var order = await _orderService.GetOrderAsync(id);
return Ok(order);
}
[HttpPost]
public async Task<ActionResult<Order>> CreateOrder(CreateOrderRequest request)
{
// Validation exception automatically returns 400 with error details
var order = await _orderService.CreateOrderAsync(request);
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}
}
// Service
public class OrderService : IOrderService
{
public async Task<Order> GetOrderAsync(int id)
{
var order = await _repository.FindAsync(id);
if (order == null)
{
throw new NotFoundException($"Order {id} not found");
}
return order;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// Validate
if (string.IsNullOrWhiteSpace(request.CustomerEmail))
{
throw new ValidationException(
"Customer email is required",
new Dictionary<string, string[]>
{
["CustomerEmail"] = new[] { "Email is required" }
}
);
}
// Business logic...
return await _repository.CreateAsync(order);
}
}
Error Response Examples
Validation Error (400)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input provided",
"details": {
"Email": ["Invalid email format"],
"Age": ["Age must be between 18 and 100"]
}
}
}
Not Found Error (404)
{
"error": {
"code": "NOT_FOUND",
"message": "Order 123 not found"
}
}
Unauthorized Error (401)
{
"error": {
"code": "UNAUTHORIZED",
"message": "Unauthorized access"
}
}
Internal Server Error (500) - Production
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An error occurred while processing your request."
}
}
Internal Server Error (500) - Development
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Object reference not set to an instance of an object.",
"details": {
"type": "NullReferenceException",
"stackTrace": "..."
}
}
}
Best Practices
- Use domain exceptions (
ValidationException,NotFoundException, etc.) for business logic errors - Map exceptions to HTTP status codes in the global handler
- Hide stack traces in production but include them in development
- Use error codes for programmatic error handling by clients
- Log and rethrow exceptions to maintain exception chain
- Check if exceptions are retriable before retrying operations
- Use consistent error format across all endpoints
API Reference
GlobalExceptionHandlerMiddleware- Middleware for global exception handlingDomainException- Base class for domain exceptionsValidationException- Exception for validation errorsNotFoundException- Exception for not found errorsUnauthorizedException- Exception for unauthorized accessExceptionExtensions- Extension methods for exception handling
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 119 | 12/29/2025 |
Initial release. See README for details.