Some context: We have a custom XSD and generate the WSDL and C# code using WSCF.blue. The client side uses ChannelFactory<T> and shares the interface which includes all the attributes added by WSCF.blue to match what is in the XSD.
I'm trying to implement IErrorHandler.ProvideFault where it provides a generic FaultException<T>, but on the client side I'm getting back a non-generic FaultContract. This is what my ProvideFault method looks like:
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (!(error is FaultException))
{
FaultException faultException = FaultExceptionFactory.CreateFaultException(error);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);
}
}
In each service method, if I do a try/catch with throw FaultExceptionFactory.CreateFaultException(ex) it works as expected, so I think the [FaultContract], factory, bindings, etc. are all correct. Just in case, this is how the factory works:
BusinessRuleFaultExceptionType businessRuleFaultException = new BusinessRuleFaultExceptionType();
BusinessRuleFaultException.Code = exception.Code.ToString();
BusinessRuleFaultException.Reason = exception.Message;
return new FaultException<BusinessRuleFaultExceptionType>(
businessRuleFaultException,
exception.Message,
new FaultCode(exception.Code.ToString())
);
I think the issue is with how the message is created in IErrorHandler, maybe in CreateMessageFault(). I have read that the action should be faultException.Action instead of null, but in fact faultException.Action is null. Maybe this is causing the issue. I can set an action in the factory, but what should the action be and why wouldn't that show up with the manual throw?
Any other ideas what I might be missing?
Edit: I checked the WSDL and found the specific operation I was calling and its action:
<wsdl:operation name="MyMethod">
<wsdl:fault wsaw:Action="http://myNamespace/MyMethodBusinessRuleFaultExceptionTypeFault" name="BusinessRuleFaultExceptionTypeFault" message="tns:..."/>
I tried hard coding the action: Message.CreateMessage(..., "http://myNamespace/MyMethodBusinessRuleFaultExceptionTypeFault") and setting it to the .Action in the factory but that still didn't work.
Edit 2: The throw/catch generates the following XML which allows catching the generic exception on the client:
<s:Fault>
<faultcode xmlns="">s:-100</faultcode>
<faultstring xml:lang="en-US" xmlns="">xxx</faultstring>
<detail xmlns="">
<BusinessRuleFaultExceptionType xmlns="http://myNamespace/Services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Code xmlns="http://myNamespace/Entitites">-100</Code>
<Reason xmlns="http://myNamespace/Entitites">xxx</Reason>
</BusinessRuleFaultExceptionType>
</detail>
</s:Fault>
The IHttpErrorHandler generates the following which goes to the non-generic FaultException:
<s:Fault>
<faultcode xmlns="">s:-100</faultcode>
<faultstring xml:lang="en-US" xmlns="">xxx</faultstring>
<detail xmlns="">
<BusinessRuleFaultExceptionType xmlns="http://schemas.datacontract.org/2004/07/My.Dot.Net.Namespace" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<codeField>-100</codeField>
<reasonField>xxx</reasonField>
</BusinessRuleFaultExceptionType>
</detail>
</s:Fault>
Edit 3:
If I add [DataContract] and [DataMember] to the BusinessRuleFaultExceptionType, then I get almost the correct XML:
<s:Fault>
<faultcode xmlns="">s:-100</faultcode>
<faultstring xml:lang="en-US" xmlns="">xxx</faultstring>
<detail xmlns="">
<BusinessRuleFaultExceptionType xmlns="http://myNamespace/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Code>-100</Code>
<Reason>xxx</Reason>
</BusinessRuleFaultExceptionType>
</detail>
</s:Fault>
It's missing the namespace for Code and Reason. At least I think this narrows it down. The regular serialization is using the [XmlType] and [XmlElement] while the IErrorHandler is using [DataContract] and [DataMember]. Unfortunately [DataMember] doesn't let you set the namespace, so I think the question now is how to use the XMLSerializer in IErrorHandler. This describes my issue, but the fix won't work for the reason stated above: http://twenty6-jc.blogspot.com/2011/05/ierrorhandlerprovidefault-serialization.html
Edit 4:
I partially found the issue. We are using XmlSerializer, but since IErrorHandler is outside the scope of the operation, it reverts to the default DataContractSerializer. The solution is to either change your service to use DataContractSerializer everywhere, or manually choose XmlSerializer when creating the faults. These two articles provided what I needed:
http://twenty6-jc.blogspot.com/2011/05/ierrorhandlerprovidefault-serialization.html http://zamd.net/2008/08/15/serializing-faults-using-xmlserializer/
That gets me very close. It's the same as the working XML, except the exception type's namespace is missing:
<s:Fault>
<faultcode xmlns="">s:-100</faultcode>
<faultstring xml:lang="en-US" xmlns="">xxx</faultstring>
<detail xmlns="">
<BusinessRuleFaultExceptionType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Code xmlns="http://myNamespace/Entitites">-100</Code>
<Reason xmlns="http://myNamespace/Entitites">xxx</Reason>
</BusinessRuleFaultExceptionType>
</detail>
</s:Fault>
I assume it doesn't add xmlns="http://myNamespace/Services" because it doesn't have the request's context. That namespace is defined in the interface, but not data contract. Am I really going to be forced to stay within the request's context (hopefully IOperationInvoker works), instead of using IHttpHandler?
I would suggest using IOperationInvoker instead of IErrorHandler. With IOperationInvoker:
The implementation in your case may look like this: