I <3 Steve McConnell*
Coding Horror
programming and human factors
by Jeff Atwood

August 17, 2004

Throwing better SOAP exceptions

I'm fairly happy with my global unhandled exception handler for WinForms and console apps. I also successfully adapted a version of it for use in ASP.NET apps, where it interfaces with the Application_Error event in global.asax:

    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
        ' Fires when an error occurs
        Dim ueh As New AspUnhandledExceptionHandler(True)
        ueh.HandleException(Server.GetLastError.GetBaseException())
    End Sub

What I haven't been able to do, however, is get it to work with NET web services. Application_Error never fires for a web service. According to my research, there really is no good way to generically handle unhandled exception in .NET web services. All the alternatives are.. well, bad. Here's what you can do:

  • Put a try..catch around every WebService method. These methods tend to be wrappers around other classes, so this isn't quite as bad as it sounds, but it's still not good.
  • use a Facade design pattern to derive all objects from parent objects that.. basically do a try..catch on the .Execute method. Uh, thanks but no thanks.
  • Write a custom SOAP Extension or HttpModule. This sounds reasonable but.. hard. If it's such a cool, important extension or HttpModule, wouldn't someone have written it already?
Are there any good answers here? I would definitely like feedback if anyone has any suggestions. After some further poking around, I located this Microsoft documentation on Handling and Throwing Exceptions in XML Web Services. While it doesn't offer any advice on the above, it did illuminate one problem: by default, .NET doesn't throw very good SOAP Exceptions! You need to re-throw exceptions with some additional data to get the "optional", but quite helpful, SOAP <detail> error element populated-- like so:

    Private Sub WebServiceExceptionHandler(ByVal ex As Exception)
        Dim ueh As New AspUnhandledExceptionHandler
        ueh.HandleException(ex)

        '-- Build the detail element of the SOAP fault.
        Dim doc As New System.Xml.XmlDocument
        Dim node As System.Xml.XmlNode = doc.CreateNode(XmlNodeType.Element, _
            SoapException.DetailElementName.Name, _
            SoapException.DetailElementName.Namespace)

        '-- append our error detail string to the SOAP detail element
        Dim details As System.Xml.XmlNode = doc.CreateNode(XmlNodeType.Element, _
            "ExceptionInfo", _
            SoapException.DetailElementName.Namespace)
        details.InnerText = ueh.ExceptionToString(ex)
        node.AppendChild(details)

        '-- re-throw the exception so we can package additional info
        Throw New SoapException("Unhandled Exception: " & ex.Message, _
            SoapException.ClientFaultCode, _
            Context.Request.Url.ToString, node)
    End Sub

And it really does work. This is a capture of a generic exception using a network sniffer, so we're looking at raw HTTP traffic here. Before:

HTTP/1.1 500 Internal Server Error.
Date: Wed, 26 May 2004 05:12:08 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Length: 488 
  
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <soap:Body>

   <soap:Fault>
     <faultcode>soap:Server</faultcode>
     <faultstring>Server was unable to process request. --> Object reference not set to an instance of an object.</faultstring>
     <detail />
   </soap:Fault>

 </soap:Body>
</soap:Envelope> 

After:

HTTP/1.1 500 Internal Server Error.
Date: Wed, 26 May 2004 05:09:20 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Length: 782 
  
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <soap:Body>

   <soap:Fault>
     <faultcode>soap:Server</faultcode>
     <faultstring>SoapException</faultstring>
     <faultactor>http://192.168.168.10/WebService1/Service1.asmx</faultactor>
     <detail>
       <ExceptionType>System.NullReferenceException</ExceptionType>
       <ExceptionMessage>Object reference not set to an instance of an object.</ExceptionMessage>
       <ExceptionTrace>at WebService1.Service1.HelloException2() in \\HOMESERVER\wwwroot$\WebService1\Service1.asmx.vb:line 70</ExceptionTrace>
     </detail>
   </soap:Fault>

 </soap:Body>
</soap:Envelope> 

Notice the <detail> element is fully populated, and the entire <soap:Fault> element is much more informative-- cool!

Posted by Jeff Atwood    View blog reactions

 

« GUI patterns Side by side issues »

 

Comments

<a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnservice/html/service09172002.asp">http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnservice/html/service09172002.asp</a>;

So what do you do when you deploy the Web service? You need to open up Web.config and edit the /configuration/system.web/customErrors/@mode attribute. By default, this attribute is set to RemoteOnly. The @mode attribute accepts two other values as well:

On
Off
The On setting tells ASP.NET not to add the stack trace information to any Faults. RemoteOnly causes the extra information to show up for clients on the same server and hides the information for remote users. When set to Off, the extra information shows up for all calls to the Web service. This is the same behavior you will see for Web pages. By setting the @mode attribute to On and throwing the same exception, I will get the following Fault instead:


I just set @mode attribute to "Off" while developing. Then when catching exceptions on the client side the exception is filled with method calls, line numbers, stack trace, etc.

Hobbes on August 15, 2005 01:32 PM

Ah, interesting. I am familiar with the error setting for ASP.NET apps; I didn't realize there was a similar setting specific to web services.

Jeff Atwood on August 15, 2005 02:35 PM

Download/order the WeFly247 application from Microsoft. (www.learn247.net)

They have made a quite elegant solution where they use the soap formatter (System.Runtime.Serialization.Formatters.Soap) to serialize and deserialize the exception.

Ralf de Kleine on November 24, 2005 08:00 AM

I do not only want to see the exceptions when developing. I need to catch them with a "Application_Error" solution. Application_Error catches allmost all errors. aspx, ashx, master, and even 404 errors when the file does not exist.

I cannot find words for what I think of Microsoft after reading "there really is no good way to generically handle unhandled exception in .NET web services".

Let us do it the windows way. Just wait for a new version and hope for the best.

Morten on September 27, 2006 05:32 AM

Actually, I did find a way to handle web service exceptions globally. It's documented in this CodeProject article..

http://www.codeproject.com/aspnet/ASPNETExceptionHandling.asp

Scroll down to "Unhandled Exceptions in ASP.NET Web Services".

Jeff Atwood on September 27, 2006 09:49 AM

My application uses web services over remoting, and the details I add in the xmlNode of the SoapException do not show on the client side.
I resigned myself into sending all error info in one string.
Anyone has an idea of what may be wrong?
Thanks.

Ian on October 26, 2006 12:16 PM

throw new SoapException("Exception message here", new XmlQualifiedName("qualified name here, not empty !"));

adrian on December 22, 2006 02:02 AM







(hear it spoken)


(no HTML)




Content (c) 2008 Jeff Atwood. Logo image used with permission of the author. (c) 1993 Steven C. McConnell. All Rights Reserved.