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

Monday, April 10, 2006

Locating embedded resources

I found it mildly challenging to locate some resources that I had embedded when using the overload ctor for ResourceManager that takes the name of the resource file to load. After some noodling around (and reading anything but the docs for what the baseName should be) it turns out that assembly resources are always named in a 'flat' format that will include the "defaultnamespace" assembly property, and any sub-folder in which the resource file is contained. For example, the resource "ExceptionStrings.resx" in an assembly with the default namespace "TestResources" in the subfolder "Resources" will be:

'TestResources.Resources.ExceptionStrings'

A good way to figure out what your resource files are called is to perform the following while debugging:

string[] resourceNames =
Assembly.GetExecutingAssembly().GetManifestResourceNames();
foreach(string name in resourceNames)
{
Debug.WriteLine("ResourceName: "+ name);
}

This will output each resource in your assembly by its full name - which should be the name provided to the ResourceManager ctor (minus the ".resources" file extension).
Submit this story to DotNetKicks