EDIT Nov 25, 2009: note that the problem described in this post below only happens in the VS debugger, under some circumstances.
Today I was attempting to implement a custom IErrorHandler to wrap Exception instances into a FaultException<T> instance, so they can be properly turned into a soap fault by WCF. Seemed easy at first, but there was a nasty fly in the ointment. The original code in the service would work fine, which looked like this:
throw new FaultException<Exception>(new Exception("test"));
When I would just throw a new Exception("test") in the service, and wrap it in the error handler, then it wouldn't work. The service code now looks like this:
throw new Exception("test");
The error handler code looks like this:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Web;
namespace Test {
public class ErrorHandler : IErrorHandler {
#region IErrorHandler Members
public bool HandleError(Exception error) {
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault) {
if (error is FaultException) {
return;
}
Type faultExceptionType = typeof(FaultException<>).MakeGenericType(error.GetType());
FaultException faultException = (FaultException)Activator.CreateInstance(faultExceptionType, error);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(
version,
messageFault,
faultException.Action
);
}
#endregion
}
}
Now the exception doesn't get turned into a soap fault anymore, instead we're getting a SerializationException hidden away quite nicely. What's going on???
After a whole day of digging around, googling, debugging and trying hacks I did manage to find out what's going on and also find a workaround. Googling around first made me find part of the problem. The normal DataContractSerializer can't handle the Exception.Data property when it has a value. And when the exception arrives at the error handler, it does have a Data value. This isn't the complete story though, the field behind the Data property is _data. Whenever Data gets evaluated, it will set _data to a value, and then the serialization breaks. The next piece in the puzzle is this: initially the _data field of an Exception is null, but doing a throw/catch causes the _data to be initialized (Edit Nov 25, 2009: only when the debugger stops when e.g. the exception is thrown, when the debugger doesn't stop, _data remains null and there is no problem). In other words: a throw/catch will break serialization! Note that this all is a debugger side effect, so once you've figured out it's a debugger side effect, you can just make sure you don't break on exceptions in the service.
There's also a workaround. The _data field can be set to null in the error handler using reflection. Modify the ProvideFault method like this and the serialization will work again:
public void ProvideFault(Exception error, MessageVersion version, ref Message fault) {
if (error is FaultException) {
return;
}
// This hack sets the _data field to null again.
FieldInfo fieldInfo = typeof(Exception).GetField("_data", BindingFlags.Instance | BindingFlags.NonPublic);
fieldInfo.SetValue(error, null);
Type faultExceptionType = typeof(FaultException<>).MakeGenericType(error.GetType());
FaultException faultException = (FaultException)Activator.CreateInstance(faultExceptionType, error);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(
version,
messageFault,
faultException.Action
);
}