Friday, February 26, 2010

WCF Exception Handling

You can create a high level exception interceptor of WCF exceptions. This should be coded to convert any exception, including unhandled exceptions into FaultExceptions since this is the only type of exception that should be legally passed across the wire to the client.
NOTE: This may not work properly if the exception is happening in the constructor itself.

The code for the IErrorHandler is below.
Note: The FaultAction value is very important. Read comments in the code.

/// <summary>
/// This intercepts exceptions before throwing them to the client from the service
/// </summary>
public class CalculatorErrorHandler : IErrorHandler
{
// Provide a fault. The Message fault parameter can be replaced, or set to
// null to suppress reporting a fault.
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
   Console.WriteLine("Error=" + error.Message);
   var fe = new FaultException<CalculatorFault>(new CalculatorFault { Message = error.Message }, new FaultReason(error.Message));
   //The FaultAction value should match exactly with the Action value set in the attribute of the OperationContract that throws this Fault
   fault = Message.CreateMessage(version, fe.CreateMessageFault(), CalculatorFault.FaultAction);
   //Console.WriteLine("provideFault=" + error.StackTrace);
}

// HandleError. Log an error, then allow the error to be handled as usual.
// Return true if the error is considered as already handled
public bool HandleError(Exception error)
{
if (error != null)
{
//Use log4net here if you want
Console.WriteLine("In ErrorHandler: " + error.GetType().Name + " - " + error.Message);
}
return false;
}
}

Here is the custom Fault datacontract:

namespace MyDataContracts
{
public class CalculatorFault
{
public const string FaultAction = "FaultAction";
public string Message { get; set; }
}
}


Then wire it up using a service behavior as an attribute:




// This attribute can be used to install a custom error handler for a service.
public class ErrorBehaviorAttribute : Attribute, IServiceBehavior
{
Type errorHandlerType;

public ErrorBehaviorAttribute(Type errorHandlerType)
{
this.errorHandlerType = errorHandlerType;
}

void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
{
}

void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
{
}

void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;

try
{
errorHandler = (IErrorHandler) Activator.CreateInstance(errorHandlerType);
}
catch (MissingMethodException e)
{
throw new ArgumentException("The errorHandlerType specified in the ErrorBehaviorAttribute constructor must have a public empty constructor.", e);
}
catch (InvalidCastException e)
{
throw new ArgumentException("The errorHandlerType specified in the ErrorBehaviorAttribute constructor must implement System.ServiceModel.Dispatcher.IErrorHandler.", e);
}

foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(errorHandler);
}
}
}



Now you can add the service behavior as an attribute. Note: The IServiceBehavior attribute has to go in the service implementation and NOT in the interface. Otherwise, the attribute is not properly invoked.


[ErrorBehavior(typeof(CalculatorErrorHandler))]
public class CFService1: ICalculator, IForecaster
{
#region ICalculator Members
//...
#endregion
}


The interface will have the fault contract decorated on each method of interest:

namespace MyServiceContracts
{
public interface ICalculator
{

[OperationContract]
[FaultContract(typeof(CalculatorFault), Action = CalculatorFault.FaultAction)]
int SquareIt(int input);

}
}

And finally, in the client, you catch your FaultException of expected type:

try
{
wcfClient.DoubleIt(32);
}
catch (FaultException<CalculatorFault> fecf)
{
Console.WriteLine("Client: Got expected Fault Exception of type CalculatorFault: " + fecf.Message);
}


test:
try
{
wcfClient.DoubleIt(32);
}

test2:
for (int i = 0; i < 4; i++)
{
    //do something
}


test3:
for in()
    sfadaf
endfor

No comments:

Post a Comment