Ответ
Обработка ошибок строится на многоуровневой стратегии, где каждый уровень отвечает за свой тип ошибок. Основной механизм — исключения (Exception).
1. Уровень бизнес-логики / сервисов (Try-Catch с конкретными исключениями)
public async Task<OperationResult> ProcessOrderAsync(Order order)
{
try
{
ValidateOrder(order); // Может выбросить ValidationException
var inventory = await _inventoryService.ReserveAsync(order.ProductId); // Может выбросить InventoryServiceException
await _paymentGateway.ChargeAsync(order); // Может выбросить PaymentGatewayException
return OperationResult.Success();
}
catch (ValidationException ex) // Ловим максимально конкретное исключение
{
_logger.LogWarning(ex, "Validation failed for order {OrderId}", order.Id);
return OperationResult.Failure("Invalid order data."); // Возвращаем понятный результат клиенту
}
catch (InventoryServiceException ex) when (ex.Code == "OUT_OF_STOCK") // Фильтр исключений (C# 6+)
{
_logger.LogInformation("Product {ProductId} is out of stock", order.ProductId);
return OperationResult.Failure("Product is out of stock.");
}
catch (Exception ex) // Общий catch — только для логирования и переброса
{
_logger.LogError(ex, "Unexpected error processing order {OrderId}", order.Id);
throw; // Перебрасываем исходное исключение на верхний уровень (Middleware)
}
}
2. Уровень доступа к данным (Использование using и finally)
public Customer GetCustomer(int id)
{
using var connection = new SqlConnection(_connectionString); // using гарантирует Dispose
try
{
connection.Open();
return connection.QuerySingle<Customer>("SELECT * FROM Customers WHERE Id = @Id", new { Id = id });
}
catch (SqlException ex) when (ex.Number == 547) // Ошибка FK-ограничения
{
throw new BusinessRuleException("Cannot delete referenced customer.", ex);
}
// finally не нужен явно, так как using его обеспечивает для освобождения connection
}
3. Глобальный уровень (Middleware в ASP.NET Core)
Перехватывает все необработанные исключения из контроллеров, логирует их и возвращает клиенту стандартизированный ответ (например, ProblemDetails).
// Program.cs
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
_logger.LogError(exception, "Unhandled exception");
context.Response.StatusCode = exception switch
{
ValidationException => StatusCodes.Status400BadRequest,
NotFoundException => StatusCodes.Status404NotFound,
_ => StatusCodes.Status500InternalServerError
};
await context.Response.WriteAsJsonAsync(new ProblemDetails
{
Title = "An error occurred",
Detail = exception?.Message,
Status = context.Response.StatusCode
});
});
});
Ключевые принципы:
- Не глотайте исключения (
catch {}без действий илиthrow ex;(перезаписывает стек вызовов)). - Создавайте свои исключения, когда стандартных недостаточно (
public class DomainException : Exception). - Используйте
try-catchдля восстановления после ожидаемых ошибок (например, некорректный ввод). - Позволяйте исключениям всплывать для неожиданных ошибок (например, сбой БД) до глобального обработчика.