coder-csharp-error-handling

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
<skill_overview> <purpose>Implement robust error handling following modern .NET patterns</purpose> <triggers> <trigger>Handling exceptions in services</trigger> <trigger>Implementing validation logic</trigger> <trigger>Designing API error responses</trigger> <trigger>Choosing Result pattern vs exceptions</trigger> <trigger>Setting up global exception handling</trigger> </triggers> <sources> <source url="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/">Microsoft Exception Handling</source> </sources> </skill_overview> <exceptions_vs_result> <use_exceptions_when> <case>Truly exceptional, unexpected failures</case> <case>System-level errors (I/O, network, hardware)</case> <case>In constructors (can't return Result)</case> <case>Framework boundaries expecting exceptions</case> <case>Programming errors (null, out of range)</case> </use_exceptions_when> <use_result_when> <case>Expected business failures (validation, not found)</case> <case>Multiple possible failure modes</case> <case>Performance-critical hot paths</case> <case>Domain layer business logic</case> <case>Compile-time safety for error handling</case> </use_result_when> <example_comparison> <code> // Exception: Unexpected failure if (connection == null) throw new InvalidOperationException("Connection not initialized"); // Result: Expected business outcome public Result<User> GetUser(int id) { var user = _repository.Find(id); if (user == null) return Result.Fail("User not found"); // Expected case return Result.Ok(user); } </code> </example_comparison> </exceptions_vs_result> <exception_best_practices> <throwing> <rule>Throw for truly exceptional conditions only</rule> <rule>Use specific exception types</rule> <rule>Include context in exception message</rule> <example> <code> // Good: Specific exception with context throw new ArgumentNullException(nameof(email), "Email is required for user creation"); // Good: Use built-in guard methods (.NET 6+) ArgumentNullException.ThrowIfNull(user); ArgumentException.ThrowIfNullOrEmpty(email); // Bad: Generic exception throw new Exception("Error occurred"); </code> </example> </throwing> <catching> <rule>Catch specific exceptions first, general last</rule> <rule>Use exception filters (when clause) for fine-grained control</rule> <rule>Always use "throw;" not "throw ex;" to preserve stack trace</rule> <example> <code> try { await ProcessOrderAsync(order); } catch (ValidationException ex) { _logger.LogWarning(ex, "Validation failed for order {OrderId}", order.Id); return BadRequest(ex.Errors); } catch (NotFoundException ex) { return NotFound(ex.Message); } catch (Exception ex) when (ex is TimeoutException or HttpRequestException) { _logger.LogWarning(ex, "Transient error, will retry"); throw; // Preserve stack trace! } catch (Exception ex) { _logger.LogError(ex, "Unexpected error processing order {OrderId}", order.Id); throw; // Never "throw ex;" } </code> </example> </catching> <custom_exceptions> <template> <code> public class OrderProcessingException : Exception { public string OrderId { get; } public string ErrorCode { get; }
public OrderProcessingException(string orderId, string message, string errorCode)
    : base(message)
{
    OrderId = orderId;
    ErrorCode = errorCode;
}

public OrderProcessingException(string orderId, string message, Exception inner)
    : base(message, inner)
{
    OrderId = orderId;
}
} </code> </template> </custom_exceptions> </exception_best_practices> <result_pattern> <libraries> <library name="FluentResults">Rich API, multiple errors, chaining</library> <library name="ErrorOr">Clean fluent API, functional style</library> <library name="OneOf">Discriminated unions, exhaustive matching</library> </libraries> <erroror_example> <code> public async Task<ErrorOr<Order>> CreateOrderAsync(CreateOrderCommand cmd) { if (cmd.Items.Count == 0) return Error.Validation("Order must have items");
var customer = await _customerRepo.FindAsync(cmd.CustomerId);
if (customer == null)
    return Error.NotFound("Customer not found");

if (!customer.IsActive)
    return Error.Validation("Customer account is inactive");

var order = new Order(cmd);
await _orderRepo.AddAsync(order);

return order;
} // Controller usage [HttpPost] public async Task<IActionResult> CreateOrder(CreateOrderDto dto) { var result = await _orderService.CreateOrderAsync(dto.ToCommand());
return result.Match(
    order =&gt; Ok(new OrderDto(order)),
    errors =&gt; Problem(errors.First().Description)
);
} </code> </erroror_example> <chaining> <code> public async Task<ErrorOr<OrderResult>> ProcessOrderAsync(int orderId) { return await ValidateOrderAsync(orderId) .Then(order => CalculatePriceAsync(order)) .Then(order => ProcessPaymentAsync(order)) .Then(order => SendConfirmationAsync(order)); } </code> </chaining> </result_pattern> <validation> <fluentvalidation recommended="true"> <description>Powerful, fluent validation with separation of concerns</description> <example> <code> public class CreateUserValidator : AbstractValidator<CreateUserDto> { public CreateUserValidator(IUserRepository repository) { RuleFor(x => x.Email) .NotEmpty().WithMessage("Email is required") .EmailAddress().WithMessage("Invalid email format") .MustAsync(async (email, ct) => !await repository.ExistsAsync(email)) .WithMessage("Email already registered");
    RuleFor(x =&gt; x.Password)
        .NotEmpty()
        .MinimumLength(8)
        .Matches("[A-Z]").WithMessage("Must contain uppercase")
        .Matches("[a-z]").WithMessage("Must contain lowercase")
        .Matches("[0-9]").WithMessage("Must contain number");
    
    RuleFor(x =&gt; x.Age)
        .InclusiveBetween(18, 120);
}
} </code> </example> </fluentvalidation> <data_annotations> <description>Simple validation with attributes</description> <use_for>DTOs, simple input validation</use_for> <example> <code> public class CreateUserDto { [Required] [EmailAddress] public string Email { get; init; }
[Required]
[MinLength(8)]
public string Password { get; init; }

[Range(18, 120)]
public int Age { get; init; }
} </code> </example> </data_annotations> <validation_layers> <layer name="Controller">Input format validation (DataAnnotations)</layer> <layer name="Service">Business rules (FluentValidation)</layer> <layer name="Domain">Invariants (guard clauses, exceptions)</layer> </validation_layers> </validation> <aspnetcore_error_handling> <iexceptionhandler recommended="true"> <description>Global exception handling (.NET 8+)</description> <example> <code> public class GlobalExceptionHandler : IExceptionHandler { private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger&lt;GlobalExceptionHandler&gt; logger)
{
    _logger = logger;
}

public async ValueTask&lt;bool&gt; TryHandleAsync(
    HttpContext httpContext,
    Exception exception,
    CancellationToken ct)
{
    _logger.LogError(exception, "Unhandled exception: {Message}", exception.Message);
    
    var problemDetails = exception switch
    {
        ValidationException ex =&gt; new ProblemDetails
        {
            Type = "https://example.com/validation-error",
            Title = "Validation Error",
            Status = 400,
            Detail = ex.Message
        },
        NotFoundException =&gt; new ProblemDetails
        {
            Type = "https://example.com/not-found",
            Title = "Not Found",
            Status = 404
        },
        _ =&gt; new ProblemDetails
        {
            Type = "https://example.com/internal-error",
            Title = "Internal Server Error",
            Status = 500,
            Detail = "An unexpected error occurred"
        }
    };
    
    problemDetails.Extensions["traceId"] = httpContext.TraceIdentifier;
    
    httpContext.Response.StatusCode = problemDetails.Status ?? 500;
    await httpContext.Response.WriteAsJsonAsync(problemDetails, ct);
    
    return true;
}
} // Registration builder.Services.AddProblemDetails(); builder.Services.AddExceptionHandler<GlobalExceptionHandler>(); app.UseExceptionHandler(); </code> </example> </iexceptionhandler> <problem_details> <description>RFC 7807 standard error format</description> <format> <code> { "type": "https://example.com/validation-error", "title": "Validation Error", "status": 400, "detail": "Email is required", "instance": "/api/users", "traceId": "00-abc123..." } </code> </format> </problem_details> </aspnetcore_error_handling> <logging_errors> <rules> <rule>Always include exception object: LogError(ex, "message")</rule> <rule>Add context: orderId, userId, operation name</rule> <rule>Use structured logging with templates</rule> <rule>Include correlation ID for tracing</rule> </rules> <example> <code> try { await ProcessOrderAsync(order); } catch (Exception ex) { _logger.LogError(ex, "Failed to process order {OrderId} for customer {CustomerId}. Amount: {Amount}", order.Id, order.CustomerId, order.TotalAmount); throw; } </code> </example> <never_log> <item>Passwords (even hashed)</item> <item>Credit card numbers</item> <item>API keys and tokens</item> <item>Personal identification numbers</item> </never_log> </logging_errors> <anti_patterns> <avoid name="empty_catch"> <bad>catch (Exception) { }</bad> <why>Swallows errors silently</why> </avoid> <avoid name="catch_and_throw_new"> <bad>catch (Exception ex) { throw new Exception("Error", ex); }</bad> <why>Loses original exception type</why> </avoid> <avoid name="throw_ex"> <bad>throw ex;</bad> <why>Resets stack trace</why> <good>throw;</good> </avoid> <avoid name="exception_for_flow_control"> <bad>Using exceptions for expected business logic</bad> <why>Expensive, unclear intent</why> <good>Use Result pattern for expected failures</good> </avoid> </anti_patterns>
<skill_overview> <purpose>遵循现代.NET模式实现健壮的错误处理</purpose> <triggers> <trigger>在服务中处理异常</trigger> <trigger>实现验证逻辑</trigger> <trigger>设计API错误响应</trigger> <trigger>选择Result模式还是异常</trigger> <trigger>设置全局异常处理</trigger> </triggers> <sources> <source url="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/">Microsoft异常处理文档</source> </sources> </skill_overview> <exceptions_vs_result> <use_exceptions_when> <case>真正异常、意外的故障</case> <case>系统级错误(I/O、网络、硬件)</case> <case>构造函数中(无法返回Result)</case> <case>框架边界预期使用异常的场景</case> <case>编程错误(空值、超出范围)</case> </use_exceptions_when> <use_result_when> <case>预期的业务失败(验证失败、资源未找到)</case> <case>存在多种可能的失败模式</case> <case>性能关键的热路径</case> <case>领域层业务逻辑</case> <case>错误处理的编译时安全性</case> </use_result_when> <example_comparison> <code> // Exception: Unexpected failure if (connection == null) throw new InvalidOperationException("Connection not initialized"); // Result: Expected business outcome public Result<User> GetUser(int id) { var user = _repository.Find(id); if (user == null) return Result.Fail("User not found"); // Expected case return Result.Ok(user); } </code> </example_comparison> </exceptions_vs_result> <exception_best_practices> <throwing> <rule>仅针对真正异常的情况抛出异常</rule> <rule>使用特定的异常类型</rule> <rule>在异常消息中包含上下文信息</rule> <example> <code> // Good: Specific exception with context throw new ArgumentNullException(nameof(email), "Email is required for user creation"); // Good: Use built-in guard methods (.NET 6+) ArgumentNullException.ThrowIfNull(user); ArgumentException.ThrowIfNullOrEmpty(email); // Bad: Generic exception throw new Exception("Error occurred"); </code> <comments> <comment>// 良好实践:带上下文的特定异常</comment> <comment>// 良好实践:使用内置守卫方法(.NET 6+)</comment> <comment>// 不良实践:通用异常</comment> </comments> </example> </throwing> <catching> <rule>先捕获特定异常,最后捕获通用异常</rule> <rule>使用异常过滤器(when子句)实现细粒度控制</rule> <rule>始终使用"throw;"而非"throw ex;"以保留堆栈跟踪</rule> <example> <code> try { await ProcessOrderAsync(order); } catch (ValidationException ex) { _logger.LogWarning(ex, "Validation failed for order {OrderId}", order.Id); return BadRequest(ex.Errors); } catch (NotFoundException ex) { return NotFound(ex.Message); } catch (Exception ex) when (ex is TimeoutException or HttpRequestException) { _logger.LogWarning(ex, "Transient error, will retry"); throw; // Preserve stack trace! } catch (Exception ex) { _logger.LogError(ex, "Unexpected error processing order {OrderId}", order.Id); throw; // Never "throw ex;" } </code> </example> </catching> <custom_exceptions> <template> <code> public class OrderProcessingException : Exception { public string OrderId { get; } public string ErrorCode { get; }
public OrderProcessingException(string orderId, string message, string errorCode)
    : base(message)
{
    OrderId = orderId;
    ErrorCode = errorCode;
}

public OrderProcessingException(string orderId, string message, Exception inner)
    : base(message, inner)
{
    OrderId = orderId;
}
} </code> </template> </custom_exceptions> </exception_best_practices> <result_pattern> <libraries> <library name="FluentResults">丰富的API、支持多错误、链式调用</library> <library name="ErrorOr">简洁的流畅API、函数式风格</library> <library name="OneOf">区分联合类型、穷尽匹配</library> </libraries> <erroror_example> <code> public async Task<ErrorOr<Order>> CreateOrderAsync(CreateOrderCommand cmd) { if (cmd.Items.Count == 0) return Error.Validation("Order must have items");
var customer = await _customerRepo.FindAsync(cmd.CustomerId);
if (customer == null)
    return Error.NotFound("Customer not found");

if (!customer.IsActive)
    return Error.Validation("Customer account is inactive");

var order = new Order(cmd);
await _orderRepo.AddAsync(order);

return order;
} // Controller usage [HttpPost] public async Task<IActionResult> CreateOrder(CreateOrderDto dto) { var result = await _orderService.CreateOrderAsync(dto.ToCommand());
return result.Match(
    order =&gt; Ok(new OrderDto(order)),
    errors =&gt; Problem(errors.First().Description)
);
} </code> </erroror_example> <chaining> <code> public async Task<ErrorOr<OrderResult>> ProcessOrderAsync(int orderId) { return await ValidateOrderAsync(orderId) .Then(order => CalculatePriceAsync(order)) .Then(order => ProcessPaymentAsync(order)) .Then(order => SendConfirmationAsync(order)); } </code> </chaining> </result_pattern> <validation> <fluentvalidation recommended="true"> <description>功能强大的流畅式验证,关注点分离</description> <example> <code> public class CreateUserValidator : AbstractValidator<CreateUserDto> { public CreateUserValidator(IUserRepository repository) { RuleFor(x => x.Email) .NotEmpty().WithMessage("Email is required") .EmailAddress().WithMessage("Invalid email format") .MustAsync(async (email, ct) => !await repository.ExistsAsync(email)) .WithMessage("Email already registered");
    RuleFor(x =&gt; x.Password)
        .NotEmpty()
        .MinimumLength(8)
        .Matches("[A-Z]").WithMessage("Must contain uppercase")
        .Matches("[a-z]").WithMessage("Must contain lowercase")
        .Matches("[0-9]").WithMessage("Must contain number");
    
    RuleFor(x =&gt; x.Age)
        .InclusiveBetween(18, 120);
}
} </code> </example> </fluentvalidation> <data_annotations> <description>使用特性的简单验证</description> <use_for>DTO、简单输入验证</use_for> <example> <code> public class CreateUserDto { [Required] [EmailAddress] public string Email { get; init; }
[Required]
[MinLength(8)]
public string Password { get; init; }

[Range(18, 120)]
public int Age { get; init; }
} </code> </example> </data_annotations> <validation_layers> <layer name="Controller">输入格式验证(DataAnnotations)</layer> <layer name="Service">业务规则(FluentValidation)</layer> <layer name="Domain">不变量检查(守卫子句、异常)</layer> </validation_layers> </validation> <aspnetcore_error_handling> <iexceptionhandler recommended="true"> <description>全局异常处理(.NET 8+)</description> <example> <code> public class GlobalExceptionHandler : IExceptionHandler { private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger&lt;GlobalExceptionHandler&gt; logger)
{
    _logger = logger;
}

public async ValueTask&lt;bool&gt; TryHandleAsync(
    HttpContext httpContext,
    Exception exception,
    CancellationToken ct)
{
    _logger.LogError(exception, "Unhandled exception: {Message}", exception.Message);
    
    var problemDetails = exception switch
    {
        ValidationException ex =&gt; new ProblemDetails
        {
            Type = "https://example.com/validation-error",
            Title = "Validation Error",
            Status = 400,
            Detail = ex.Message
        },
        NotFoundException =&gt; new ProblemDetails
        {
            Type = "https://example.com/not-found",
            Title = "Not Found",
            Status = 404
        },
        _ =&gt; new ProblemDetails
        {
            Type = "https://example.com/internal-error",
            Title = "Internal Server Error",
            Status = 500,
            Detail = "An unexpected error occurred"
        }
    };
    
    problemDetails.Extensions["traceId"] = httpContext.TraceIdentifier;
    
    httpContext.Response.StatusCode = problemDetails.Status ?? 500;
    await httpContext.Response.WriteAsJsonAsync(problemDetails, ct);
    
    return true;
}
} // Registration builder.Services.AddProblemDetails(); builder.Services.AddExceptionHandler<GlobalExceptionHandler>(); app.UseExceptionHandler(); </code> </example> </iexceptionhandler> <problem_details> <description>RFC 7807标准错误格式</description> <format> <code> { "type": "https://example.com/validation-error", "title": "Validation Error", "status": 400, "detail": "Email is required", "instance": "/api/users", "traceId": "00-abc123..." } </code> </format> </problem_details> </aspnetcore_error_handling> <logging_errors> <rules> <rule>始终包含异常对象:LogError(ex, "message")</rule> <rule>添加上下文信息:orderId、userId、操作名称</rule> <rule>使用带模板的结构化日志</rule> <rule>包含用于追踪的关联ID</rule> </rules> <example> <code> try { await ProcessOrderAsync(order); } catch (Exception ex) { _logger.LogError(ex, "Failed to process order {OrderId} for customer {CustomerId}. Amount: {Amount}", order.Id, order.CustomerId, order.TotalAmount); throw; } </code> </example> <never_log> <item>密码(即使是哈希后的)</item> <item>信用卡号</item> <item>API密钥和令牌</item> <item>个人识别号码</item> </never_log> </logging_errors> <anti_patterns> <avoid name="empty_catch"> <bad>catch (Exception) { }</bad> <why>静默吞噬错误</why> </avoid> <avoid name="catch_and_throw_new"> <bad>catch (Exception ex) { throw new Exception("Error", ex); }</bad> <why>丢失原始异常类型</why> </avoid> <avoid name="throw_ex"> <bad>throw ex;</bad> <why>重置堆栈跟踪</why> <good>throw;</good> </avoid> <avoid name="exception_for_flow_control"> <bad>Using exceptions for expected business logic</bad> <why>性能开销大、意图不清晰</why> <good>Use Result pattern for expected failures</good> <comments> <comment>// 不良实践:使用异常处理预期的业务逻辑</comment> <comment>// 原因:性能开销大、意图不清晰</comment> <comment>// 良好实践:对预期失败使用Result模式</comment> </comments> </avoid> </anti_patterns>