Global Error Handling

Never show NullReferenceException to users again. Return clean JSON errors.

The Problem: Controller throws exception โ†’ ASP.NET returns HTML page with full stack trace. React app crashes trying to JSON.parse() it. Users see "Something went wrong". You have no logs.

1. The Zomato Standard - What Errors Should Look Like

Bad: 500 Internal Server Error + HTML with at Zomato.API.Controllers.OrdersController.Get()

Good: 500 Internal Server Error + JSON:

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred. Please try again.",
    "traceId": "0HMVFE0A284AM:00000001"
  }
}

Why: Frontend can display message. You can search logs using traceId. No sensitive data leaked.

2. Solution: Global Exception Middleware

Catches ANY unhandled exception from controllers, filters, services.

// Middleware/GlobalExceptionHandler.cs
public class GlobalExceptionHandler : IExceptionHandler {
  private readonly ILogger _logger;
  public GlobalExceptionHandler(ILogger logger) => _logger = logger;

  public async ValueTask TryHandleAsync(
    HttpContext httpContext,
    Exception exception,
    CancellationToken cancellationToken) {

    var traceId = Activity.Current?.Id?? httpContext.TraceIdentifier;
    _logger.LogError(exception, "Unhandled exception. TraceId: {TraceId}", traceId);

    var problemDetails = new ProblemDetails {
      Status = StatusCodes.Status500InternalServerError,
      Title = "Server Error",
      Detail = "An unexpected error occurred. Please try again.",
      Extensions = { ["traceId"] = traceId }
    };

    httpContext.Response.StatusCode = problemDetails.Status.Value;
    await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
    return true; // Mark as handled
  }
}

// Program.cs -.NET 8 way
builder.Services.AddExceptionHandler();
builder.Services.AddProblemDetails(); // Standard RFC 7807 format

var app = builder.Build();
app.UseExceptionHandler(); // Must be first! Catches all errors
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

3. Handle Known Errors Differently

Not all exceptions = 500. Custom exceptions = clean codes.

// Exceptions/NotFoundException.cs
public class NotFoundException : Exception {
  public NotFoundException(string message) : base(message) {}
}

// GlobalExceptionHandler.cs - update TryHandleAsync
public async ValueTask TryHandleAsync(...) {
  var statusCode = exception switch {
    NotFoundException => StatusCodes.Status404NotFound,
    UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
    ArgumentException => StatusCodes.Status400BadRequest,
    _ => StatusCodes.Status500InternalServerError
  };

  var message = statusCode == 500
   ? "An unexpected error occurred"
    : exception.Message; // Safe to show for known errors

  var problemDetails = new ProblemDetails {
    Status = statusCode,
    Title = GetTitle(statusCode),
    Detail = message,
    Extensions = { ["traceId"] = traceId }
  };
 ...
}

Controller: throw new NotFoundException("Restaurant not found") โ†’ 404 JSON, not 500.

Career-Killer Mistake: Returning exception.Message for all errors.
new Exception("Connection string: Server=prod-db;Pwd=Secret123") โ†’ You just leaked DB password to hackers.
Fix: Only show exception.Message for custom exceptions you control. For Exception or SqlException, return generic "Server Error". Log the real message server-side.

Quick Check ๐Ÿง 

Error handling done. Next: Async & CancellationToken - Make API handle 10,000 users at once without freezing. Cancel DB queries when user closes browser. This is the performance layer. Continue โ†’

Comments on Global Error Handling (0)

No comments yet. Be the first to share your thoughts!