In WCF, methods communicate error information by means of SOAP faults. These SOAP faults are message types which are part of the metadata of your service contracts: service operations declare the possible faults by using FaultContracts.
[ServiceContract]
public interface IOrderMgrService
{
[OperationContract]
[FaultContract(typeof(Faulty))]
string GetData(int value);
}
[DataContract]
public class Faulty
{
[DataMember]
public string ErrorMessage { get; set; }
}
In .NET applications, errors are represented by Exception objects. So, to travel the exception from the server to the client, one needs to map Exception objects to SOAP faults.
The problem with this approach is of course : how do you map exceptions to faults. You are probably going to define multiple FaultContracts which you need to define on all the operation contracts. So first, every operation will contain a big try…catch statement to map existing .NET exceptions into faults. And secondly, an operation can only expose those faults that are applied in the contract.
public string GetData(int value)
{
try
{
int divider = 0;
int restult = value / divider;
}
catch (DivideByZeroException ex)
{
throw new FaultException<Faulty>(new Faulty() { ErrorMessage = ex.Message },
"GetData failed");
}
return string.Format("You entered: {0}", value);
}
A generic solution for this is implementing an ErrorHandler: create a class that implements IErrorHandler and implement both HandleError and ProvideFault.
The ProvideFault() method is called by any exception - a CLR exception or a fault - that occurs in your operation. The error property contains the exception that occurred, the fault property will contain a fault if the exception is already converted into a fault (so you can check wethether it is null or not).
HandleError() takes the exception : this is execute on a seperate thread. Definitely the right place to add logging or tracing.
public class MyErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
{
if (fault != null)
return;
if (error.GetType()== typeof(DivideByZeroException))
{
FaultException<Faulty> f = new FaultException<Faulty>(new Faulty() { ErrorMessage = error.Message },
"GetData failed");
fault = Message.CreateMessage(
version,
f.CreateMessageFault(),
f.Action);
}
}
...
An ErrorHandler is installed as a ServiceBehavior by registering it per ChannelDispatcher. The easiest way is to make your ErrorHander class an attribute and implement the IServiceBehavior interface on it.
public class MyErrorHandler : Attribute, IErrorHandler, IServiceBehavior
{
...
The ApplyDispatchBehavior method passes a reference to the service host, which you can use to iterate over the different ChannelDispatchers in order to add your ErrorHander to the ErrorHandlers collection.
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher disp in serviceHostBase.ChannelDispatchers)
{
disp.ErrorHandlers.Add(this);
}
}