Gishu
Gishu

Reputation: 136613

How to propogate the fields of a custom exception from service to client via WCF?

Here's what I observe

I need to throw a custom Exception subtype from the service to the client. (Listed as a FaultContract on the specific operation). I have certain fields on the CustomException, that should be received by the client.

[Serializable]
class MyCustomException : Exception
{
    public string From { get; private set; }

    public MyCustomException(string where)
    {
        From = where;
    }

}

}

I find that the field isn't being deserialized even though the exception is present inside the FaultException instance. I tried implementing ISerializable by overriding GetObjectData and the serialization ctor, but no dice. The only way I could get it across was changing MyCustomException to be a DataContract and not derive from Exception.

[DataContract]
class MyCustomException
{
    [DataMember]
    public string From { get; private set; }

    public MyCustomException(string where)
    {
        From = where;
    }
}

This works. However it can't be derived from Exception anymore.. since Exception is marked with the Serializable attribute and you can't have both Serializable and DataContract on a type. (Confirmed: run time Exception thrown)

So my question is : What is the right way to propogate the fields of a custom exception subtype in WCF?

Upvotes: 1

Views: 441

Answers (2)

Gishu
Gishu

Reputation: 136613

Here's how I got it to work.. not sure if it is the right way. I could not find any explicit guideline stating that you're not supposed to use Exception subtypes as the TDetail parameter in FaultException<TDetail>.

I tried throwing a FaultException<FileNotFoundException> and found that the filename field was in fact propogated correctly.

So what's the delta between FileNotFoundException and MyCustomException?

Since the base exception implements ISerializable, I had to override the methods on the server side..

    protected MyCustomException(SerializationInfo info, StreamingContext context) :
        base(info, context)
    {
        this.From = info.GetString("From");
    }
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue("From", this.From);
    }

I then used svcutil to generate the client proxy classes. I find that FileNotFoundException isn't generated (since it is an built-in type) but MyCustomException is. However it is without any fields and has just an empty deserialization ctor.

I did not delve into ISerializable earlier because when I set a breakpoint in the proxy class ctor it wasn't being hit. I incorrectly aborted that line of investigation (Failed to see the DebuggerStepThrough attribute and GeneratedCode attribute on the class; added by svcutil).

So I then manually edit the auto-generated classes to add the field and set it in the ctor like so..

[System.SerializableAttribute()]
    public partial class MyCustomException : System.Exception
    {
        public string From { get; private set; } // manual edit

        public MyCustomException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) :
            base(info, context)
        {
             this.From = info.GetString("From");  // manual edit
        }
    }

Now it works as expected; The field data is intact.

This raises another question: Why doesn't the proxy generation step do this automatically ?

Upvotes: 1

Channs
Channs

Reputation: 2101

... you can't have both Serializable and DataContract on a type. (Confirmed: run time Exception thrown)

Firstly, you can have DataContract and Serializable together. Infact, without this, the website I am working on now will not work, since my WCF service is consumed over the web via $.ajax. This SO thread will provide you the formal details.

Next, I strongly suggest not to inherit your custom fault from Exception. Reason is you already have a FaultException<TDetail> built-in class, where TDetail is your custom fault. You can read this MSDN article for implementation details - remember to turn off 'exception details' at the client while deploying.

catch (FaultException<MyFault> e)
{
    //e is the full exception (with StackTrace et al.) when 'exception details' is on
    //e.Detail is your custom fault which is always available
}

Upvotes: 2

Related Questions