我正在使用 JsonConvert.SerializeObject 在服务器上序列化一个 Exception,然后编码为 byte[] 并在客户端反序列化使用 JsonConvert.DeserializeObject。到目前为止一切正常......问题是当我抛出 Exception 被替换的堆栈跟踪时,让我演示:
public void HandleException(RpcException exp)
{
// Get the exception byte[]
string exceptionString = exp.Trailer.GetBytes("exception-bin");
// Deserialize the exception
Exception exception = JsonConvert.DeserializeObject<Exception>(exceptionString, new
JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
// Log the Exception: The stacktrace is correct. Ex.: at ServerMethod()
Console.WriteLine(exception);
// Throw the same Exception: The stacktrace is changed. Ex.: at HandleException()
ExceptionDispatchInfo.Capture(exception).Throw();
}
答案 0 :(得分:0)
如果您反序列化一个 Exception
并设置 JsonSerializerSettings.Context = new StreamingContext(StreamingContextStates.CrossAppDomain)
,那么即使在抛出异常之后,反序列化的堆栈跟踪字符串也会被添加到显示的 StackTrace
之前:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
Context = new StreamingContext(StreamingContextStates.CrossAppDomain),
};
var exception = JsonConvert.DeserializeObject<Exception>(exceptionString, settings);
注意事项:
这是可行的,因为在 streaming constructor for Exception
中,反序列化的堆栈跟踪字符串被保存到一个 _remoteStackTraceString
中,该字符串随后被添加到常规堆栈跟踪中:
if (context.State == StreamingContextStates.CrossAppDomain)
{
// ...this new exception may get thrown. It is logically a re-throw, but
// physically a brand-new exception. Since the stack trace is cleared
// on a new exception, the "_remoteStackTraceString" is provided to
// effectively import a stack trace from a "remote" exception. So,
// move the _stackTraceString into the _remoteStackTraceString. Note
// that if there is an existing _remoteStackTraceString, it will be
// preserved at the head of the new string, so everything works as
// expected.
// Even if this exception is NOT thrown, things will still work as expected
// because the StackTrace property returns the concatenation of the
// _remoteStackTraceString and the _stackTraceString.
_remoteStackTraceString = _remoteStackTraceString + _stackTraceString;
_stackTraceString = null;
}
虽然 Exception
的序列化流确实包含堆栈跟踪字符串,但它不会尝试捕获 private Object _stackTrace
,运行时使用它来识别正在执行的程序集中异常的位置被抛出。这似乎是 ExceptionDispatchInfo
在抛出异常时无法复制和使用此信息的原因。因此,抛出反序列化异常并从序列化流中恢复其“真实”堆栈跟踪似乎是不可能的。
为了让 Json.NET 使用其流构造函数反序列化一个类型(从而根据需要设置远程跟踪字符串),该类型必须用 [Serializable]
标记并实现 ISerializable
。 System.Exception
满足这两个要求,但 Exception
的某些派生类并不总是添加 [Serializable]
属性。如果您的特定序列化异常缺少该属性,请参阅Deserializing custom exceptions in Newtonsoft.Json。
使用 TypeNameHandling.All
反序列化异常是不安全的,并且在从不受信任的来源反序列化时可能会导致注入攻击类型。请参阅:External json vulnerable because of Json.Net TypeNameHandling auto? 其回答专门讨论了异常的反序列化。
演示小提琴here。
答案 1 :(得分:0)
我想指出的只是一个小案例:我从两个应用程序中调用此序列化/反序列化,一个是 Blazor (.net 5),另一个是 WinForms (.net framework 4.7)。在 blazor one 中,接受答案的方法不起作用。在这种情况下,我所做的是通过反射设置 RemoteStackTrace。
// Convert string para exception
Exception exception = JsonConvert.DeserializeObject<Exception>(exceptionString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
// Set RemoteStackTrace
exception.GetType().GetField("_remoteStackTraceString", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception, exception.StackTrace);
// Throw the Exception with original stacktrace
ExceptionDispatchInfo.Capture(exception).Throw();