Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C#
Article

WCF Error Handling and Fault Conversion

Rate me:
Please Sign up or sign in to vote.
4.69/5 (45 votes)
24 May 2008CPOL8 min read 243.3K   5.5K   97   17
This article describes the WCF error-handling paradigm, and provides a mechanism for automatic mapping of exceptions to WCF faults.

Introduction

WCF seems like an easy technology to master. It serves as the ultimate example of framework design -- it is simple for simple tasks, and not too complicated for complex ones; it is extensible and replaceable; its API is well-designed and convenient. One of the greatest challenges I encountered with WCF has to do with overcoming a very natural tendency .NET developers have: The Fallacy of the .NET-Only World.

WCF is designed for service orientation, and it is easy indeed to write explicitly demarcated services using WCF. Such services will grow to be first-class citizens in today's interoperable world, revolving around dozens of constantly changing standards. However, it is also immensely easy to write coupled .NET-to-.NET solutions, even if you didn't intend to.

There are numerous areas in which this coupling can manifest itself, threaded through contract design, service boundaries, error handling -- nearly every aspect of a proper service. For example, it might seem very natural to use a class hierarchy as the foundation of a data contract for a WCF service. However, the very concept of inheritance is alien to the service world: it is a concept from object-oriented programming -- a .NET concept, not a service concept. Another example is error handling -- what could be more natural than throwing an exception to indicate failure? But, exceptions are again a .NET concept -- what value does a service stack trace provide to the service consumer? How can exception data be propagated and consumed? What does an exception hierarchy mean, bringing us back to the inheritance example?

But, if on the one hand, the concepts of inheritance and exceptions are so deeply ingrained in our development process, and on the other hand, these concepts are alien to the WCF world, isn't it terribly hard to write proper service-oriented applications? It is, unless you have framework support for bridging this gap.

In this article, we will look into managing an automatic bridge between the .NET exception-handling world and the service-oriented WCF error-handling paradigm, namely faults.

Faults at a Glance

A lot has been written in the past on the subject of WCF error handling, so I will only mention the basic principles. For more detailed information, consult the MSDN documentation or any good introductory text on WCF.

The underlying principle of service-oriented error handling consists of SOAP fault messages, which convey the failure semantics and additional information associated with the failure (such as the reason).

Most services which require error handling also require additional information to be passed with the error notification. This information can be transferred to the client as a standard WCF data contract, in the disguise of a fault. The contractual specification that a particular service operation can result in the specified fault is called a fault contract. The following code demonstrates a service operation contract that can result in the MyApplicationFault fault message:

C#
[ServiceContract]
public interface IMyService {
    [OperationContract]
    [FaultContract(typeof(MyApplicationFault))]
    void MyMethod();
}

As far as the client is concerned, this is a contract -- the service has just committed to only letting the MyApplicationFault fault message escape its boundaries. The client can now expect this fault message in his communication with the service.

Producing and Consuming Faults

The WCF service can produce a fault that is part of its fault contract, by throwing an exception. Throwing an exception is the most natural thing to do to indicate failure, for a .NET developer. The service is expected to throw the FaultException<TDetail> generic exception, with TDetail being the actual fault type that is being conveyed to the client. For example, the following service code conveys the MyApplicationFault fault to the client:

C#
class MyService : IMyService {
    public void MyMethod() {
        MyApplicationFault fault = new MyApplicationFault(...);
        throw new FaultException<MyApplicationFault>(fault);
    }
}

Consuming the fault on the .NET client side is as simple as catching the FaultException. It's possible to catch the non-generic FaultException base class, or any specific generic derivative. It's also reasonable to expect communication- related exceptions that have nothing to do with the specific service implementation. For example, the following client code is a sensible way of calling the MyService service and catching the fault:

C#
IMyService proxy = ...;    //Get proxy from somewhere
try {
    proxy.MyMethod();
}
catch (CommunicationException) { ... }
catch (TimeoutException) { ... }
catch (FaultException<MyApplicationFault> myFault) {
    MyApplicationFault detail = myFault.Detail;
    //Do something with the actual fault
}
catch (FaultException otherFault) { ... }

This code defensively assumes that a communication problem might occur, a timeout might happen while waiting for the service call to complete, and a general unexpected fault might occur (other than the MyApplicationFault that is expected by the client).

What happens, though, if an unexpected exception escapes from the service side? Well, the client receives a non-generic FaultException that doesn't convey too much information, and the client's channel is faulted (it is possible to obtain more information on the fault by using ServiceBehavior.IncludeExceptionDetailInFaults, but it's good for the debugging environment only -- you don't want to expose server stack traces to the outside world). All in all, it is not a friendly behavior, and should be avoided at all possible costs.

But, is it really so easy to avoid? Consider a typical service, that does a little more than just adding a couple of numbers together and returning the result. It probably calls into lots of downstream classes, which are by no means aware of the fact that they are being called by a specific service method, with a specific fault contract. Even if they were aware of it, coupling their implementation to the fault contract is a poor design decision, which decreases their general reusability. On the other hand, putting try...catch blocks inside every service call and deciding what fault message to return is a tedious task as well. This is another classic example of something we want the framework to perform on our behalf.

Error Handling Behavior

WCF has an excellent built-in extensibility mechanism for converting exceptions to faults. This extensibility point can be consumed through the IErrorHandler interface, which provides two methods: HandleError and ProvideFault. The HandleError method is called on a separate thread after the call has already completed, to possibly log the error and perform other book-keeping operations. It is useless in our scenario. The ProvideFault method, on the other hand, is called on the worker thread that is invoking the service call, and accepts the exception that was thrown by the service. It is expected to provide a fault message that will be sent to the client, and thus fits exactly what we are trying to accomplish. At runtime, an implementation of these methods can be hooked up to the ChannelDispatcher on the service side, and automatically get called whenever an unhandled exception escapes the service code.

We will begin with the core of the error handler. How do we decide what to do with the exception? Well, our first attempt could be converting any exception to a FaultException<TDetail> with TDetail as the exception type. For example, if an ArgumentException could be thrown from downstream code, then I should place the [FaultContract(typeof(ArgumentException))] attribute on my operation, and let the error handler convert the exception to a FaultException<ArgumentException> "fault". Considering that .NET exceptions are expected to be serializable, this is the minimal-effort path. On the other hand, a .NET exception type contains too much information that is only relevant on the service side. While easily achievable, this approach will reveal implementation details and contribute to the .NET- only fallacy.

To enforce stronger separation, we need a mapping mechanism between .NET exceptions and faults. While it is theoretically possible to establish such a mapping in a service-wide or even process-wide manner, it probably makes more sense to perform it in an operation-specific fashion. Like any operation-specific behavior, this is a good candidate for an attribute -- so essentially, what we're looking for is this kind of a syntax:

C#
[ServiceContract]
public interface IMyService {
    [OperationContract]
    [FaultContract(typeof(MyApplicationFault))]
    [MapExceptionToFault(typeof(ApplicationException), typeof(MyApplicationFault))]
    void MyMethod();
}

What are we saying here? First of all, we're specifying that the MyApplicationFault fault is part of the operation's fault contract. Second, we're specifying that whenever an ApplicationException exception is thrown, it should be converted to a MyApplicationFault fault. This is fairly easy to implement:

C#
sealed class ErrorHandler : IErrorHandler {
    public void ProvideFault(Exception error,
                 MessageVersion version,
                 ref Message fault)
        {
            //If it's a FaultException already, then we have nothing to do
            if (error is FaultException)
                return;

            //Get the operation description; omitted for brevity
            OperationDescription operationDesc = ...;

            object faultDetail = GetFaultDetail(operationDesc.SyncMethod,
                        operationDesc.Faults,
                        error);
            if (faultDetail != null)
            {
                Type faultExceptionType =
                    typeof(FaultException<>).MakeGenericType(faultDetail.GetType());
                FaultException faultException =
                    (FaultException)Activator.CreateInstance(
            faultExceptionType, faultDetail, error.Message);
                MessageFault faultMessage = faultException.CreateMessageFault();
                fault = Message.CreateMessage(version,
                          faultMessage,
                          faultException.Action);
            }
        }

        private object GetFaultDetail(MethodInfo method,
        FaultDescriptionCollection faults,
            Exception error)
        {
            if (method != null)
            {
                MapExceptionToFaultAttribute[] mappers =
            (MapExceptionToFaultAttribute[])
                    method.GetCustomAttributes(
            typeof(MapExceptionToFaultAttribute), true);
                foreach (MapExceptionToFaultAttribute mapAttribute in mappers)
                {
                    if (mapAttribute.ExceptionType == error.GetType())
                    {
            //Creates an instance of the fault detail
            //based on the exception object
                        faultDetail =
                mapAttribute.GetFaultDetailForException(error);
                        if (faultDetail != null)
                        {
                            return faultDetail;
                        }
                    }
                }
            }
        //No mapping found, so try the fault contract:
            foreach (FaultDescription faultDesc in faults)
            {
                if (faultDesc.DetailType == error.GetType())
                {
                   faultDetail = error;
                   break;
                }
            }
            return null;
        }
    //Other members omitted for brevity
}

If no mapping attribute is present, the code automatically attempts to convert the exception into a fault, if the fault is part of the contract. If there is a mapping attribute present, then it is used to perform the conversion.

One of the tricky parts in writing the above code was getting the operation description for the currently executing service operation. Juval Lowy, in his "Programming WCF Services" book, parses the service type, looking for interface implementations and attributes on interface methods. I found an alternative, which uses the cleaner WCF API to find the currently executing operation:

C#
OperationContext context = OperationContext.Current;
ServiceEndpoint endpoint =
    context.Host.Description.Endpoints.Find(
        context.EndpointDispatcher.EndpointAddress.Uri);
DispatchOperation dispatchOperation =
    context.EndpointDispatcher.DispatchRuntime.Operations.Where(
        op => op.Action == context.IncomingMessageHeaders.Action).First();
OperationDescription operationDesc =
    endpoint.Contract.Operations.Find(dispatchOperation.Name);

Installing the Error Handling Behavior

How do we go about installing this error handler on every channel dispatcher on our service? Well, it's as simple as defining an attribute and implementing IServiceBehavior. Decorating our service class with this attribute will then install the error handler when the service host is opened. The following code demonstrates what the behavior needs to do:

C#
public sealed class ErrorHandlingBehaviorAttribute : Attribute, IServiceBehavior
{
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcherBase chanDispBase in
         serviceHostBase.ChannelDispatchers)
        {
            ChannelDispatcher channelDispatcher =
        chanDispBase as ChannelDispatcher;
            if (channelDispatcher == null)
                continue;
            channelDispatcher.ErrorHandlers.Add(new ErrorHandler(...));
        }
    }
    //Other interface methods omitted for brevity
}

This behavior can now be applied to a service implementation class:

C#
[ErrorHandlingBehavior]
class MyService : IMyService ...

For completeness (to provide this behavior via configuration), a behavior extension element is required -- but, this is one of the service traits that you will most often want to provide through code and not configuration.

Explicit Translation

The declarative model is not always sufficient for any service. Sometimes, the translation semantics must be specified only at runtime -- something attributes cannot provide. One possible approach is to specify a type that augments the exception conversion process as part of the attribute placed on the service. We can define the following interface for external types to implement:

C#
public interface IExceptionToFaultConverter
{
    object ConvertExceptionToFaultDetail(Exception error);
}
//Example implementation:
class MyServiceFaultProvider : IExceptionToFaultConverter
{
    public object ConvertExceptionToFaultDetail(Exception error)
    {
        if (error is ApplicationException)
            return new MyApplicationFault(...);
        return null;
    }
}

...and then specify the type that assists in the conversion as part of the attribute, when implementing the service:

C#
[ErrorHandlingBehavior(
    ExceptionToFaultConverter=typeof(MyServiceFaultProvider))]
class MyService : IMyService ...

Adding support for this feature to the error handler itself is trivial (the necessary code is included as part of the source download for this article).

Summary

The approach outlined in this article allows service developers to focus on their business logic and call downstream facilities directly. It absolves service developers from the need to worry about letting only permitted faults escape the service boundary, and provides a convenient mechanism for mapping .NET exceptions to well-defined WCF faults.

History

  • Version 1 -- May 24th, 2008

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer SELA Group
Israel Israel
Sasha Goldshtein is the CTO of SELA Group, an Israeli company specializing in training, consulting and outsourcing to local and international customers.

Sasha's work is divided across these three primary disciplines. He consults for clients on architecture, development, debugging and performance issues; he actively develops code using the latest bits of technology from Microsoft; and he conducts training classes on a variety of topics, from Windows Internals to .NET Performance.

You can read more about Sasha's work and his latest ventures at his blog: http://blogs.microsoft.co.il/blogs/sasha

Sasha writes from Jerusalem, Israel.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 1123488822-Jun-19 11:43
Member 1123488822-Jun-19 11:43 
GeneralMy vote of 3 Pin
Qwertie7-Jun-19 10:07
Qwertie7-Jun-19 10:07 
GeneralRe: My vote of 3 Pin
Qwertie8-Jun-19 12:31
Qwertie8-Jun-19 12:31 
QuestionThank you! Pin
Member 223287521-Aug-15 2:33
Member 223287521-Aug-15 2:33 
GeneralMy vote of 5 Pin
Joe Egan5-Jul-13 7:59
Joe Egan5-Jul-13 7:59 
GeneralWell written Pin
Espen Harlinn16-Aug-12 10:21
professionalEspen Harlinn16-Aug-12 10:21 
QuestionAm i missing something simple about losing server connection in WCF? Pin
MikeKosak17-Oct-11 3:18
MikeKosak17-Oct-11 3:18 
QuestionRe: Am i missing something simple about losing server connection in WCF? Pin
stixoffire23-Sep-15 4:18
stixoffire23-Sep-15 4:18 
GeneralMy vote of 5 Pin
Joshua Ramirez21-Apr-11 5:42
Joshua Ramirez21-Apr-11 5:42 
Generalreturn error as JSON Pin
kuttan227-Jan-11 4:20
kuttan227-Jan-11 4:20 
How can i return error as JSON not as XML?
GeneralMy vote of 5 Pin
Dmitriy Modlin1-Oct-10 8:42
Dmitriy Modlin1-Oct-10 8:42 
GeneralMy vote of 4 Pin
Hasmukhkp14-Jul-10 2:26
Hasmukhkp14-Jul-10 2:26 
GeneralExcellent Article in My Opinion Pin
lou_davis24-Feb-10 7:56
lou_davis24-Feb-10 7:56 
GeneralVery nice Pin
AroglDarthu27-Mar-09 8:54
AroglDarthu27-Mar-09 8:54 
RantException Shielding Pin
an_phu22-Aug-08 15:00
an_phu22-Aug-08 15:00 
General[Message Removed] Pin
Mojtaba Vali24-May-08 18:22
Mojtaba Vali24-May-08 18:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.