One of the key things in any application is to have an exception handler that logs any unhandled exception so that the application can be debugged in the future.
In many applications I see this done by wrapping every external method in a try / catch block. While this works it has several drawbacks. First of all it is a pain to type the same code over and over again. It is easy to forget to add the try / catch / log block one one method. The biggest pain is if you need to change the way you log you have to change every single instance of that code.
In WCF there is an easy way to intercept all exceptions and log them via adding in by implementing a few simple interfaces that extend WCF.
The first one is the IErrorHandler interface:
public class ErrorHandler : IErrorHandler
{
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
}
public bool HandleError(Exception error)
{
if (!EventLog.SourceExists("Operations")) EventLog.CreateEventSource("Operations", "Application");
EventLog.WriteEntry("Operations", error.ToString());
return false;
}
}
The HandleError() method will be called whenever an exception occurs. Here we log to the event log and then return false so the error can continue to propagate up the chain. The ProvideFault() method can be used to transform exceptions into faults but for this example we are not going to do any rewriting of the fault message that is to be returned and will leave the method blank.
Next we have to write a service behaviour that will allow us to add our custom error handler for each channel we have a service running on. This is done by implementing the IServiceBehavior interface.
public class ErrorServiceBehavior : IServiceBehavior
{
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
ErrorHandler handler = new ErrorHandler();
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(handler);
}
}
}
Here we are enumerating all channels and adding the error handler to the collection. We do not need to change anything in the other methods of the interface.
Now we need to do is create a simple behaviour extension element so that we can use the error service behaviour in our config.
public class ErrorHandlerBehavior : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new ErrorServiceBehavior();
}
public override Type BehaviorType
{
get { return typeof(ErrorServiceBehavior); }
}
}
Lastly we can put the behaviour in the config file for our service.
<system.serviceModel>
<extensions>
<behaviorExtensions>
<!—Add in our custom error handler -->
<add name="ErrorLogging" type="Dispatch.Service.ErrorHandlerBehavior, Dispatch.Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c62bf877ee633f38" />
</behaviorExtensions>
</extensions>
<services>
<service name="Dispatch.Service.DispatchService" behaviorConfiguration="Dispatch.Service.Service1Behavior">
<endpoint address="" binding="wsHttpBinding" contract="Dispatch.Service.IDispatchService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Dispatch.Service.Service1Behavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<ErrorLogging /> <!—Name from behaviorExtensions Element -->
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
As you can see I added the extension to the Extensions element and then placed the name in the behaviour. My assembly is strong named but it should not be required to implement this behaviour (you should just be able to use PublicKeyToken=null if it is not strong named).