Peter Goodman bio photo

Peter Goodman

A software engineer and leader living in Auckland building products and teams. Originally from Derry, Ireland.

Twitter Google+ LinkedIn Github

I ran across an issue recently trying to get a custom exception across a WCF boundary. The trouble is, WCF does not like to tell you what the problem was, and for good reason. I was thinking about exceptions the wrong way. In reality we do not want to pass exceptions across a WCF service boundary, instead we want to pass a Fault back to the caller.

An exception is a CLR concept, it does not make sense to expose this outside of the CLR, despite the fact that an exception contains potentially dangerous information (like the stack trace) which we do not want to expose. If your service does not handle an exception, it will fault. Hence a lot of the time we implement a try-catch handler and raise a FaultException which gets sent across the boundary.

 

 

[OperationContract]

[FaultContract(typeof(DivideByZeroException))]

public void MyServiceMethod() {

    try {

 

        // Do some actual stuff

    }

    catch(DivideByZeroException ex) {

        throw new FaultException<DivideByZeroException>(ex, new FaultReason("DivisionByZero"));

    }

}

Then on your proxy side you will typically recreate this exception and throw it.

This approach sometimes has problems when your exception has some extra data in it. Consider the following custom exception type:

 

public class MyDivideByZeroException : DivideByZeroException, ISerializable {

 

    protected MyDivideByZeroException(SerializationInfo info, StreamingContext context)

        : base(info, context) {

        numerator = (int)info.GetValue("numerator", typeof(int));

        denominator = (int)info.GetValue("denominator", typeof(int));

    }

 

    private int numerator;

    public int Numerator {

        get { return numerator; }

        set{ numerator = value;}

    }

 

    public int denominator;

    public int Denominator {

        get { return denominator; }

        set { denominator = value; }

    }

 

    public MyDivideByZeroException(int numerator, int denominator) : base() {

        this.numerator = numerator;

        this.denominator = denominator;

    }

 

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {

        info.AddValue("numerator", numerator, typeof(int));

        info.AddValue("denominator", denominator, typeof(int));

        base.GetObjectData(info, context);

    }

}

When you try this with the FaultException you may get an exception like the following:
The underlying secure session has faulted before the reliable session fully completed. The reliable session was faulted. The reliable messaging channel threw an exception because the reliable session was broken.

In this case you can create a Fault class. Your fault class is simply a Data Contract serializable type containing the information you require to create your exception on the other side. e.g.

 

[DataContract]

public class DivideByZeroFault {

 

    public DivideByZeroFault(MyDivideByZeroException exception) {

        this.numerator = exception.Numerator;

        this.denominator = exception.Denominator;

    }

 

    public MyDivideByZeroException GetException() {

        return new MyDivideByZeroException(numerator, denominator);

    }

 

    [DataMember]

    private int numerator;

    public int Numerator {

        get { return numerator; }

        set { numerator = value; }

    }

 

    [DataMember]

    public int denominator;

    public int Denominator {

        get { return denominator; }

        set { denominator = value; }

    }

}

 

Therefore your service code in the first example will change to be:

[OperationContract]

[FaultContract(typeof(DivideByZeroFault))]

public void MyServiceMethod() {

    try {

 

        // Do some actual stuff

    }

    catch(MyDivideByZeroException ex) {

        throw new FaultException<DivideByZeroFault>(new DivideByZeroFault(ex), new FaultReason("DivisionByZero"));

    }

}

 

This will allow WCF to send across the relevant information without compromising the applications security by sending a stack trace etc.

Pete