JsonConvert序列化Exception丢失消息:MSMQ权限报错问题问询
这个问题我之前也碰到过,核心原因和MessageQueueException的底层实现以及Json.NET的序列化机制有关,咱们一步步拆解:
为什么序列化后Message变了?
System.Messaging.MessageQueueException是基于COM互操作的异常(MSMQ底层依赖COM组件),它继承自ExternalException。这类异常的Message属性并不是直接存储字符串,而是延迟加载的——只有在特定上下文(比如异常抛出的线程、有足够权限访问COM资源时)才能获取到真实的权限拒绝提示。
当你用JsonConvert.SerializeObject序列化这个异常对象时,Json.NET是通过反射读取属性值的。这个过程可能处于不同的安全上下文,或者没有触发异常内部加载真实错误消息的逻辑,导致它返回了ExternalException的默认通用消息:"External component has thrown an exception."。
而调试时hover能看到正确信息,是因为调试器在异常抛出的原始上下文里,能正常触发Message属性的加载逻辑,拿到真实的权限拒绝提示。
是否必须将异常转为字符串存储?
不是必须,但需要调整异常的存储/序列化方式,这里有几个可行方案:
方案1:提前提取异常关键信息到DTO
在捕获异常时,不要直接把Exception对象塞进LogEntry,而是创建一个自定义的异常信息类,比如:
public class ExceptionLogDto { public string Message { get; set; } public string StackTrace { get; set; } public int ErrorCode { get; set; } public string FullException { get; set; } }
然后把异常的真实信息填充进去:
catch (MessageQueueException ex) { var exceptionDto = new ExceptionLogDto { Message = ex.Message, StackTrace = ex.StackTrace, ErrorCode = ex.ErrorCode, FullException = ex.ToString() }; var logEntry = new LogEntry { Exception = exceptionDto }; // 后续序列化logEntry }
这样序列化的是咱们自己的DTO,不会有反射访问的问题,还能保留结构化的异常数据。
方案2:直接存储异常的字符串表示
如果不需要结构化数据,直接存储ex.ToString()的结果是最省心的方式——它会完整保留原始的异常消息、栈跟踪和内部异常信息,不会出现序列化丢失的问题。
方案3:自定义JsonConverter处理MessageQueueException
如果你一定要序列化原始的Exception对象,可以给Json.NET写一个自定义转换器,专门处理MessageQueueException:
public class MessageQueueExceptionConverter : JsonConverter<MessageQueueException> { public override void WriteJson(JsonWriter writer, MessageQueueException value, JsonSerializer serializer) { writer.WriteStartObject(); writer.WritePropertyName("Message"); writer.WriteValue(value.Message); writer.WritePropertyName("StackTrace"); writer.WriteValue(value.StackTrace); writer.WritePropertyName("ErrorCode"); writer.WriteValue(value.ErrorCode); writer.WritePropertyName("ToString"); writer.WriteValue(value.ToString()); writer.WriteEndObject(); } public override MessageQueueException ReadJson(JsonReader reader, Type objectType, MessageQueueException existingValue, bool hasExistingValue, JsonSerializer serializer) { // 如果不需要反序列化,可以返回null或者抛出 NotImplementedException throw new NotImplementedException(); } }
然后序列化时指定这个转换器:
var settings = new JsonSerializerSettings(); settings.Converters.Add(new MessageQueueExceptionConverter()); var json = JsonConvert.SerializeObject(logEntry, settings);
总结
本质问题是COM互操作异常的延迟加载属性在序列化时无法正常获取值,解决的核心思路就是在能正常获取异常信息的上下文里,提前把信息提取出来,不管是存成字符串还是自定义DTO,都能避免序列化时的丢失问题。
内容的提问来源于stack exchange,提问作者NibblyPig

