Using IOperationInvoker in WCF For Global Exception Handling and Logging

There are several ways to implement Global Exception Handling in WCF, for example using an IErrorHandler (the best way to handle the uncaught exceptions) or by creating your own Façade for calling all service methods. In this article we'll discuss an Interceptor IOperationInvoker that can be used before and after an 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 (especially Unhandled Exceptions).

First we'll start to look at a high level of the picture where the operation invoker actually stands when a client makes a service method call. The following diagram shows a picture of that.

IOperationInvoker1.jpg

So let's create a WCF service project. (Learn how to create and consume a WCF service.) After adding the project, we need to add some operation in the service so for for example our service operation is GetParseData(). So it can look something like this, as generated from the 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 use 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 exceptions. 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 attach this Invoker/Dispatcher to our service. For this we'll implement the IOperationBehavior first to attach the invoker with default operation behaviors.

/// <summary>
/// Behavior 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 the service class. To create an Attribute for the service behavior we need to inherit the Attribute class in our CustomAttribute implmenetation with IServiceBehavior.

Here is 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 does the same as we do to apply a 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, simply host the service in IIS Express and find the service reference via the 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 various values. For example call the method GetParseData() as in the following:

  1. "some values are valid values" (will not throw exception)
  2. "somevalue" (will throw exception)

    IOperationInvoker2.jpg

Let's have 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.

Up Next
    Ebook Download
    View all
    Learn
    View all