Web API如何按需返回正常响应或异常错误信息?
针对你遇到的Web API响应标准化问题,结合你想尽量少改动现有代码的需求,我整理了几个实用的解决方案,咱们一步步来看:
方案一:基于基类统一错误信息字段(优化你提到的思路)
这个方案的核心是给所有业务返回类加一个通用基类,不用逐个修改每个返回类的属性:
首先定义包含错误信息的基类:
public class BaseResponse { public bool IsSuccess { get; set; } = true; public string ErrorMessage { get; set; } public string ErrorCode { get; set; } // 可选,用来区分不同错误类型,方便前端精准处理 }
然后让所有业务返回类继承这个基类,比如InfoResponse和InfoRequest:
public class InfoResponse : BaseResponse { // 保留原有的业务属性 public string InfoContent { get; set; } public int InfoId { get; set; } } public class InfoRequest : BaseResponse { // 原有的请求相关属性(如果这个类作为返回值使用,调整对应字段即可) }
接下来修改中间层方法,不再返回null,而是返回带错误信息的实例:
public InfoResponse ProcessInfoResponse(InfoModel info) { try { var result = serviceLayer.Post<InfoModel>(info); if (result != null) { // 处理业务逻辑,组装正常返回数据 return new InfoResponse { InfoContent = "处理后的有效内容", InfoId = 1001 }; } else { Log.Error("第三方API返回空结果"); return new InfoResponse { IsSuccess = false, ErrorMessage = "获取信息失败:第三方API未返回有效数据", ErrorCode = "THIRD_PARTY_EMPTY" }; } } catch (Exception ex) { Log.Error("处理信息时发生异常", ex); return new InfoResponse { IsSuccess = false, ErrorMessage = $"获取信息失败:{ex.Message}", ErrorCode = "PROCESS_ERROR" }; } }
最后调整API控制器逻辑,根据IsSuccess字段返回对应状态码:
[HttpPost] public IActionResult GetInfo([FromBody] InfoModel info) { try { var result = new Info().ProcessInfoResponse(info); if (!result.IsSuccess) { // 根据错误码区分返回状态码,比如第三方错误返回400,内部错误返回500 return result.ErrorCode.StartsWith("THIRD_PARTY") ? BadRequest(result) : StatusCode(500, result); } return Ok(result); } catch (Exception e) { Log.Error("API层未捕获异常", e); return StatusCode(500, new BaseResponse { IsSuccess = false, ErrorMessage = "服务器内部错误", ErrorCode = "API_UNHANDLED" }); } }
这个方案改动量极小,只需要给返回类加继承、修改中间层的返回逻辑,完全兼容现有代码结构。
方案二:使用泛型响应包装类(侵入性更低)
如果你不想修改现有业务返回类的结构,可以用泛型包装类把业务数据和错误信息打包:
先定义泛型包装类:
public class ApiResponse<T> { public bool IsSuccess { get; set; } = true; public T Data { get; set; } public string ErrorMessage { get; set; } public string ErrorCode { get; set; } }
然后修改中间层方法的返回类型为这个泛型类:
public ApiResponse<InfoResponse> ProcessInfoResponse(InfoModel info) { try { var result = serviceLayer.Post<InfoModel>(info); if (result != null) { // 组装业务数据 var data = new InfoResponse { InfoContent = "处理后的有效内容", InfoId = 1001 }; return new ApiResponse<InfoResponse> { Data = data }; } else { Log.Error("第三方API返回空结果"); return new ApiResponse<InfoResponse> { IsSuccess = false, ErrorMessage = "获取信息失败:第三方API未返回有效数据", ErrorCode = "THIRD_PARTY_EMPTY" }; } } catch (Exception ex) { Log.Error("处理信息时发生异常", ex); return new ApiResponse<InfoResponse> { IsSuccess = false, ErrorMessage = $"获取信息失败:{ex.Message}", ErrorCode = "PROCESS_ERROR" }; } }
控制器层代码调整如下:
[HttpPost] public IActionResult GetInfo([FromBody] InfoModel info) { try { var result = new Info().ProcessInfoResponse(info); if (!result.IsSuccess) { return result.ErrorCode.StartsWith("THIRD_PARTY") ? BadRequest(result) : StatusCode(500, result); } return Ok(result); } catch (Exception e) { Log.Error("API层未捕获异常", e); return StatusCode(500, new ApiResponse<object> { IsSuccess = false, ErrorMessage = "服务器内部错误", ErrorCode = "API_UNHANDLED" }); } }
这个方案完全不用修改原有业务模型,只需要新增一个泛型类,适合不想动历史代码的场景。
方案三:自定义异常 + 全局异常过滤器(代码更简洁)
如果想让错误处理逻辑更清晰、职责更分离,可以用自定义异常配合全局过滤器,让中间层只负责业务逻辑,错误处理交给全局过滤器:
首先定义自定义业务异常:
public class BusinessException : Exception { public string ErrorCode { get; } public int StatusCode { get; } public BusinessException(string message, string errorCode, int statusCode = 400) : base(message) { ErrorCode = errorCode; StatusCode = statusCode; } }
然后修改中间层方法,遇到错误时直接抛出异常,不用返回null:
public InfoResponse ProcessInfoResponse(InfoModel info) { var result = serviceLayer.Post<InfoModel>(info); if (result != null) { // 处理业务逻辑,返回正常数据 return new InfoResponse { InfoContent = "处理后的有效内容", InfoId = 1001 }; } else { Log.Error("第三方API返回空结果"); throw new BusinessException("获取信息失败:第三方API未返回有效数据", "THIRD_PARTY_EMPTY", 400); } }
接下来创建全局异常过滤器,统一处理所有异常:
public class GlobalExceptionFilter : IExceptionFilter { private readonly ILogger<GlobalExceptionFilter> _logger; public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger) { _logger = logger; } public void OnException(ExceptionContext context) { var response = new BaseResponse { IsSuccess = false }; if (context.Exception is BusinessException businessEx) { response.ErrorMessage = businessEx.Message; response.ErrorCode = businessEx.ErrorCode; context.HttpContext.Response.StatusCode = businessEx.StatusCode; } else { response.ErrorMessage = "服务器内部错误"; response.ErrorCode = "UNHANDLED_ERROR"; context.HttpContext.Response.StatusCode = 500; _logger.LogError(context.Exception, "未捕获的服务器异常"); } context.Result = new JsonResult(response); context.ExceptionHandled = true; } }
在Program.cs(或Startup.cs)中注册这个过滤器:
// .NET 6+ 写法 builder.Services.AddControllers(options => { options.Filters.Add<GlobalExceptionFilter>(); });
最后控制器的代码可以大幅简化:
[HttpPost] public IActionResult GetInfo([FromBody] InfoModel info) { var result = new Info().ProcessInfoResponse(info); return Ok(result); }
这个方案让代码职责更清晰:中间层专注业务逻辑,错误处理交给全局过滤器,后期维护起来更方便。
方案选择建议
- 如果想尽量少改动现有代码,方案一是最优选择;
- 如果不想修改原有业务模型,方案二更适合;
- 如果希望代码结构更清晰、职责更分离,方案三是长期维护的更好选择。
内容的提问来源于stack exchange,提问作者XamDev




