SoapExceptions

Jan Tielens in his blog brought up an approach http://weblogs.asp.net/jan/posts/27835.aspx he used to create SoapExceptions. The code/concept is similar to something I wrote here where I am working. However, we added a twist that I thought was kindof interesting and worth sharing. Specifically, our web services are basically a service facade into an underlying business tier. This is nothing new to anyone. We also make it a point to say that all exceptions emanating from our business tier are derived from ApplicationException and defined also within our tier. This means that we have total control of the definition of all of our exceptions. Again, a relatively common practice and probably nothing new to many of you.

Now, since we own the definitions of all of these exceptions, we created several custom attributes that let us use a SoapFactory I wrote to create a SoapException from any of our Business Exceptions. This simplifies our WebMethod code tremendously. For example:

[ WebMethod ( Description = "Removed for brevity") ]
public CustomerProfile GetXXXX( string identifier )
{
        try
        {
                return Customer.CreateXXXFromYYY( identifier );
        }
        catch( Exception e )
        {
                throw CybralSoapException.Create( e );
        }
}

And the definition code for one one of my exception look like this. You will notice that I decorated the exceptions with two new Attributes CybralSoapFaultCode and CybralSoapErrorCode.

CybralSoapFaultCode allows me to decorate the XmlQualifiedName consistently anytime a specific exception is thrown.

CybralSoapErrorCode is an attribute that allows me to populate the detail block of a SoapException with a common key defined in the enumeration ProfileSoapKeys. By using an enumeration to fill in the key and providing it in the Attribute I can do typechecking to make sure that there are no misspellings. This means that I don't have to worry about misspellings in my keys and a client can be certain that I did spell it right when they evaluate the Detail block in order to figure out any application specific error information ( a small thing but worth the trouble ) (Also using the Enumeration was the idea of a coworker of mine. He had a bug where a misspelling occurred and he vowed he never wanted it to happen again;):

[Serializable]
[CybralSoapFaultCode( "ClientFaultCode" )]
[CybralSoapErrorCode( typeof(ProfileSoapKeys),"DataSourceUnavailable")]
public class DataSourceUnavailableException : BaseApplicationException
{
        protected const string _message = "A requested datasource is unavailable.";
        public DataSourceUnavailableException() : base( _message ){}
        public DataSourceUnavailableException( Exception inner ) : base( _message, inner ){}
        public DataSourceUnavailableException(string message) : base(message){}
        public DataSourceUnavailableException(string message, Exception inner) : base(message,inner){}
        protected DataSourceUnavailableException(SerializationInfo info, StreamingContext context) : base(info, context){}
}

Now the CybralSoapException factory method code looks like this:
 
public static CybralSoapException Create( Exception ex  )
{
        MemberInfo info = ex.GetType();
        // get the errorCode
        object[] attributes = info.GetCustomAttributes( typeof( CybralSoapErrorCodeAttribute ), false );
        Enum errorCode = (attributes.Length > 0) ? ((CybralSoapErrorCodeAttribute)
        attributes[0]).ErrorCode : CybralSoapExceptionKeys.UnexpectedSystemError;
        // get the faultCode
        attributes = info.GetCustomAttributes( typeof( CybralSoapFaultCodeAttribute ), false );
        XmlQualifiedName faultCode = (attributes.Length > 0) ? ((CybralSoapFaultCodeAttribute) 
        attributes[0]).FaultCode : SoapException.ServerFaultCode;
        // THIS CONTRUCTOR MERELY FORMATS THE DETAIL BLOCK FOR ME.
        // Also this class is derived from SoapException which means it gets caught correctly by all clients as a SoapException
        // Of course it will lose all reference to CybralSoapException and will be formatted as a regular SoapException, but thats ok.
       
return
new CybralSoapException ( ex.Message, faultCode, errorCode, System.Web.HttpContext.Current.Request.Path, ex );
}

I could get into the code of the attributes, but I am starting to annoy myself with the length of this blog. As always, feedback is appreciated.

-Mathew Nolton

No Comments