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
            );
        }