C#

Lesson 06

Exceptions and result boundaries

Translate domain exceptions at application boundaries instead of catching everything and returning vague results.

Good Code

src/ReviewEndpoint.cs
public async Task<IResult> GetAsync(Guid id, CancellationToken cancellationToken)
{
    try
    {
        ReviewDto review = await reviews.GetRequiredAsync(id, cancellationToken);
        return Results.Ok(review);
    }
    catch (ReviewNotFoundException)
    {
        // Domain exception maps to the HTTP boundary.
        return Results.NotFound();
    }
}

Bad Code

ReviewEndpoint.cs
public async Task<IResult> GetAsync(Guid id)
{
    try
    {
        return Results.Ok(await reviews.GetRequiredAsync(id));
    }
    catch (Exception)
    {
        // Catching every exception turns many failures into 404.
        return Results.NotFound();
    }
}

Review Notes

What to review

Good Code

The good version maps a named domain exception to a 404. Other exceptions keep their meaning and can travel to logging or middleware.

Bad Code

The bad version catches every exception and returns 404. A database outage, serialization bug, or programming error becomes indistinguishable from a missing review.

Takeaways

  • C# exception handling should preserve the failure reason until a boundary can map it to HTTP, UI, or logging.