4.3 KiB
4.3 KiB
title, theme, layout, class, fonts
| title | theme | layout | class | fonts | ||
|---|---|---|---|---|---|---|
| Why I Love Control Flow in C# | default | center | text-center |
|
Why I love control flow in C#
layout: center class: text-center
The false dichotomy
Team "Never Throw"
"Exceptions are expensive! Treat errors as values! Not every failure is exceptional!"
Team "Always Throw"
"Results are noisy! Just throw and let the middleware handle it! Keep the code clean!"
> Why not both?
layout: center
The sweet spot
Global Handler
Prevent crashes
Controller
Matches result
Service Layer
Returns results
Deep / Domain
Throws exceptions
layout: center
An example in a web API
layout: center
Use exceptions for context agnostic code
public async Task<User?> GetUserAsync(int userId)
{
// NOTE: EF Core might throw exceptions for connection issues, etc.
var user = await _dbContext.Users.FindAsync(userId);
// NOTE: Domain logic might throw exceptions for business rules
if (user?.IsBanned)
{
throw new UserBannedException(userId);
}
return user;
}
Service layer returns results
public async Task<Result<User>> GetUserAsync(int userId)
{
try
{
var user = await _userService.GetUserAsync(userId);
if (user is null)
{
return Result.Failure<User>(new UserNotFoundException(userId));
}
return Result.Success(user);
}
catch (UserBannedException ex)
{
return Result.Failure<User>(ex);
}
}
Match results in the controller
public static async Task<IResult> GetUserHandler(int userId, IUserService _service)
{
var result = await _service.GetUserAsync(userId);
return result.Match(
user => Ok(user),
error => error switch
{
UserNotFoundException => Results.NotFound(),
UserBannedException => Results.Forbid(),
_ => Results.InternalServerError()
}
);
}
Global handler for unexpected errors
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken
)
{
_logger.LogError(
exception,
"Exception occurred: {Message}", exception.Message
);
httpContext.Response.StatusCode =
StatusCodes.Status500InternalServerError;
await httpContext.Response
.WriteAsync("An unexpected error occurred.", cancellationToken);
return true;
}
layout: center
C# gives me the freedom to choose
- Use exceptions when deep in the call stack.
- Use result types where business logic is centralized.
- Use global handlers for unexpected exceptions and to prevent crashes.