Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Handling SoapException Detail with a WCF Client from a Web Service

5.00/5 (11 votes)
4 Nov 2009CPOL10 min read 108.8K   1.5K  
How to catch and handle ASMX based SOAP exceptions in a WCF client.

Image 1

Introduction

This project solves handling SoapException Web Service (ASMX) issues in WCF based client applications. It is very common in companies that work with external partners today to use Web Services as the means of sharing information. This is true in healthcare, banking, and many other company specific applications. A fundamental problem for clients working with external Web Service partners is that the client company normally has no control over what is returned by the Web Service and, in particular, the exceptions. It is truly a WYSIWYG situation.

Background

It matters little how or why a service is implemented the way it is to the "under the gun - get it done" developer. I have seen so many forum posts that simply tell you to do this or that on the Web Service and "problem-solved!" Obviously, these people have luxuries that the rest of us don't enjoy. Normal developers can't just run out and change the Web Service, it usually doesn't even belong to us. So you can see the dilemma we face. How do we get SoapException information into a WCF based client from an old SOAP or ASMX based Web Service?

For those who don't know, WCF shields us from the problems of handling SOAP - good/bad... depends on which day you ask me. On the whole, in my humble opinion, WCF handling of exceptions is far superior than other choices, but that is another future article. The WCF SOAP shield (wraps) the SoapException into a FaultException. For those of us who must have the "detail" section of the SoapException are simply out of luck, or so it seems. Microsoft, for reason only they know, decided to not surface the "detail" section. They did however conveniently provide a HasDetail property.

So we know from the HasDetail that the Web Service is providing us with additional SoapException information, but we can't get to it. Now, talking about a real "chaps my butt" issue. This is one of those. I worked on the problem, and found several ways to approach the problem without resorting to dumping WCF. The good things that WCF provides outweighs the exception issue. I found a thread on the MSDN Forum - Problem Catching SoapExceptions. Seems I am not the only developer with this issue. While working on the forum, John Saunders (MVP) came up with a compact workaround; while not utopia, it is certainly effective. Below, I am going to discuss several ways to tackle the issue of SoapException to FaultException management.

Using the Code

I have provided a complete test ASMX Web Service and a WCF SoapClientTest project. Instructions on setting up the solution (.NET 3.5 SP1 Visual Studio 2008) are in the zip file.

The real world problem we are trying to solve is to handle SOAP exceptions thrown by the Web Service. I must inject for the "purest", you should as a competent developer study the Web Service API and code against the API to avoid exceptions in the first place. For example, suppose a service has a requirement that a client ID must exist before you attempt to write to a record. Also, suppose the service provides a query method to "get client info" that lets you know if the client ID exists. Then, it is entirely possible to avoid the exception using good coding practice. But in the real world, companies are exposing more services to end-user clients that often must hit the Web Services of external partners such as a bank or shipper, so the problem of exception handling is a real one.

Below is a real (although sanitized) SOAP exception. Notice the detail section of the exception, this is the critical issue as we will see in this article. detail is specified in the SOAP specification, and you don't own it. Everything inside detail is owned by the Web Service owner. As we shall see below, the fact that the content of detail is at the Web Service developer's discretion is why SOAP exception handling is such a chore.

XML
//This is what a complete soap exception
//looks like when captured using fiddler2
//<a href="http://www.fiddler2.com/">http://www.fiddler2.com</a>
<?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:Client</faultcode>
        <faultstring>System.Web.Services.Protocols.SoapException: 
             Provisioning violation: unknown IP address. IP address [xxxxxxx] 
             is not recognized or client application is not 
             a licensed subscriber.</faultstring>
          <faultactor>GetCustomerProfile</faultactor>
            <detail>
              <faulttype>Data</faulttype>
                <errorlist>
                  <field errorcode="Client" 
                     errortype="Provision">[127.0.0.1]</field>
                </errorlist>
            </detail>
        </soap:Fault>
    </soap:Body>
</soap:Envelope>

Many Web Service developers take a cheap shot, and return your requested object with an embedded error response in the object. Other Web Service developers take the high road and actually develop SOAP exception handling in their system. There are two sources of SOAP exceptions: those that are generated in the client, and those generated by the Web Service. You must handle both. Client side exceptions are things like endpoint problems, time-outs, network problems, things that keep you from reaching the Web Service or loosing connection to it. Web Service generated exceptions (like the one above) take three forms: Schema problems, Client data problems (application level), and Server side problems. Schema problems surface when the contracts between the Web Service (XSD) and client are no longer in sync (somebody changed something somewhere). Client data problems usually should be caught early, but sometimes they sneak through. Server side issues could be anything inside the Web Service provider's network; for example, their database could be offline. In the case of a SOAP exception generated by the server, you will get just a "server" exception (<faultcode>soap:Server</faultcode>); they aren't going to tell you why their internal system is failing, only that it has. Usually, your only option is to try again later in that case.

OK. We know that SOAP exceptions are a fact of life, what do we do?

The Web Service provided in this project is ASMX based because SOAP exception handling is not so much of an issue in WCF to WCF client/server configurations. Also, many .NET Web Service adopters have implemented ASMX solutions, and are slow to change to WCF. Naturally, there are also all the other Web Services that are not and never will be .NET based. So in your career as a developer, you will more than likely face the management of SOAP exceptions from Web Services you don't control. I have created, in this project, three possible SOAP exception generation methods that are commonly employed by Web Service providers. I have called them Simple, Multiple-element, and Complex. Since these exceptions are totally optional, there is no standard other than that given by the Web Service provider in their API documentation. These are common implementations; however, there are any possible number of combinations, which is why it not easy.

Simple SOAP Exceptions

A "Simple" SOAP exception will have the entire error response inside a single XML element, inside the detail. It could be text or additional XML data, but it is all contained within a single element. Below are a couple possibilities. This is "simple" because with WCF, you can get the first element (and all its contents) easily.

XML
<!--Simple SoapException: All elements are contained 
inside a single XML element inside the <detail>.-->
<detail>
    <soapexception1>
        <faulttype>Data</faulttype>
        <errorlist>
            <errorcode>123</errorcode>
            <errorsource>methodName<errorsource>
            <errordata>Mr. James O'brian</errordata>
        </errorlist>
    </soapexception1>
</detail>
//

<!--Simple SoapException: A text based (csv) exception also
contained inside a single XML element inside the <detail>.-->
<detail>
    <soapexception1>
        Data, 123, methodName, Mr. James O'brian
    </soapexception1>
</detail>

Catching the simple element detail from a SOAP exception:

C#
//Catches Simple SoapException (WCF FaultException)
catch (FaultException faultException) 
//Catches the SOAP exceptions thrown by web services
{
    MessageFault messageFault = faultException.CreateMessageFault();
    if (messageFault.HasDetail)
    {
        var doc = new XmlDocument();
        var node = doc.CreateNode(XmlNodeType.Element, 
                                  SoapException.DetailElementName.Name,
                                  SoapException.DetailElementName.Namespace);
        //It is easier to manage XML with LINQ so get
        //the "detail" node just created above into a LINQ class
        var detail = GetXElement(node);  //helper method in project code
        var xElement = GetXElement(messageFault.GetDetail());
        //*** This is the workhorse of this exception handler.

        detail.Add(xElement);
        node = GetXmlNode(detail);
        //helper method in project code

        //information available from the WCF FaultException object
        string faultNameSpace = "CODE NAMESPACE   : " + faultException.Code.Namespace;
        string faultCode = "CODE NAME        : " + faultException.Code.Name;
        string faultString = "EXCEPTION MESSAGE:\n" + faultException.Message;
        
        //This is where you implement you API interpretation of the exception
        //You can extract the data directly as shown below 
        //or use the XmlDocument (node) to navigate the data.
        //from the element contained in the detail
        string faultTypeName = "XML ELEMENT NAME : " + xElement.Name.LocalName;
        string faultType = "XML ELEMENT VALUE: " + xElement.Value;
        string innerText = "RAW INNERTEXT    :\n" + node.InnerText;
        this.rtbMessage.Text = faultNameSpace + "\n" +
                               faultCode + "\n" +
                               faultString + "\n\n" +
                               faultTypeName + "\n" +
                               faultType + "\n\n" + innerText;
    }

}

When the SoapException (converted by WCF to FaultException) is caught, we want to get the detail if there is any detail provided by the Web Service. Assuming that the Web Service developers provide some details about the nature of the exception, we will want to get that information. We interpret it based on the API because with ASMX, we don't get classes with the exceptions that are possible in WCF.

Walking the Code

First, we create a MessageFault instance loaded with the SOAP exception that was thrown. Then, we check it to see if the Web Service has provided any detail information. If it does, get it next. In this example, I am expecting a text string representation of our exception data. I am putting the fault data into an XmlDocument in case the return is not text but more XML, in which case, we could easily navigate the data. So, with a text response, we just have a small expense of an XmlDocument container. The CreateNode method creates for us a "root" element which is named the same as SoapException.DetailElementName.Name that was created in the SoapException. We are going to append our fault as a child of the node. By the way, this creates a proper XML document. I have found it easier to work with XML using LINQ, so I am using a helper method and am putting the node information into an XElement. Next, I use the same method to get the actual fault detail, also into an XElement. I append that to the node. Next, I put all of it back into the node (our XmlDocument). You could now use standard XmlDocument navigation to decode the data. However, since this is a simple exception, all of it is readily available at this point, as shown in the code above.

Multiple Element SOAP Exceptions

More difficult to extract, at least with WCF, is a SOAP exception that contains multiple discrete XML elements. Why? Remember our messageFault.GetDetail() above, it will only return the first detail item. Everything else is out of reach of the messageFault object, because Microsoft did not provide a "detail" property in the FaultException class. In the sample XML data below, we get only the first element with the messageFault.GetDetail() method. There must be a way, and naturally there is, see code below.

XML
<!--Multiple Element SoapException: Multiple discrete elements
provided in the soap exception inside the <detail>.-->
<detail>
    <faulttype>Data</faulttype>
    <errorcode>123</errorcode>
    <errorsource>methodName<errorsource>
    <errordata>Mr. James O'brian</errordata>
</detail>

Catching multiple element detail from a SOAP exception:

C#
//Catches Multi-Element SoapException (WCF FaultException)
catch (FaultException faultException)
//Catches the SOAP exceptions thrown by web services
{
    MessageFault messageFault = faultException.CreateMessageFault();
    if (messageFault.HasDetail)
    {
        string faultString = "EXCEPTION MESSAGE:\n" + 
               faultException.Message + "\n";
        string faultNameSpace = "CODE NAMESPACE   : " + 
               faultException.Code.Namespace;
        string faultCode = "CODE NAME        : " + 
               faultException.Code.Name;

        //we will create dictionary of our element
        //and values that exist in the XML elements.
        var theXElements = new Dictionary<string, string>();
        using (var xmlReader = messageFault.GetReaderAtDetailContents())
        {
            while (!xmlReader.EOF)
            {
                if (xmlReader.NodeType != XmlNodeType.EndElement)
                {
                    string localName = xmlReader.LocalName;
                    string readElementString = xmlReader.ReadElementString();
                    theXElements.Add(localName, readElementString);
                }
                else
                {
                    xmlReader.ReadEndElement();
                }

            }
        }

        //Application specific implementation of the API goes here...
        this.rtbMessage.Text = faultNameSpace + "\n" +
                               faultCode + "\n" +
                               faultString + "\n";
        foreach (var element in theXElements)
        {
            this.rtbMessage.Text += element.Key + ": " + 
                                    element.Value + "\n";
        }
    }
}

Walking the Code

In this sample, since our Web Service API specifies multiple simple XML elements (no attributes), we are reading each element in turn into a dictionary, storing the element tag name and the value in the element. The NodeType test filters out XML end tags such as "</errorlist>". The ReadEndElement() is required to actually "read" the line in to advance the reader forward to the next element to read. When the EOF is encountered, the reader exits. Once we exit the reader, we have our API implementation of our exception handler to actually do something meaningful.

Complex SOAP Exceptions

Never said exception handling was easy! It gets worse! Next, we will look at what I call a Complex SOAP Exception. It contains multiple elements, with nested elements that have attributes on the elements. Jump back up to the top of this article, and take another look at the real SOAP exception.

XML
<!--Complex soap exception with multiple 
and nested elements with attributes.-->
<detail>
    <faulttype>Data</faulttype>
    <errorlist>
        <field errorcode="Client" 
           errortype="Provision">[127.0.0.1]</field>
    </errorlist>
</detail>

Catching complex SOAP exceptions:

C#
catch (FaultException faultException)
//Catches the SOAP exceptions thrown by web services
{
    var fault = faultException.CreateMessageFault();
    var doc = new XmlDocument();
    var nav = doc.CreateNavigator();
    if (nav != null)
    {
        using (var writer = nav.AppendChild())
        {
            fault.WriteTo(writer, EnvelopeVersion.Soap11);
            
        }
        
        //this is where you implement your API specific code 
        //to handle the returned detail.
        string s = doc.InnerXml; //do something with it
        this.rtbMessage.Text = s;
    }
}

Wow! This is actually smaller than the multiple element example. It is because the Web Service provider API specifies XML data detail. That means that we simply drop the XML into an XmlDocument object. Then, your job is to use standard XML methods to manage the data. If by chance the data is text, then you can do something like I have in the example above and make a string out of it. You could then parse the string into meaningful data.

Points of Interest

I won't tell you how many days it took to find this solution. I also know that lots of developers do a very bad job of exception handling. More of my clients use ASMX and SOAP exceptions than WCF. As a software architect, I see lots of solutions, good and bad. My experience is that development projects are driven by the lowest bid, or by internal demands that don't foster best practices. Companies lose tons of money on projects, that were rushed to production, when the maintenance begins. Companies also don't have policies of on-going code reviews to look for problems. As an outside contractor, I most often come in when the existing systems have consumed all the existing IT staff's time and they are on the brink of collapse.

I hope this will help you. My thanks again to John Saunders for his contribution. You can see the forum thread here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)