继承Exception需实现序列化构造函数及GetObjectData以支持远程调用;Web API应统一用ProblemDetails包装异常;重抛异常须用throw而非throw new,避免堆栈丢失;DTO中禁止暴露Exception实例。
怎么继承 Exception 写一个可序列化的自定义异常
直接继承 exception 类是最稳妥的起点,但漏掉序列化构造函数会导致跨 appdomain 或 wcf/远程调用时抛出 serializationexception。
- 必须提供带
(SerializationInfo, StreamingContext)参数的受保护构造函数,哪怕只写一行base(info, context) - 推荐同时实现无参、单字符串消息、带 innerException 的三个常用构造函数,覆盖多数 throw 场景
- 如果想传业务字段(比如错误码
ErrorCode),加 public 属性即可,但别在构造函数里赋值给私有字段后又不写入序列化流——否则反序列化后字段为空
public class OrderValidationException : Exception
{
public int ErrorCode { get; }
public OrderValidationException() { }
public OrderValidationException(string message) : base(message) { }
public OrderValidationException(string message, Exception inner) : base(message, inner) { }
public OrderValidationException(int errorCode, string message)
: base(message) => ErrorCode = errorCode;
protected OrderValidationException(SerializationInfo info, StreamingContext context)
: base(info, context) => ErrorCode = info.GetInt32(nameof(ErrorCode));
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue(nameof(ErrorCode), ErrorCode);
}
}
Web API 里怎么统一包装 Exception 成 JSON 错误响应
ASP.NET Core 默认把未捕获异常转成 500 响应体,但结构不可控、字段不一致、缺状态码和业务码。靠中间件 + ProblemDetails 是目前最轻量且兼容 OpenAPI 的方式。
- 不要手动 new
JsonResult或拼字符串——会绕过模型验证、Content-Type 自动设置、格式化器链 - 在
UseExceptionHandler中用HttpContext.Response.WriteAsJsonAsync(...),配合ProblemDetails类型,它自带status、title、detail字段 - 对自定义异常(如
OrderValidationException),提取ErrorCode放进Extensions字典,前端能取到;普通异常默认 status=500,但别硬塞title="Internal Server Error",保留原Exception.Message更利于排查
throw new MyException() 和 throw 重新抛出的区别
这是堆栈跟踪被截断还是保留的关键分水岭,尤其影响日志定位和调试效率。
-
throw new MyException("msg", ex):创建新异常,原始堆栈在InnerException里,顶层堆栈是你这行代码——查问题要翻两层 -
throw(单独写,不带对象):原异常继续上抛,堆栈从最初 throw 的位置开始,没被污染——这才是“重抛”的正确写法 - 如果真要加上下文(比如“处理订单 ID=123 时失败”),用
ExceptionDispatchInfo.Capture(ex).Throw()或干脆在 catch 块里 log 后直接throw,别 new 新异常
为什么不该在 DTO 或返回模型里暴露 Exception 属性
把异常实例塞进 API 返回体(比如 ApiResponse<t> { Data, Exception }</t>)看似方便,实则破坏分层、泄露实现细节、引发序列化风险。
-
Exception类含大量非序列化字段(如_stackTraceString)、内部引用、甚至线程相关状态,JSON 序列化器可能崩溃或静默跳过 - 前端无法消费原始
Exception对象,真正需要的是错误码、用户提示、重试建议等结构化字段 - 统一用
ProblemDetails或自定义错误结构(如ApiError { Code, Message, TraceId })替代,异常只留在服务端日志里
throw 写法——这两个点一旦出错,要么上线后远程调用失败,要么日志里永远看不到第一现场。


















