.NET多应用跨API控制器的自定义异常处理方案咨询
跨应用API调用的异常处理与标准化响应方案
现有问题分析
当前App1的Controller A调用App2的Controller B时,App2抛出的CustomException自定义消息无法直接传递到App1——默认HTTP客户端会将非成功响应包装为APIException,导致App1只能返回自身预设的通用错误信息。临时通过捕获APIException读取响应内容的方式,缺乏标准化,也无法优雅区分不同异常场景。
长期可行解决方案
1. 标准化错误响应格式(核心推荐)
为所有服务定义统一的结构化响应类,无论成功/失败都返回该格式,确保错误信息、状态码等元数据能完整传递:
通用响应类定义
public class ApiResponse<T> { public bool Success { get; set; } public T Data { get; set; } public string ErrorMessage { get; set; } public int StatusCode { get; set; } // 可选扩展:添加RequestId、Timestamp、ErrorCode等排查字段 }
App2的Controller B改造
public ActionResult<ApiResponse<ExpectedType>> GetThatData(QueryModelType model) { try { var data = _dataFetchingDAO.GetDataFromDB(model); return Ok(new ApiResponse<ExpectedType> { Success = true, Data = data, StatusCode = 200 }); } catch (CustomException customEx) { _logger.Log(customEx); return StatusCode(500, new ApiResponse<ExpectedType> { Success = false, ErrorMessage = customEx.Message, StatusCode = 500 }); } catch (Exception ex) { _logger.Log(ex); return StatusCode(500, new ApiResponse<ExpectedType> { Success = false, ErrorMessage = "App2服务内部错误", StatusCode = 500 }); } }
App1调用层(_secondaryAppWrapper)改造
public async Task<ApiResponse<ExpectedType>> GetThatData(QueryModelType model) { var httpResponse = await _httpClient.PostAsJsonAsync("/api/controllerB/getthatdata", model); var response = await httpResponse.Content.ReadFromJsonAsync<ApiResponse<ExpectedType>>(); if (!httpResponse.IsSuccessStatusCode) { response.StatusCode = (int)httpResponse.StatusCode; } return response; }
App1的Controller A处理逻辑
public async Task<ActionResult<ApiResponse<ExpectedType>>> GetData(ModelType model) { try { var apiResponse = await _dataFetchingBizLayer.GetThatDataFromApp2(model); return StatusCode(apiResponse.StatusCode, apiResponse); } catch (Exception ex) { _logger.Log(ex); return StatusCode(500, new ApiResponse<ExpectedType> { Success = false, ErrorMessage = "App1服务内部错误", StatusCode = 500 }); } }
2. 全局异常处理中间件(简化App2代码)
在App2中添加全局异常中间件,自动捕获所有异常并转换为标准化响应,无需每个Controller编写重复的try-catch:
中间件实现
public class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<ExceptionHandlingMiddleware> _logger; public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (CustomException ex) { _logger.LogError(ex, "App2自定义异常"); await WriteErrorResponse(context, ex.Message, StatusCodes.Status500InternalServerError); } catch (Exception ex) { _logger.LogError(ex, "App2未处理异常"); await WriteErrorResponse(context, "服务内部错误", StatusCodes.Status500InternalServerError); } } private static async Task WriteErrorResponse(HttpContext context, string errorMsg, int statusCode) { context.Response.ContentType = "application/json"; context.Response.StatusCode = statusCode; var errorResponse = new ApiResponse<object> { Success = false, ErrorMessage = errorMsg, StatusCode = statusCode }; await context.Response.WriteAsJsonAsync(errorResponse); } }
注册中间件(App2的Program.cs)
app.UseMiddleware<ExceptionHandlingMiddleware>();
改造后App2的Controller B可简化为:
public ActionResult<ExpectedType> GetThatData(QueryModelType model) { // 直接抛出异常,由中间件统一处理 return _dataFetchingDAO.GetDataFromDB(model); }
3. 调用层异常映射(优化App1业务逻辑)
在App1的调用层将标准化响应映射为自定义异常,让上层业务逻辑能针对性处理不同错误场景:
public async Task<ExpectedType> GetThatData(QueryModelType model) { var httpResponse = await _httpClient.PostAsJsonAsync("/api/controllerB/getthatdata", model); if (httpResponse.IsSuccessStatusCode) { var apiResponse = await httpResponse.Content.ReadFromJsonAsync<ApiResponse<ExpectedType>>(); return apiResponse.Data; } else { var errorResponse = await httpResponse.Content.ReadFromJsonAsync<ApiResponse<object>>(); var statusCode = (int)httpResponse.StatusCode; // 根据错误信息或状态码抛出对应异常 if (errorResponse?.ErrorMessage.Contains("station B") == true) { throw new App2StationBException(errorResponse.ErrorMessage, statusCode); } else { throw new ApiCallException($"调用App2失败: {errorResponse?.ErrorMessage}", statusCode); } } }
App1的Controller A可针对性捕获异常:
public async Task<ActionResult<ExpectedType>> GetData(ModelType model) { try { return await _dataFetchingBizLayer.GetThatDataFromApp2(model); } catch (App2StationBException ex) { _logger.Log(ex); return StatusCode(ex.StatusCode, ex.Message); } catch (ApiCallException ex) { _logger.Log(ex); return StatusCode(ex.StatusCode, ex.Message); } catch (Exception ex) { _logger.Log(ex); return StatusCode(500, "App1服务内部错误"); } }
自定义响应类方案的核心优势
你考虑的自定义响应类是非常适合长期落地的方案,核心优势包括:
- 标准化统一:所有服务响应格式一致,调用方无需适配多种结构
- 扩展性强:可轻松添加请求ID、时间戳等元数据,便于问题排查
- 兼容性好:成功/失败响应使用同一解析逻辑,降低出错概率
- 语义清晰:明确区分成功/失败状态,避免错误数据被当作正常数据处理
总结
优先推荐标准化错误响应格式+全局异常中间件的组合方案,既能统一处理App2的异常输出,又能让App1完整获取自定义错误信息与状态码。结合调用层的异常映射,可进一步优化App1的业务逻辑处理效率,实现不同错误场景的差异化应对。
内容的提问来源于stack exchange,提问作者subparmelon
相关产品推荐
相关产品推荐

