I'm just starting with WCF and am trying to verify fault handling in a WCF rest service that supports both JSON and XML. My test service generates a fault but no matter what I try I can't get my client to pick up the details of the fault (and the behavior varies based on the request format and http status code):
My test service generates the fault as follows:
public Data GetResponse()
{
throw new WebFaultException<ErrorDetails>(
new ErrorDetails {ErrorMessage = "Server Config Value not set"},
HttpStatusCode.OK
);
}
This gets sent across the wire quite reasonably:
{"ErrorMessage":"Server Config Value not set"}
And:
<ErrorDetails xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ErrorMessage>Server Config Value not set</ErrorMessage>
</ErrorDetails>
My client is defined with a FaultContract
:
[OperationContract]
[WebInvoke(
UriTemplate="/response",
Method="GET",
RequestFormat = WebMessageFormat.Xml, // or .Json
ResponseFormat = WebMessageFormat.Xml // or .Json
)]
[FaultContract(typeof(ErrorDetails), Namespace="")]
Data GetResponse();
Here's the full error message for (format/status code):
XML/Conflict:
Requesting response CommunicationException: The remote server returned an unexpected response: (409) Conflict., System.Collections.ListDictionary Internal, System.ServiceModel.ProtocolException Press a key to exit...
And XML/OK:
Requesting response Exception: Unable to deserialize XML body with root name 'ErrorDetails' and root namespace '' (for operation 'GetResponse' an d contract ('IClient', '')) using DataContractSerializer. Ensure that the type corresponding to the XML is added to the know n types collection of the service., System.Collections.ListDictionaryInternal Press a key to exit...
And JSON/Conflict:
Requesting response CommunicationException: The remote server returned an unexpected response: (409) Conflict., System.Collections.ListDictionary Internal, System.ServiceModel.ProtocolException Press a key to exit...
And JSON/OK:
Requesting response Response: Request complete Press a key to exit...
The client code catches the exceptions in the proper order:
try
{
Console.WriteLine("Requesting response");
Console.WriteLine("Response: " + client.GetResponse().Message);
Console.WriteLine("Request complete");
}
// sanity check, just in case...
catch (WebFaultException<ErrorDetails> ex)
{
Console.WriteLine("WebFaultException<ErrorDetails>: " + ex.Detail.ErrorMessage + ", " + ex.Reason);
}
catch (FaultException<ErrorDetails> ex)
{
Console.WriteLine("FaultException<ErrorDetails>: " + ex.Detail.ErrorMessage + ", " + ex.Reason);
}
catch (FaultException ex)
{
Console.WriteLine("FaultException: " + ex.Message + ", " + ex.Reason);
}
catch (CommunicationException ex)
{
Console.WriteLine("CommunicationException: " + ex.Message + ", " + ex.Data + ", " + ex.GetType().FullName);
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.Message + ", " + ex.Data);
}
What do I have to do so that FaultException<ErrorDetails>
will be thrown and I can get access to the ErrorDetails
?
Note: The gist should be fully compileable and runnable.
It's not possible to use Faults/FaultContract as an error handling mechanism in WCF REST clients (as of WCF version 4.0) service:
Per one thread on the WCF list:
In WCF Rest service there is not SOAP message, so you can't return a FaultException to the client. Actually the appropriate status code is returned to the requestor as an HTTP header, allowing the requestor to determine the result of the call.
And from a StackOverflow post :
Faults are part of SOAP protocol and are not available in REST scenarios
Options considered
FaultContract - The FaultContractAttribute
works for SOAP envelopes, but not for WCF REST services. It's possible that if you send XML across that's formatted as a SOAP envelope that you could get it to work, but then you're not using reasonable custom error messages.
IErrorHandler - A quick read of the documentation indicates this is for the service and considering it was a newbie error: "Allows an implementer to control the fault message returned to the caller and optionally perform custom error processing such as logging."
Message Inspectors - The AfterReceiveRequest
doesn't execute because the CommunicationException
is thrown first.
Workaround
After quite a bit of digging I found one blogger who recommended creating a helper class to extract the information from response stream that's embedded in the exception objects. Here's his implementation, as is:
public static void HandleRestServiceError(Exception exception, Action<TServiceResult> serviceResultHandler, Action<TServiceFault> serviceFaultHandler = null, Action<Exception> exceptionHandler = null)
{
var serviceResultOrServiceFaultHandled = false;
if (exception == null) throw new ArgumentNullException("exception");
if (serviceResultHandler == null) throw new ArgumentNullException("serviceResultHandler");
// REST uses the HTTP procol status codes to communicate errors that happens on the service side.
// This means if we have a teller service and you need to supply username and password to login
// and you do not supply the password, a possible scenario is that you get a 400 - Bad request.
// However it is still possible that the expected type is returned so it would have been possible
// to process the response - instead it will manifest as a ProtocolException on the client side.
var protocolException = exception as ProtocolException;
if (protocolException != null)
{
var webException = protocolException.InnerException as WebException;
if (webException != null)
{
var responseStream = webException.Response.GetResponseStream();
if (responseStream != null)
{
try
{
// Debugging code to be able to see the reponse in clear text
//SeeResponseAsClearText(responseStream);
// Try to deserialize the returned XML to the expected result type (TServiceResult)
var response = (TServiceResult) GetSerializer(typeof(TServiceResult)).ReadObject(responseStream);
serviceResultHandler(response);
serviceResultOrServiceFaultHandled = true;
}
catch (SerializationException serializationException)
{
// This happens if we try to deserialize the responseStream to type TServiceResult
// when an error occured on the service side. An service side error serialized object
// is not deserializable into a TServiceResult
// Reset responseStream to beginning and deserialize to a TServiceError instead
responseStream.Seek(0, SeekOrigin.Begin);
var serviceFault = (TServiceFault) GetSerializer(typeof(TServiceFault)).ReadObject(responseStream);
if (serviceFaultHandler != null && serviceFault != null)
{
serviceFaultHandler(serviceFault);
serviceResultOrServiceFaultHandled = true;
}
else if (serviceFaultHandler == null && serviceFault != null)
{
throw new WcfServiceException<TServiceFault>() { ServiceFault = serviceFault };
}
}
}
}
}
// If we have not handled the serviceResult or the serviceFault then we have to pass it on to the exceptionHandler delegate
if (!serviceResultOrServiceFaultHandled && exceptionHandler != null)
{
exceptionHandler(exception);
}
else if (!serviceResultOrServiceFaultHandled && exceptionHandler == null)
{
// Unable to handle and no exceptionHandler passed in throw exception to be handled at a higher level
throw exception;
}
}
I've used this:
try
{
//wcf service call
}
catch (FaultException ex)
{
throw new Exception( (ex as WebFaultException<MyContractApplicationFault>).Detail.MyContractErrorMessage );
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.