Handling exceptions the right way in WCF (part 2)

In my previous post I talked about FaultExceptions and how they are transmitted from the server to the client.
First, let's have a look at the sample service contract :
[ServiceContract]
public interface ITimeService
{
[OperationContract]
[FaultContract(typeof(TimeExceptionDetails))]
DateTime GetTime();
}
The service implementation :
public class TimeService : ITimeService
{
#region ITimeService Members

public DateTime GetTime()
{
return DateTime.Now;
}

#endregion
}
And the Fault details class :
public class TimeExceptionDetails
{
public static readonly TimeExceptionDetails Default = new TimeExceptionDetails();

[DataMember]
public String Message { get; private set; }

public TimeExceptionDetails()
{
this.Message = "There is a problem in the spacetime continuum, Marty";
}

public TimeExceptionDetails(String message)
{
this.Message = message;
}
}

Now we'll learn how to ease the process of creating FaultExceptions by using the IErrorHandler interface. This interface allows the customization of exceptions and provide a centralized location for responding to exceptions in a very generic way.
This interface contains two methods :
  • HandleError
  • ProvideFault
The first one is called each time an exception is raised by the service (logging for example). The second one is more subtle, it allows to change the message that will be sent to the caller. Here for example you could create a FaultException, generate the corresponding message and set it as the message to be sent.
The following code is a simple implementation of the IErrorHandler interface. It simply create a new FaultException and send it to the caller.
public class TimeServiceBehavior : IErrorHandler
{
#region IErrorHandler Members

public bool HandleError(Exception error)
{
// Log the error here
EventLog.WriteEntry("TimeService", error.ToString());

// Let the other ErrorHandler do their jobs
return true;
}

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is FaultException)
return;

// Creates the exception we want to send back to the client
var exception = new FaultException<TimeExceptionDetails>(
TimeExceptionDetails.Default,
new FaultReason(TimeExceptionDetails.Default.Message));

// Creates a message fault
var messageFault = exception.CreateMessageFault();

// Creates the new message based on the message fault
fault = Message.CreateMessage(version, messageFault, exception.Action);
}

#endregion
}

The implementation here is really simple, you will probably need to add some logic in the ProvideFault method to distinguish between technical exception and business exceptions and create the appropriate type of fault.

The next step is to associate this error handler with the service. Each ChannelDispatcher of a ServiceHost have a property named ErrorHandlers that contains a list of error handlers applied to the channel. One easy way to add a error handler to the channel dispatcher is to create a ServiceBehavior. To create a new service behavior, we simply need to implement IServiceBehavior in the TimeServiceBehavior class :
public class TimeServiceBehavior : IErrorHandler, IServiceBehavior
{
#region IErrorHandler Members

[...]

#endregion

#region IServiceBehavior Members

public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}

public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
// Adds a TimeServiceErrorHandler to each ChannelDispatcher
foreach (var channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
var channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(new TimeServiceBehavior());
}
}

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

#endregion
}

The only method of interest here is ApplyDispatchBehavior used to apply a specific behavior on the dispatchers (which is exactly what we are trying to achieve here).
Unfortunately, in its current state, our service behavior is not easy to use unless we are creating the service host and all its configuration by hand. For example, it is not possible to use our behavior from a configuration file.

Let's make this service behavior a little more developer friendly. We have two possible solutions :
  • Make it compatible with the file-based configuration infrastructure (app/web.config),
  • Make it an attribute that can decorate our service implementation.
We will implement the app.config version first. We simply need to create a class that inherit from BehaviorExtensionElement. This class will allow us to add our service behavior as an extension in the App/Web.config file. Here is the TimeServiceBehaviorElement class :
public class TimeServiceBehaviorElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(TimeServiceBehavior); }
}

protected override object CreateBehavior()
{
return new TimeServiceBehavior();
}
}

The code is pretty straight-forward, it just links our service behavior to a configuration element.

Here is an example of a configuration file making use of the TimeServiceBehavior :
<system.serviceModel>
<services>
[...]
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WcfErrorSample.Service1Behavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<timeService />
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="timeService" type="WcfErrorSample.TimeServiceBehaviorElement,
WcfErrorSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
</system.serviceModel>

That's it for the configuration model. Now let's make the behavior a little easier to use from a developer perspective. We are simply going to make the service behavior a custom attribute. This attribute will then be applied on the service itself. To transform the behavior into an attribute, TimeServiceBehavior will now inherit from Attribute, that's it :
public class TimeServiceBehavior : Attribute, IErrorHandler, IServiceBehavior
{
[...]
}

Now, the service implementation is decorated with our attribute :
[TimeServiceBehavior]
public class TimeService : ITimeService
{
[...]
}

This will have exactly the same effect than the configuration based implementation.

You can implement both solutions for your behaviors and have the choice of using one way or another depending on your policy ("all in config", "fixed service behavior", etc.).

Comments

  1. hmm.. simple and to the point.

    ReplyDelete
  2. Wow, Johann. I have been searching the entire day trying to find an easy to follow and GOOD example of how this works. I am a WCF noob and am at work trying to figure out the best way to handle exceptions and wow this example made my day.

    ReplyDelete
  3. Thanks a ton from Hyderabad, India!

    ReplyDelete

Post a Comment

Popular posts from this blog

Create a draft release and start it using the TFS REST API

Adding a delay before processing Textbox events

Change the deployment URL of a ClickOnce application