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:
[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:
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:
IMyService proxy = ...;
try {
proxy.MyMethod();
}
catch (CommunicationException) { ... }
catch (TimeoutException) { ... }
catch (FaultException<MyApplicationFault> myFault) {
MyApplicationFault detail = myFault.Detail;
}
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:
[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:
sealed class ErrorHandler : IErrorHandler {
public void ProvideFault(Exception error,
MessageVersion version,
ref Message fault)
{
if (error is FaultException)
return;
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())
{
faultDetail =
mapAttribute.GetFaultDetailForException(error);
if (faultDetail != null)
{
return faultDetail;
}
}
}
}
foreach (FaultDescription faultDesc in faults)
{
if (faultDesc.DetailType == error.GetType())
{
faultDetail = error;
break;
}
}
return null;
}
}
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:
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:
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(...));
}
}
}
This behavior can now be applied to a service implementation class:
[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:
public interface IExceptionToFaultConverter
{
object ConvertExceptionToFaultDetail(Exception error);
}
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:
[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