My previous post IErrorHandler: A generic Fault Converter (1/2) describes how to build an ErrorHandler for WCF. The challenge is now to write a generic ErrorHander which can be configured to automatically convert exceptions into faults based upon a pre-defined list of converter methods. Converting an exception into a fault is one thing, but we need to be careful that we only return those faults that the operation contract defines (by means of FaultContract attributes)
Lets first focus on the implementation of the converter : we need a delegate definition for a method that takes any Exception as input and that converts this into a Fault. Faults are just classes that have a DataContract attribute (or even without when trusting on POCO support introduced in .NET 3.5 SP1). Since there isn’t any base class for FaultContracts, let’s define the type as generic.
public delegate TFault FaultConverter<TFault>(Exception e);
The converter maintains a Dictionary of exception types as keys and FaultConverter<TFault> delegates as values. We need to be able to add new converter methods and execute the converter method that suits the exception type.
public class ExceptionToFaultConverter
{
private Dictionary<Type, Delegate> lst = new Dictionary<Type, Delegate>();
public void AddConverter<TException, TFault>(FaultConverter<TFault> converter)
{
lst.Add(typeof(TException), converter);
}
public object Convert(Exception ex)
{
if (lst.ContainsKey(ex.GetType()))
{
return lst[ex.GetType()].DynamicInvoke(ex);
}
return null;
}
}
Adding a converter just looks like this :
faultConverter.AddConverter<DivideByZeroException, Faulty>(e => new Faulty() { ErrorMessage = e.Message });
During ProvideFault(), theFautlConverter will convert the Exception info a fault (if any). Next thing to to is checking if we can return this fault for the current operation.
public class MyErrorHandler : Attribute, IErrorHandler, IServiceBehavior
{
ExceptionToFaultConverter faultConverter =new ExceptionToFaultConverter();
public MyErrorHandler()
{
faultConverter.AddConverter<DivideByZeroException, Faulty>(
e => new Faulty() { ErrorMessage = e.Message }
);
}
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error,
System.ServiceModel.Channels.MessageVersion version,
ref System.ServiceModel.Channels.Message fault)
{
ServiceDescription description = OperationContext.Current.Host.Description;
ServiceEndpoint endpoint = description.Endpoints.Find(OperationContext.Current.IncomingMessageHeaders.To);
string operationName =OperationContext.Current.IncomingMessageHeaders.Action.Replace(
endpoint.Contract.Namespace + endpoint.Contract.Name + "/", "");
OperationDescription operation =endpoint.Contract.Operations.Find(operationName);
object faulttype = faultConverter.Convert(error);
if (faulttype == null)
return;
foreach (FaultDescription faultdescription in operation.Faults)
{
if (faultdescription.DetailType.IsAssignableFrom(faulttype.GetType()))
{
fault = Message.CreateMessage(
version,
FaultCode.CreateSenderFaultCode(
faultdescription.Name,
faultdescription.Namespace),
faulttype.ToString(),
faulttype,
faultdescription.Action);
break;
}
}
}
public void AddBindingParameters(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase,
System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher disp in serviceHostBase.ChannelDispatchers)
{
disp.ErrorHandlers.Add(this);
}
}
public void Validate(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
}