Monday, 24 June 2013

Using IOperationInvoker in WCF for Global exception handling and logging

There are several ways to implement the Global Exception handling in WCF for e.g. using a  IErrorHandler (The best way to handle the uncaught exceptions) or creating your own Fa├žade for calling all service methods. In this article we’ll discuss about an Interceptor IOperationInvoker that can be used before and after activity when a service actually calls it’s operations. There are several things you can perform at this stage like Logging, Tracing or Handling the Exception(specially Unhandled Exceptions).

First we’ll start to look at high level of the picture where the Operation invoker actually stands when a client make a service method call. Below diagram is showing a picture of the same.

IOperationInvoker WCF cshandler.com

So lets create a WCF service project. (Learn how to create and consume WCF service). After adding the project, we need to add some operation in the service so for e.g. our service operation is GetParseData(). So it can look something like this(as generated from Project template).

IServiceContract:

[OperationContract]
string GetParseData(string value)


Service.cs


public string GetParseData(string value)
{
return string.Format("You entered: {0}", value);
}

To get some exceptions we’ll a custom logic in the Implementation of GetParseData(). So it can look something like this.

public string GetParseData(string value)
{
// check if value is alphabets/Numeric
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("Method parameter value can not be null or empty");
}

// split the value if it contains spaces and return first two words
var stringArray = value.Split(' ');

// This will/may cause an Unhandled exception for OutOfIndex in case of single word is passed.
return string.Format("{0}_{1}_{2} ..", stringArray[0], stringArray[1], stringArray[2]);
}


Here in this method we have introduced some known and unhandled exception. To demonstrate the logging and Global exception handling we’ll now create a class that will Implement the IOperationInvoker interface.


/// <summary>
/// Global error handler of service operations using IOperationInvoker
/// </summary>
public class ErrorHandlingLoggingOperationInvoker : IOperationInvoker
{
private readonly ILog logger;

private IOperationInvoker invoker;

private string operationName;



public ErrorHandlingLoggingOperationInvoker(IOperationInvoker baseInvoker, DispatchOperation operation)
{
this.invoker = baseInvoker;
this.operationName = operation.Name;
XmlConfigurator.Configure();
logger = LogManager.GetLogger("serviceLogger");
}



public bool IsSynchronous
{
get
{
return true;
}
}



public object[] AllocateInputs()
{
return this.invoker.AllocateInputs();
}



//This is our intent method to intercept the request.
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
logger.InfoFormat("Start command operation:{0}", this.operationName);

object result = null;

try
{
result = this.invoker.Invoke(instance, inputs, out outputs);
}
catch (Exception exception)
{
logger.Error("Unhandled Exception: ", exception);
outputs = new object[] { };

throw new FaultException(new FaultReason(exception.Message));
}

logger.InfoFormat("End command operation:{0}", this.operationName);

return result;
}



public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
throw new Exception("The operation invoker is not asynchronous.");
}



public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
throw new Exception("The operation invoker is not asynchronous.");
}
}



In this implementation of IOperationInvoker we will only focus on the Invoke() method to log the request begin and request end also a Try Catch block is placed on the invoke statement so that we can capture any unhandled exception and log it in the first place with all details. And we’re not sending the complete exception to the client but a custom FaultException with less details or a message.



Now the invoker is ready but how to attached this Invoker/Dispatcher to our service. For this we’ll implement the IOperationBehavior first to attach the invoker with default operation behaviors.


/// <summary>
/// Bhavior defined for global exception handler
/// </summary>
public class GlobalExceptionHandlerBehavior : IOperationBehavior
{
public void Validate(OperationDescription operationDescription)
{
return;
}



public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{// Here we have added our invoker to the dispatch operation dispatchOperation.Invoker = new ErrorHandlingLoggingOperationInvoker(dispatchOperation.Invoker, dispatchOperation);
}



public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
return;
}



public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
return;
}
}



Now we have our invoker as Operation behavior.  Now we need to implement the IServiceBehavior and we’re done. Then you can directly use this behavior via the binding configuration directly using extensions. See here How to add the extended behavior in the service from configuration file.


But here we’ll create our Custom Attribute class and will use it in the code directly as an Attribute on service class. To create an Attribute for service behavior we need to inherit the Attribute class in our CustomAttribute implmenetation with IServiceBehavior .


Here the code:


/// <summary>
/// Attribute class for applying custom service behavior on Service
/// </summary>
public class ExceptionHandlerLoggerBehaviorAttribute : Attribute, IServiceBehavior
{
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
return;
}

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

public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ServiceEndpoint endpoint in serviceDescription.Endpoints)
{
foreach (OperationDescription operation in endpoint.Contract.Operations)
{
IOperationBehavior behavior = new GlobalExceptionHandlerBehavior();
operation.Behaviors.Add(behavior);
}
}
}
}


Here we are actually applying the behavior to the end points. which in turn doing the same as we do apply behavior via the configuration file with each EndPoint. So our Attribute class is ready, we're going to tell the Service calls that you have a custom behavior to handle the global exception.


[ExceptionHandlerLoggerBehavior]
public class Service1 : IService1
{
public string GetParseData(string value)
{
// check if value is alphabets/Numeric
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("Method parameter value can not be null or empty");
}

// split the value if it contains spaces and return first two words
var stringArray = value.Split(' ');

// This will/may cause an Unhandled exception for OutOfIndex in case of single word is passed.
return string.Format("{0}_{1}_{2} ..", stringArray[0], stringArray[1], stringArray[2]);
}
}


Now we’ll create a client and call the service method. To create a client simple host the service in IIS express and find the service reference via service reference dialog box. We’ll simply use the Console application as a client.


public static void Main(string[] args)
{
ServiceReference1.Service1Client client = new Service1Client();
try
{
string value = client.GetParseData("somevalue");
Console.WriteLine(value);
}
catch (FaultException ex)
{
Console.WriteLine("========= Error ==========");
Console.WriteLine("{0}", ex.Message);
}

Console.Read();
}


so run the client with different values. For e.g. call the method GetParseData()


i) “some values are valid values” (will not throw exception)



ii) “somevalue” (will throw exception)


image


Let’s take a look at the log file. How the logs are getting produced from the our custom Interceptor.


2013-06-24 14:09:35,339 [6] INFO  serviceLogger - Start command operation:GetParseData
2013-06-24 14:09:35,353 [6] INFO serviceLogger - End command operation:GetParseData
2013-06-24 14:09:57,714 [6] INFO serviceLogger - Start command operation:GetParseData
2013-06-24 14:09:57,716 [6] ERROR serviceLogger - Unhandled Exception:
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at CShandler.SampleWCFServiceApplication.Service1.GetParseData(String value) in c:\Users\achoudhary\Documents\Visual Studio 2012\Projects\WpfApplication1\CShandler.SampleWCFServiceApplication\Service1.svc.cs:line 26
at SyncInvokeGetParseData(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at CShandler.SampleWCFServiceApplication.ErrorHandlingLoggingOperationInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs) in c:\Users\achoudhary\Documents\Visual Studio 2012\Projects\WpfApplication1\CShandler.SampleWCFServiceApplication\ErrorHandlingLoggingOperationInvoker.cs:line 85


Hope you enjoyed the article. Please leave a comment or suggestion.

No comments:

Post a Comment