IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| Dry Run in Program | JIT vs AOT in .NET Core | |
API Error Handling Best Practices in .NET Core |
Robust error handling in APIs isn’t just a technical necessity; it’s a contract of clarity between your backend and its consumers. Here’s a distilled guide to best practices, tailored for ASP.NET Core Web APIs and extensible to broader RESTful design:
Ensure your API communicates intent clearly:
| Status Code | Meaning |
|---|---|
| 200 OK | Success |
| 400 Bad Request | Invalid input from client |
| 401 Unauthorized | Missing/invalid credentials |
| 403 Forbidden | Authenticated but not allowed |
| 404 Not Found | Resource doesn’t exist |
| 500 Internal Server Error | Server-side failure |
Avoid using 200 OK for failed operations—it misleads clients.
Structure your error payloads predictably:
{
"status": 400,
"error": "BadRequest",
"message": "Product name is required.",
"details": [
{
"field": "Name",
"issue": "Must be between 3 and 50 characters."
}
],
"timestamp": "2025-09-19T12:15:00Z",
"requestId": "abc123"
}
This helps consumers parse and log errors uniformly.
Use Middleware or ExceptionFilter to catch unhandled exceptions:
public class GlobalExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
public GlobalExceptionHandlerMiddleware(RequestDelegate next) => _next = next;
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var errorResponse = new
{
status = 500,
error = "InternalServerError",
message = ex.Message,
traceId = context.TraceIdentifier
};
await context.Response.WriteAsJsonAsync(errorResponse);
}
}
}
Register it in Startup.cs or Program.cs:
app.UseMiddleware<GlobalExceptionHandlerMiddleware>();
Never expose stack traces, database errors, or internal logic in production. Use generic messages like:
“An unexpected error occurred. Please contact support.”
Log the full details internally, but sanitize the client-facing response.
Include error codes, formats, and remediation steps in your API docs. Example:
### Error Codes
- ERR001: Invalid input format
- ERR002: Unauthorized access
- ERR003: Resource not found
This empowers consumers to handle errors gracefully.
Validate early (e.g., model validation) and return meaningful errors. Don’t let bad data propagate.
Proper error handling ensures that your API provides clear, consistent, and secure error responses to clients. This improves the developer experience, application reliability, and system security.
400 Bad Request with validation details for invalid input.ProblemDetails for a standard error contract.| Error Type | HTTP Status Code | Recommended Response |
|---|---|---|
| Validation Error | 400 Bad Request | {"error":"Invalid input","details":"Name field is required"} |
| Unauthorized | 401 Unauthorized | {"error":"Unauthorized","details":"Valid authentication required"} |
| Forbidden | 403 Forbidden | {"error":"Forbidden","details":"You do not have access to this resource"} |
| Resource Not Found | 404 Not Found | {"error":"Not Found","details":"The requested item was not found"} |
| Conflict | 409 Conflict | {"error":"Conflict","details":"Duplicate entry detected"} |
| Server Error | 500 Internal Server Error | {"error":"Unexpected error","details":"An unexpected error occurred"} |
using Microsoft.AspNetCore.Http;
using System;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError; // Default 500
var result = JsonSerializer.Serialize(new
{
error = ex.Message,
details = "An unexpected error occurred. Please try again later."
});
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
{
"error": "Object reference not set to an instance of an object.",
"details": "An unexpected error occurred. Please try again later."
}
| Dry Run in Program | JIT vs AOT in .NET Core | |