Eric's Blog

Day to day experience in .NET
Welcome to Blogs @ IRM Sign in | Join | Help
 Search

Disclaimer

The content of this site is my own personal opinion and does not in any way represent my employer, it's subsideries or affiliates. These postings are provided "AS IS" with no warranties, and confer no rights.

This Blog

Extending the reach of the custom ExceptionHandler

In two previous posts (Write a custom ExceptionHandler to expose more details in SoapException and An ExceptionHandler that extract the detail information from the SoapException) I wrote about how to create a custom ExceptionHandler for EntLib. The ExceptionHandlers created a SoapException with the real exception serialized in the Detail element, respectively deserialized it. In this post I will describe a modification that I have made to this two classes. I have added special support for serializing my custom BusinessRuleException.
My custom BusinessRuleException is really simple; it just adds an extra property that can contain a DataSet and typically a DataSet with error information. The exception handlers will treat BusinessRueException with the DataSet property set specially by not serializing the exception but instead converting the DataSet to a diffgram. Why? Because a serialized exception requires that the types are available on the client which is something that is not wishful, because you don't want to share class between services. It goes without saying that it will also require the client to be a .NET client, and sure the Diffgram is easily handled by a .NET client, but it is at least also possible for a client of another environment to handle it too. For the conversion I'm using my Convert-class that I wrote about here
For the SoapServerExceptionHandler I just added an overloaded version of the SerializeException method, that looks like this:
public static XmlElement SerializeException(IRM.BusinessRuleException exception)
{
    if (exception.DataSet == null) return SerializeException(exception, true);
    XmlDocument dom = new XmlDocument();
    XmlNode node = dom.AppendChild(dom.CreateElement("detail"));
    XmlElement el = Convert.ToDiffGram(exception.DataSet);
    node = node.AppendChild(dom.CreateElement(namespacePrefix, schemaName, detailNamespace));
    node.InnerXml = exception.DataSet.GetXmlSchema();
    node.RemoveChild(node.FirstChild); //Remove Xml Declaration
    node.RemoveChild(node.FirstChild); //Remove Whitespace
    node = dom.FirstChild;
    node = node.AppendChild(dom.CreateElement(namespacePrefix, diffgramName, detailNamespace));
    node.AppendChild(dom.ImportNode(el, true));
    return dom.DocumentElement;
}
The code is straightforward and simply adds one node with schema information and another node with the diffgram. The schema is necessary if you want to create a DataSet on the client. This brings us to the client code:
public override Exception HandleException(Exception exception, string policyName, Guid handlingInstanceId)
{
    SoapException soapEx = exception as SoapException;
    if (soapEx == null) return exception;
    if (soapEx.Detail == null) return exception;
    if (soapEx.Detail.ChildNodes.Count < 2) return exception;
    Exception ex = null;
    XmlReader reader = null;
    XmlWriter writer = null;
    System.IO.MemoryStream stream = new System.IO.MemoryStream();
    try
    {
        XmlNode node = null;
        foreach (XmlNode childNode in soapEx.Detail.ChildNodes)
        {
            if (childNode.NodeType==XmlNodeType.Element)
            {
                node = childNode;
                break;
            }
       }
       if (node!=null)
       {
           if (node.LocalName=="SOAP-ENV" &&
               node.NamespaceURI=="http://schemas.xmlsoap.org/soap/envelope/")
           {
               reader = new XmlNodeReader(node);
               writer = new XmlTextWriter(stream, System.Text.Encoding.UTF8);
               writer.WriteNode(reader, true);
               writer.Flush();
               stream.Position = 0;
               SoapFormatter formatter = new SoapFormatter();
               ex = formatter.Deserialize(stream) as Exception;
          }
          else if (node.LocalName==Xml.ExceptionSerializer.schemaName &&
                     node.NamespaceURI==Xml.ExceptionSerializer.detailNamespace)
          {
              reader = new XmlNodeReader(node);
              System.Data.DataSet data = new System.Data.DataSet();
              data.ReadXmlSchema(reader);
              reader.Close();
              while (node.NextSibling!=null)
              {
                  node = node.NextSibling;
                  if (node.NodeType==XmlNodeType.Element)
                      break;
              }
              node = node.FirstChild; //not interested in the irm diffgram node
              while (node!=null && node.NodeType!=XmlNodeType.Element)
                  node = node.NextSibling;
              data.AcceptChanges();
              if (node!=null)
                 IRM.Xml.Convert.ToDataSet((XmlElement)node, data);
              ex = new IRM.BusinessRuleException(data);
           }
       }
    }
    catch (System.Runtime.Serialization.SerializationException serEx)
    {
        System.Diagnostics.Debug.WriteLine(serEx.Message);
    }
    finally
    {
        if (reader != null)
            reader.Close();
        if (writer != null)
            writer.Close();
        ((IDisposable)stream).Dispose();
    }
    if (ex == null) ex = exception;
    return ex;
}
If you compare this code with the one posted first, you will notice that I have added some more checks in the beginning and that I also uses a safer way to get to the nodes in the xml document. The logical differences is that I now check for a SOAP envelope (=a serialized exception) and then checking for my own nodes that will contain the dataset with error information. If it is a DataSet, I recreate the structure of it by reading the schema and then again using the Convert-class to fill the DataSet with the data. Finally I recreate a BusinessRuleException that is later returned to the exception handler logic of EntLib.
With this code in place I have been able to get the error information to propagate through several services up to the client. Before I had to catch the exception in each service just to parse the error information and reset it on the structure of that service and then pass it along. This way the code gets cleaner and faster. 
Published den 4 september 2005 12:21 by ericqu
Filed under: ,

Comments

 

C# .Net Tales said:

I&#39;ve been using the new Web Services Factory and am looking into the arguments behind Exception Shielding

oktober 18, 2006 05:22
New Comments to this post are disabled
Powered by Community Server, by Telligent Systems