Automatically Converting Exceptions to WCF Faults

April 12, 2008

tags:
7 comments

One of the annoyances in service design is that you have to dedicate lots of thinking to your error propagation strategy.  Any application framework or even utility class should be well-defined with regard to exceptions flowing to the outside world; however, when services are concerned, this is a matter of utmost importance.

The reason for the distinction is the following simple notion: If you’re dealing with an object, as the object’s client you are very strongly coupled to its exception model.  You are well-aware of the fact that exceptions can propagate from the object and you have the facilities for catching those exceptions and acting according.  However, if you’re dealing with a service, as the service’s client you are decoupled from its exception model.  In fact, you don’t care what it uses internally (be it exceptions, Win32 error codes, HRESULTs or longjmp’s), as long as there is a well-defined contract that exposes these errors externally.

In WCF, errors are exposed externally through the use of a fault contract.  An operation can specify that certain kinds of faults can escape it – this is similar to an exception specification in C++ or in Java.  While it is possible for standard .NET exceptions to cross service boundaries, a fault contract is the cleanest way that can be discovered in advance, as part of the service description.

The problem begins when you want to map .NET exceptions (which are the most straightforward way of dealing with application errors) to WCF faults.  This basically means that your top-level service methods have to take the following form:

public void SomeOperation()

{

    try

    {

        DoSomething();

        DoSomethingElse();

        DoSomethingElseEntirely();

    }

    catch (InvalidOperationException iopEx)

    {

        throw new FaultException<InvalidOperationFault>(iopEx.ToString());

    }

    catch (SecurityException)

    {

        throw new FaultException<SecurityFault>();

    }

    catch (Exception ex)

    {

        throw new FaultException(ex.Message);

    }

}

Note that in this particular case, there is a well-defined mapping the application has established between particular types of exceptions to particular types of faults (the InvalidOperationFault and SecurityFault classes were made up for this specific purpose).  However, this mapping is not automatic and therefore very error-prone.  And you don’t want your exception propagation to be error-prone, right?

I was looking for an automatic approach to this scenario, and just like with everything else, WCF has an extensibility point to provide exactly what we’re looking for.  There are two well-defined ways to provide fault messages corresponding to an exception.  The first is through the use of a FaultConverter, which is what you use if you’re authoring a custom channel.  However, authoring a custom channel (or even binding element) for the sole purpose of translating exceptions to faults is an overkill.

The second approach is implementing the IErrorHandler interface and registering your object to the channel dispatcher’s error handlers’ collection.  This can be done as a service behavior, in about 20 lines of code.  Providing the mapping from exceptions to faults is not very difficult either:

public static class ExceptionToFaultConverter

{

    private static Dictionary<Type, Delegate> _converters =

        new Dictionary<Type, Delegate>();

 

    public static void RegisterConverter<TException, TFault>(

        Func<TException, TFault> converter)

    {

        _converters.Add(typeof(TException), converter);

    }

 

    public static object ConvertExceptionToFault(Exception ex)

    {

        Delegate converter;

        if (!_converters.TryGetValue(ex.GetType(), out converter))

            return null;

 

        return converter.DynamicInvoke(ex);

    }

}

With this simple framework in place, when we have a new exception type to map to a new fault contract, we can go ahead and do it:

ExceptionToFaultConverter.RegisterConverter<

    InvalidOperationException, InvalidOperationFault>(

        x => new InvalidOperationFault {ErrorDetails=x.ToString()});

We could also filter by the actual operation, the contract name, the fault contracts on the operation, or whatever we want really, because this is what the point of invocation looks like for our extension:

class MyErrorHandler : IErrorHandler

{

    public bool HandleError(Exception error)

    {

        return false;

    }

    public void ProvideFault(Exception error, MessageVersion version,

        ref Message fault)

    {

 

        object faultDetail =

            ExceptionToFaultConverter.ConvertExceptionToFault(error);

 

        //Find fault contracts for current operation.

        OperationContext ctx = OperationContext.Current;

        ServiceDescription hostDesc = ctx.Host.Description;

        ServiceEndpoint endpoint =

            hostDesc.Endpoints.Find(

                ctx.IncomingMessageHeaders.To);

        string operationName =

            ctx.IncomingMessageHeaders.Action.Replace(

                endpoint.Contract.Namespace +

                endpoint.Contract.Name + “/”,

                “”);

        OperationDescription operation =

            endpoint.Contract.Operations.Find(operationName);

        foreach (FaultDescription faultDesc in operation.Faults)

        {

            if (faultDesc.DetailType.IsAssignableFrom(

                faultDetail.GetType()))

            {

                //Found a match:

                fault = Message.CreateMessage(

                    version,

                    FaultCode.CreateSenderFaultCode(

                        faultDesc.Name, faultDesc.Namespace),

                    faultDetail.ToString(),

                    faultDetail,

                    faultDesc.Action

                    );

                break;

            }

        }

    }

}

To register this error handler, we need a service behavior attached, like the following one:

class ErrorBehaviorAttribute : Attribute, IServiceBehavior

{

    Type _handlerType;

    public ErrorBehaviorAttribute(Type handlerType)

    {

        _handlerType = handlerType;

    }

 

    #region IServiceBehavior Members

 

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

    {

    }

 

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

    {

        IErrorHandler errorHandler = (IErrorHandler)

            Activator.CreateInstance(_handlerType);

        foreach (ChannelDispatcherBase chanDisp in serviceHostBase.ChannelDispatchers)

        {

            ChannelDispatcher disp = chanDisp as ChannelDispatcher;

            disp.ErrorHandlers.Add(errorHandler);

        }

    }

 

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

    {

    }

 

    #endregion

}

And finally, we need to specify that behavior on our service:

[ErrorBehavior(typeof(MyErrorHandler))]

class ServiceImpl : IService

This gives us the automatic facilities for mapping .NET exceptions to WCF faults.  This is a simplistic sample, but it shows what the possibilities really are.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

7 comments

  1. CaroleApril 26, 2008 ב 2:24 AM

    Thanks for the detailed information on converting exceptions to faults on the service side.

    What do you do on the client side, though, if you want to convert the fault back to an exception? I would like to apply some sort of handler in my proxy so that FaultException faults are caught on all OperationContract methods and converted back to the exception defined in MyExceptionDetail.

    Reply
  2. Sasha GoldshteinMay 26, 2008 ב 12:57 PM

    Well, on the client side you’ll need a custom channel which will provide a FaultConverter when asked for by the GetProperty method. This is outlined in Nicholas Allen’s blog: http://blogs.msdn.com/drnick/archive/2006/12/29/consuming-faults-part-2.aspx

    The question has also been asked on the MSDN forums, and someone suggested using an IClientMessageInspector to read the fault detail and throw the exception. I feel that this is unsafe because there’s additional WCF processing after message inspectors are called (e.g. parameter inspectors), but apparently the OP got a confirmation from Microsoft that this is a feasible option. Anyway, it’s much easier than writing a custom channel.

    Reply
  3. UildsonJune 24, 2009 ב 4:16 PM

    IMPORTANT!: fix your fault action description, like “action= http://fault” or you will not be able to properly deserialize the fault by the client-side.

    Reply
  4. Jorge VinagreAugust 4, 2009 ב 6:57 PM

    Hi
    Just tell me something, where is the best place to register the Faults?

    Reply
  5. Sergey NamnyasovJanuary 20, 2010 ב 1:01 PM

    Instead of ExceptionToFaultConvertor you can use Exception Handling Application Block from Enterprise Library

    Reply
  6. Sergey NamnyasovJanuary 20, 2010 ב 2:26 PM

    Instead of
    ServiceEndpoint endpoint =
    hostDesc.Endpoints.Find(
    ctx.IncomingMessageHeaders.To);

    is better to use

    ServiceEndpoint endpoint =
    hostDesc.Endpoints.Find(
    ctx.EndpointDispatcher.EndpointAddress.Uri);

    because “ctx.IncomingMessageHeaders.To” may contain another host address.
    For example IP-address instead of the domain name or vice versa

    Reply
  7. kubaApril 21, 2011 ב 3:46 PM

    Ahhh….
    I don’t think sucha a desing for blog (really narrow) is a good idea when posting entries with code.

    Reply