Exception Injection Using a Custom SOAP Extension

0 comments suggest edit

You kind of get the feeling that Keith Brown has a beef with soap exceptions when he writes that SoapException Sucks. I won’t rehash everything he says here, but the gist of his complaint is that when throwing an exception from within a web service, the exception gets wrapped by a SoapException. What’s so bad about that? As Keith relates, the Message property of the SoapException class intersperses your fault string with a load of other crap you really don’t care about. Also, the InnerException doesn’t get serialized into the SOAP fault packet, so it is always null on the client side.

A couple solutions proposed within his comments require putting a try/catch around the body of every method and construct a suitable SoapException by hand. This just didn’t sit well with me (neither did the burrito I just ate) as it seemed quite repetitive. I figured there had to be a better way. If only there were some way to inject code after a SOAP method is called and before the XML payload is delivered to the client. Fortunately there is. SOAP Extensions!

The solution I hacked together here is to build a custom SoapExtensionAttribute used to mark up a method. If that method throws an exception, the original exception information is serialized into the detail element of the soap exception.

The key here is to remember that SOAP is at its core simply XML text messages being sent back and forth between computers. A SoapExtension lets you peek under the hood and manipulate the actual messages going in and out.

There are three classes involved in this solution, SerializedExceptionExtensionAttribute, SerializedExceptionExtension and SoapOriginalException. I’ll briefly go over each one.

SerializedExceptionExtensionAttribute is a very simple Attribute class that inherits from SoapExtensionAttribute. When applied to a target, this attribute has a property that indicates what type of SoapExtension to use for that target.

SerializedExceptionExtension inherits from SoapExtension and for the most part looks like your typical MSDN example of a soap extension in which you override ChainStream, store the old stream in a member variable, and replace it with a new stream. For the sake of illustration, I will highlight a few methods that make this extension somewhat interesting (at least for me)…

public override void ProcessMessage(SoapMessage message)
{
    if(message.Stage == SoapMessageStage.AfterSerialize)
    {
        _newStream.Position = 0;
        if(message.Exception != null && message.Exception.InnerException != null)
        {
            InsertDetailIntoOldStream(message.Exception.InnerException);
        }
        else
        {
            CopyStream(_newStream, _oldStream);   
        }
    }
    else if(message.Stage == SoapMessageStage.BeforeDeserialize)
    {
        CopyStream(_oldStream, _newStream);
        _newStream.Position = 0;
    }
}

void InsertDetailIntoOldStream(Exception exception)
{
    XmlDocument doc = new XmlDocument();
    doc.Load(_newStream);
    XmlNode detailNode = doc.SelectSingleNode("//detail");

    try
    {
        detailNode.InnerXml = GetXmlExceptionInformation(exception);
    }
    catch(Exception exc)
    {
        detailNode.InnerXml = exc.Message;
    }

    XmlWriter writer = new XmlTextWriter(_oldStream, Encoding.UTF8);
    doc.WriteTo(writer);
    writer.Flush();
}

In the method ProcessMessage, you can see that the code waits till after the method has been serialized to XML (represented by SoapMessageStage.AfterSerialize) and is ready to be sent back to the client. That’s where the exception detail is injected into the stream, assuming an exception did occur.

InsertDetailIntoOldStream finds the detail node within the serialized stream and inserts exception information into that node. In order to make this information useful to both non .NET clients and .NET clients, the exception information is formatted as XML. However, in one of the nodes of that XML, I serialize the exception using a BinaryFormatter. That way, a .NET client can gain access to the full original exception.

string GetXmlExceptionInformation(Exception exception)
{
    string format = "<Message>{0}</Message>"
        + "<Type>{1}</Type>"
        + "<StackTrace>{2}</StackTrace>"
        + "<Serialized>{3}</Serialized>";
    return string.Format(
        format,
        exception.Message,
        exception.GetType().FullName,
        exception.StackTrace,
        SerializeException(exception));
}

string SerializeException(Exception exception)
{
    MemoryStream stream = new MemoryStream();
    IFormatter formatter = new  BinaryFormatter();
    formatter.Serialize(stream, exception);
    stream.Position = 0;
    return Convert.ToBase64String(stream.ToArray());
}

Security Note!, for a production system, you probably don’t want to serialize the original exception as it will contain a stack trace and could give out more information than you wish clients to the service to have. For debugging, however, this is quite useful.

Allow me to walk through how you can apply these classes in your own code. Below, I’ve written a method that simply throws an exception. You can see that I marked it with the SerializedExceptionExtension attribute.

[WebMethod, SerializedExceptionExtension]
public string ThrowNormalException()
{
    throw new ArgumentNullException("MyParameter", "Exception thrown for testing purposes");
}

Now on the client, I simply make a call to the web service within a try/catch clause. In the snippet below, you’ll notice that I wrap the thrown exception with the SoapOriginalException class. That class is a helpful wrapper that knows how to deserialize an exception serialized using this technique. The original exception is accessed via the InnerException property.

void CallService()
{
    TestService proxy = new TestService();
    try
    {
        proxy.ThrowNormalException();
    }
    catch(SoapException e)
    {
        SoapOriginalException realException = new SoapOriginalException(e);
        Console.WriteLine(realException.InnerException.Message);
    }
}

If you’d like to try this technique out yourself and provide a critique, download the ExceptionInjectionWithSoapExtension.zip source files here. There are certainly some enhancements that could be made to the code to make it even more useful. Let me know if you make improvements.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

8 responses

  1. Avatar for drew
    drew June 12th, 2006

    Hi!
    Very interesting article, but what about other platform clients? E.g., java clients will get a java.rmi.RemoteException?
    What way you suggest in the case?

  2. Avatar for Michael Freidgeim
    Michael Freidgeim August 21st, 2006

    I've tryed to use "Print" link, but it left a big empty left margin and truncated right part of the text

  3. Avatar for Haacked
    Haacked August 21st, 2006

    My print.css is messed up. Thanks for the bug report!

  4. Avatar for d
    d October 6th, 2006

    Ok I have a web service and a web client which calls the service.
    WHen the service throws SoapException, the message of the excpetion on the client says that exact message that got thrown.
    When the service throws ApplicationExcpetion, the message of exception on client side says "Server Unavailable".
    SO I used ur extension, hoping that when the ApplicationExcpetion gets thrown, ur soap extension will wrap it up in SoapException and error message on client side will be correct. But that does not happen, why?

  5. Avatar for crezee
    crezee December 5th, 2006

    what is the use of Copystream,I just do not get it ??? can it be explained in detail.

  6. Avatar for RN
    RN April 6th, 2008

    Dont make any sense to me.
    we have an overloaded constructor in the soap Exception class which takes an xml node as one of the argument.
    So just create that node with your custom exception messages and errorcodes.That way when you throw the soapexception your new info also get thrown
    Makes sense to you Hack??
    Thanks,
    RN

  7. Avatar for Saeed Neamati
    Saeed Neamati November 30th, 2011

    Haacked, thanks for this great article. However, what we have to do if we want to change the communication protocol from SOAP, to JSON (like calling web services directly from jQuery, while providing a general exception handler, to return JSON data back, with 50X HTTP family code?)

  8. Avatar for haacked
    haacked November 30th, 2011

    @Saeed, unfortunately, I'm not a WCF expert. However, if you're planning to use JSON, I'd take a look at WCF Web API. They may have better mechanisms.