Reputation: 136613
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
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
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