Catching the real exception …

Posted on January 4, 2011 by

2


In the previous post (see whose-fault-is-it), I mentioned that there is a trick you can implement in order to enable your clients catch the exact exception thrown by the server instead of the generic FaultException. This contradicts the WCF pattern according to which the client should catch only FaultExceptions. The reason WCF promotes catching generic FaultsExceptions  is to prevent exposing the server’s internals to unauthorized parties. When performing communication internal to your system in which both the client and the server are completely controlled by the system developer there is no concern in exposing the server internals, and we might even prefer exposing the server internals in favor of  better debugging capabilities.

In our system we prefer only catching explicit exceptions  instead of catching a global FaultException, because we believe a developer should thoroughly think how to handle each error case. We prefer crashing the process on unexpected error types which usually hide unknown bugs and might leave the system in an unstable state.

This is why we decided to implement this trick and catch explicit exceptions types on the client side.

To enable this behavior, you will have to tweak both the server side and the client side.

Server side

On the server side, we will create a custom error handler (see https://wcfpro.wordpress.com/2010/11/18/wcf-extensions-error-handler/).

This ErrorHandler will create a custom FaultException using the MessageFault.CreateFault static method:

public static MessageFault CreateFault(FaultCode code,
        FaultReason reason, 
        object detail, 
        XmlObjectSerializer serializer);

The created fault message will hold internally, in its Detail property, the real exception thrown by the server. Later on, the client side we will reconstruct this exception.

    internal class MyErrorHandler : IErrorHandler
    {
        #region IErrorHandler Members

        public bool HandleError(Exception error)
        {
            return false;
        }

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            MessageFault faultMessage = MessageFault.CreateFault(
                new FaultCode("Sender"),
                new FaultReason(error.Message),
                error, // This will go into the detail property
                new NetDataContractSerializer());

            fault = Message.CreateMessage(version, faultMessage, null);
        }

        #endregion
    }

Client side

On the client side, we will reconstruct the exception stored in the Detail property of the received FaultException.

For this, we will have a custom implementation of IClientMessageInspector, which in case of an error (fault message) creates the real exception from the message, and throws it back to the client.

    /// <summary>
    /// This MessageInspector is used to rethrow to the client exceptions as normal exceptions
    /// after a message is received on the client side.
    /// </summary>
    internal class MyExceptionHandlingMessageInspector : IClientMessageInspector
    {
        #region IClientMessageInspector Members

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            if (reply.IsFault)
            {
                // Create a copy of the original reply to allow default WCF processing
                var buffer = reply.CreateBufferedCopy(Int32.MaxValue);

                // Create a copy to work with
                var copy = buffer.CreateMessage();

                // Restore the original message 
                reply = buffer.CreateMessage();

                var exception = ReadFaultDetail(copy) as Exception;

                if (exception != null) 
                {
                    throw exception;
                }
            }

        }

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            return null;
        }
        #endregion


        /// <summary>
        /// Used to locate the FaultDeail of the reply message which will be used to generate the 
        /// new Exception
        /// </summary>
        /// <param name="reply"></param>
        /// <returns></returns>
        private static object ReadFaultDetail(Message reply)
        {
            const string detailElementName = "Detail";

            using (XmlDictionaryReader reader = reply.GetReaderAtBodyContents())
            {
                // Find <soap:Detail>
                while (reader.Read())
                {
                   if (reader.NodeType == XmlNodeType.Element && detailElementName.Equals(reader.LocalName))
                    {
                        break;
                    }
                }
                if (reader.EOF)
                {
                    return null;
                }

                // Move to the contents of <soap:Detail>
                if (!reader.Read())
                {
                    return null;
                }

                // Deserialize the fault
                NetDataContractSerializer serializer = new NetDataContractSerializer();

                try
                {
                    return serializer.ReadObject(reader);
                }

                catch (SerializationException ex)
                {
                    return new CommunicationException("SerializationException", ex);
                }
            }
        }
    }

 

Inserting the custom IClientMessageInspector is done by adding it through IEndpointBehavior, the following way:

    internal class ClientExceptionHandlingBehavior : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint serviceEndpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        { }

        public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            //Add the inspector
            clientRuntime.MessageInspectors.Insert(new MyExceptionHandlingMessageInspector());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        { }

        public void Validate(ServiceEndpoint serviceEndpoint)
        { }
    }

And, that’s it Smile

Advertisements
Posted in: Error Handling