In one of my SOA based software system, I had a requirement of intercepting the service method calls to automatically logging the method calls on entry and exit of the service method call without putting any logging statements inside the service method implementation and to have some default exception handling logic again without putting any try catch blocks inside service method implementation. The implementation of these features was giving us more cleaner implementation of business functionality inside service methods by abstracting away the implementation of crosscutting concerns like exception handling or logging in my case.
To implement the above feature I was looking for the way to get execution control even before it goes to my service methods. Good news is that WCF itself helped me and gave the control with the help of it's extension points. Lets see how?
Proxy and Dispatcher
WCF works on messages which are send between client and service. Every time client wants to call a service method, it sends a message to service with help of Proxy running in the client process. Job of the Proxy is to take the client request and convert it into a message which is than further serialized and transferred to the service. At service end this message is received by Dispatcher. Dispatcher takes the message and desterilizes the content into appropriate CLR type, finds out which method has to be called on behalf of the client and calls that method.
Both Proxy and Dispatcher provides many extension points using different interfaces. When you implement these interfaces in your own class and register those class with WCF runtime (we will see how?), Both Proxy and Dispatcher calls the implementation in your class at the time of message processing.
Lets see what all interfaces are provided via WCF framework to intercept the message processing.
- IParameterInspector : Called before and after invocation to inspect and modify parameter values.
- IDispatchMessageFormatter/IClientFormatter : Called to perform serialization and deserialization.
- IDispatchMessageInspector/IClientMessageInspector : Called before send or after receive to inspect and replace message contents.
- IDispatchOperationSelector/IClientOperationSelector : Called to select the operation to invoke for the given message.
- IOperationInvoker : Called to invoke the operation.
Registering your inspector class with WCF runtime
You use WCF behaviors to register your inspector classes with WCF runtime. When the ServiceHost initializes the service, it looks into app.config/web.config file for the behaviour to apply and also reflects over all the attributes applied on service contract methods to check if any attribute has been applied which is implementing any behaviour interface. If it finds one, it creates an instance of that attribute type and calls appropriate methods of behaviour interface, implemented by attribute class, to you give you a change to register your interceptors during service initialization. There are following types of behaviors provided via WCF
- IServiceBehavior: Allows you to register your inspectors at Service level.
- IEndpointBehavior: Allows you to register your inspectors at Endpoint level.
- IContractBehavior: Allows you to register your inspectors at Service Contract level.
- IOperarionBehavior: Allows you to register your inspectors at each Operation level.
All these behavior interfaces are having same methods but the method signatures are different so that your only get those runtime objects which comes under the scope you want to extend. Following are the methods available in each behavior interface.
- Validate: Called just before the runtime is built—allows you to perform custom validation on the service description.
- AddBindingParameters: Called in the first step of building the runtime, before the underlying channel is constructed—allows you to add parameters to influence the underlying channel stack.
- ApplyClientBehavior: Allows behavior to inject proxy (client) inspectors. this method is not present on IServiceBehavior.
- ApplyDispatchBehavior:Allows behavior to inject dispatcher inspectors.
Now you know what all classes and interfaces are available from WCF to extend its runtime, lets extend the runtime to intercept the service method calls and log the entry and exit of method call automatically (without writing logging code in service method implementation).
Following are the classes involved in my solution. To make the sample as simple as possible I have create all these classes and interfaces in single WCF project.
Code for LoggingAttribute.
Note how this class is inherited from Attribute class and implementing IOperationBehavior. Methods of IOperationBehavior will be called by ServiceHost at the time of service initialization process to give you a chance to register your inspectors. In this example I am registring LoggingInspector instance.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Description;
namespace SampleService
{
public class LoggingAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
//for this example no implementation is required in this method.
}
public void ApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation)
{
//for this example no implementation is required in this method.
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)
{
//Register your inspector class
dispatchOperation.ParameterInspectors.Add(new LoggingInspector());
}
public void Validate(OperationDescription operationDescription)
{
//for this example no implementation is required in this method.
}
}
}
Code for LoggingInspector.
See how LoggingInspector is implementing IParameterInspector interface and providing the logging functionality.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Dispatcher;
namespace SampleService
{
public class LoggingInspector : IParameterInspector
{
public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
{
//Your can put any logging code here. here is the dummy one.
Console.WriteLine(string.Format("Method exit : {0}",operationName));
}
public object BeforeCall(string operationName, object[] inputs)
{
//Your can put any logging code here. here is the dummy one.
Console.WriteLine(string.Format("Method entry : {0}", operationName));
return null;
}
}
}
Code for ISampleService service contract interface.
Here I am applying my own custom attribute to enable automatic logging on IncrementNumber method.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace SampleService
{
[ServiceContract]
public interface ISampleService
{
[OperationContract]
[Logging]
int IncrementNumber(int number);
}
}
Code for SampleService implementation.
Note that there is no logging related implementation inside IncrementNumber method implementation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace SampleService
{
public class SampleService : ISampleService
{
public int IncrementNumber(int number)
{
return number++;
}
}
}