Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCF exception handling using IErrorHandler

I am basically implementing the IErrorHandler interface to catch all kinds of exceptions from the WCF service and sending it to the client by implementing the ProvideFault method.

I am facing one critical issue however. All of the exceptions are sent to the client as a FaultException but this disables the client to handle specific exceptions that he may have defined in the service.

Consider: SomeException that has been defined and thrown in one of the OperationContract implementations. When the exception is thrown, its converted to a fault using the following code:

var faultException = new FaultException(error.Message);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);

This does send the error as a string, but the client has to catch a general exception like:

try{...}
catch(Exception e){...}

and not:

try{...}
catch(SomeException e){...}

Not only custom exceptions like SomeException, but system exceptions like InvalidOperationException cannot be caught using the above process.

Any ideas as to how to implement this behavior?

like image 585
p0lAris Avatar asked Jul 31 '13 04:07

p0lAris


People also ask

What is exception handling in WCF?

Advertisements. A WCF service developer may encounter some unforeseen errors which require reporting to the client in a suitable manner. Such errors, known as exceptions, are normally handled by using try/catch blocks, but again, this is very technology specific.

Which of the exception does WCF throw?

Errors of this kind are sent to the client as FaultException or FaultException<TDetail>. Error handling in WCF is performed by one or more of the following: Directly handling the exception thrown. This is only done for communication and proxy/channel errors.


2 Answers

In WCF desirable to use special exceptions described as contracts, because your client may not be .NET application which has information about standard .NET exceptions. For do this you can define the FaultContract in the your service and then use the FaultException class.

SERVER SIDE

[ServiceContract]
public interface ISampleService
{
    [OperationContract]
    [FaultContractAttribute(typeof(MyFaultMessage))]
    string SampleMethod(string msg);
}

[DataContract]
public class MyFaultMessage
{
    public MyFaultMessage(string message)
    {
        Message = message;
    }

    [DataMember]
    public string Message { get; set; }
}

class SampleService : ISampleService
{
    public string SampleMethod(string msg)
    {
        throw new FaultException<MyFaultMessage>(new MyFaultMessage("An error occurred."));
    }        
}

In addition, you can specify in the configuration file that the server returns exception details in it's FaultExceptions, but this is not recommended in a production application:

<serviceBehaviors>
   <behavior>
   <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
      <serviceDebug includeExceptionDetailInFaults="true"/>
   </behavior>
</serviceBehaviors>

After that, you can rewrite your method for handling exceptions:

var faultException = error as FaultException;
if (faultException == null)
{
    //If includeExceptionDetailInFaults = true, the fault exception with details will created by WCF.
    return;
}
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);

CLIENT:

try
{
    _client.SampleMethod();
}
catch (FaultException<MyFaultMessage> e)
{
    //Handle            
}
catch (FaultException<ExceptionDetail> exception)
{
    //Getting original exception detail if includeExceptionDetailInFaults = true
    ExceptionDetail exceptionDetail = exception.Detail;
}
like image 140
Vyacheslav Volkov Avatar answered Oct 19 '22 11:10

Vyacheslav Volkov


This article might help:

http://www.olegsych.com/2008/07/simplifying-wcf-using-exceptions-as-faults/

An approach I've used with some success when I didn't want to enumerate the exceptions that might be thrown is to create a PassthroughExceptionHandlingBehavior class that implements an IErrorHandler behavior for the server-side and IClientMessageInspector for the client side. The IErrorHandler behavior serializes the exception into the fault message. The IClientMessageInspector deserializes and throws the exception.

You have to attach this behavior to both the WCF client and the WCF server. You can attach the behaviors using the config file or by applying the [PassthroughExceptionHandlingBehavior] attribute to your contract.

Here's the behavior class:

public class PassthroughExceptionHandlingBehavior : Attribute, IClientMessageInspector, IErrorHandler,
    IEndpointBehavior, IServiceBehavior, IContractBehavior
{
    #region IClientMessageInspector Members

    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
        if (reply.IsFault)
        {
            // Create a copy of the original reply to allow default processing of the message
            MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
            Message copy = buffer.CreateMessage();  // Create a copy to work with
            reply = buffer.CreateMessage();         // Restore the original message

            var exception = ReadExceptionFromFaultDetail(copy) as Exception;
            if (exception != null)
            {
                throw exception;
            }
        }
    }

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

    private static object ReadExceptionFromFaultDetail(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, StringComparison.InvariantCultureIgnoreCase))
                {
                    return ReadExceptionFromDetailNode(reader);
                }
            }
            // Couldn't find it!
            return null;
        }
    }

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

        // Return the deserialized fault
        try
        {
            NetDataContractSerializer serializer = new NetDataContractSerializer();
            return serializer.ReadObject(reader);
        }
        catch (SerializationException)
        {
            return null;
        }
    }

    #endregion

    #region IErrorHandler Members

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

    public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
    {
        if (error is FaultException)
        {
            // Let WCF do normal processing
        }
        else
        {
            // Generate fault message manually including the exception as the fault detail
            MessageFault messageFault = MessageFault.CreateFault(
                new FaultCode("Sender"),
                new FaultReason(error.Message),
                error,
                new NetDataContractSerializer());
            fault = Message.CreateMessage(version, messageFault, null);
        }
    }

    #endregion

    #region IContractBehavior Members

    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        ApplyClientBehavior(clientRuntime);
    }

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        ApplyDispatchBehavior(dispatchRuntime.ChannelDispatcher);
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }

    #endregion

    #region IEndpointBehavior Members

    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        ApplyClientBehavior(clientRuntime);
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    {
        ApplyDispatchBehavior(endpointDispatcher.ChannelDispatcher);
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    #endregion

    #region IServiceBehavior Members

    public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
        {
            ApplyDispatchBehavior(dispatcher);
        }
    }

    public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
    {
    }

    #endregion

    #region Behavior helpers

    private static void ApplyClientBehavior(System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        foreach (IClientMessageInspector messageInspector in clientRuntime.MessageInspectors)
        {
            if (messageInspector is PassthroughExceptionHandlingBehavior)
            {
                return;
            }
        }

        clientRuntime.MessageInspectors.Add(new PassthroughExceptionHandlingBehavior());
    }

    private static void ApplyDispatchBehavior(System.ServiceModel.Dispatcher.ChannelDispatcher dispatcher)
    {
        // Don't add an error handler if it already exists
        foreach (IErrorHandler errorHandler in dispatcher.ErrorHandlers)
        {
            if (errorHandler is PassthroughExceptionHandlingBehavior)
            {
                return;
            }
        }

        dispatcher.ErrorHandlers.Add(new PassthroughExceptionHandlingBehavior());
    }

    #endregion
}

#region PassthroughExceptionHandlingElement class

public class PassthroughExceptionExtension : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get { return typeof(PassthroughExceptionHandlingBehavior); }
    }

    protected override object CreateBehavior()
    {
        System.Diagnostics.Debugger.Launch();
        return new PassthroughExceptionHandlingBehavior();
    }
}

#endregion
like image 44
Matt Varblow Avatar answered Oct 19 '22 11:10

Matt Varblow