Wednesday, April 12, 2006

Doing Web Service Exceptions Right

These things are covered in various articles as referenced below, but I would like to synthesize the most important points. First, a summary of the issue. When your code in a web service method throws an exception, the framework wraps it in a SoapException object which relates to the "Fault" node permitted by the SOAP recommendation. If you throw (or allow an unhandled exception) of any type other than SoapException, the SoapException thrown by the framework will only contain the text details of the exception in the message of the exception. This is ugly, and hard to work with. The SOAP recomendation is that you provide fault details within the fault. In order to do this in an asp.net web service, you must throw a SoapException where you have set the details node first via the Details property on the SoapException object.

1. Every web service method should catch System.Exception and wrap the exception in a SoapException, adding necessary details to the Detail property of the exception. Please note that the xml node provided to this property must have the root name "[Dd]etail". It is recommended that you create the root element utilizing the SoapException.DetailElementName.Name and SoapException.DetailElementName.Namespace constants.

2. You must also provide the detail node as a node from a document(such as myXmlDoc.DocumentElement) and cannot just pass the XmlDocument.

3. The InnerException property of your custom SoapException will always be ignored. This is used by the framework for unhandled exceptions of types other than SoapException.

4. To work with SoapException details, it makes sense to have a helper method to wrap other exceptions such that every catch block can simply throw via a call to the helper:

[WebMethod]
public string SomeMethod()
{
try
{
//do something
}
catch(System.Exception excep)
{
throw GetSoapException("Failed to do something",excep);
}
}

private SoapException GetSoapException(string message, System.Exception originalException)
{
StackTrace trace = new StackTrace(1);
SoapException eSoap = new SoapException(message,
SoapException.ServerFaultCode, //Could be ClientFaultCode depending on circumstances.
trace.GetFrame(0).GetMethod().Name,
detail.GetSerializedData(), //detail is some serializable object with xml nodes providing
//exception info. Cut from this sample for clarity.
excep);
return eSoap;
}

5. ServerFaultCode and ClientFaultCode are not necessarily important to set properly, but they indicate what the cause of the problem was. You should indicate a ServerFaultCode if something went wrong in the normal operation of the service. This might be the case if you are wrapping an exception from your catch block. If you intentionally throwing a fault because the client has sent bad data:

if(String.IsNullOrEmpty(someInputString))
throw GetSoapException("Your input string was null or empty",
new ArgumentNullException("someInputString"));

you would indicate this via the ClientFaultCode code.

6. If you want to have a serializable object which contains error details, you will need to expose this to the client code via customizations to your wsdl document. Alternatively, the client code could have its own version of the object via a seperate shared assembly. Whatever makes sense.

7. The client should then have a catch block for SoapException around all web service calls, and some helper method for deserializing the Detail property and taking action based on the contents.

Links :

Using SOAP Faults
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnservice/html/service09172002.asp

Handling and Throwing Exceptions in XML Web Services
http://msdn2.microsoft.com/en-us/library/ds492xtk.aspx

SoapException.Detail Property
http://msdn2.microsoft.com/en-us/library/system.web.services.protocols.soapexception.detail(VS.80).aspx

Discussion on InnerException
http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.dotnet.framework.webservices&tid=48c5c279-1982-462d-8a2d-10db072671bb
Submit this story to DotNetKicks

2 comments:

Paul Reedy's Dev Blog said...

I guess I don't understand where the information comes from to fill in "details".

In the example, the only parameters, is a brief description, and the exceptin itself.

I know you cut it out for clarity, but I need to see an example on how to initialize this node with good information.

Thanks

gabe19 said...

Hi Paul,

In the example I drew from code that used a class object like

class Details
{
public string message {get{;}set{;}};
public string stackTrace{get{;}set{;}};
}

where I put Xml Serialization attributes on the properties. You could get as fancy as you like with that.

You could also simply create a details element like this:

XmlDocument detailDoc = new XmlDocument();

detailDoc.LoadXml("<Detail><Some other xml in here/></Detail>");

SoapException eSoap = new SoapException("message",SoapException.ServerFaultCode, "FailedMethodName",detailDoc.DocumentElement,null);