다음을 통해 공유


WCF Extension Points and how to use them?

In one of the SOA based system, I had a requirement of intercepting the service method calls to automatically log the method's entry and exit calls to implement automatic logging and exception handling without putting any logging statements or  try catch block in each service method implementation. The implementation of these features was giving us more cleaner implementation of business functionality in service methods by abstracting away the implementation of crosscutting concerns, In this case, exception handling or logging.

 

To implement the above feature I was looking for the way to get execution control even before it goes to the service methods. Good news is that WCF itself helped me and gave the control with the help of it's extension points. Lets see what all extension points I used.

 

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 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 end. At service end this is received by Dispatcher. Dispatcher takes the message and desterilizes the content into appropriate CLR type, finds out which has to be called on behalf of the client and calls that message.

 

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 framework (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.

 

  1. IParameterInspector : Called before and after invocation to inspect and modify parameter values.
  1. IDispatchMessageFormatter/IClientFormatter : Called to perform serialization and deserialization.
  1. IDispatchMessageInspector/IClientMessageInspector : Called before send or after receive to inspect and replace message contents.
  1. IDispatchOperationSelector/IClientOperationSelector : Called to select the operation to invoke for the given message.
  1. IOperationInvoker : Called to invoke the operation.

Registering your inspector class with WCF runtime

You use WCF behaviours to register your inspector classes with WCF runtime. When theServiceHost initializes the service, it looks into app.config/web.configfiles  for the behaviours to apply and also reflects over all the attributes applied on service contract methods to check if any attribute has implemented 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 behaviours provided supported in WCF

 

  1. IServiceBehavior: Allows you to register your inspectors at Service level.
  1. IEndpointBehavior: Allows you to register your inspectors at Endpoint level.
  1. IContractBehavior: Allows you to register your inspectors at Service Contract level.
  1. IOperarionBehavior: Allows you to register your inspectors at each Operation level.

All these behaviour 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 behaviour interface.

 

  1. Validate: Called just before the runtime is built—allows you to perform custom validation on the service description.
  1. 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.
  1. ApplyClientBehavior: Allows behavior to inject proxy (client) inspectors. this method is not present on IServiceBehavior.
  1. 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 the example code. To make the sample as simple as possible I have create all these classes and interfaces in single WCF project without worrying about the modularization.

 

clip_image001

Code forLoggingAttribute.

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;

namespaceSampleService

{

public class LoggingAttribute : Attribute, IOperationBehavior

{

        public voidAddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

        {

            //for this example no implementation is required in this method.

        }

        public voidApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation)

        {

            //for this example no implementation is required in this method.

        }

 

        public voidApplyDispatchBehavior(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 forLoggingInspector:

Note: 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;

namespaceSampleService

{

    public class LoggingInspector :IParameterInspector

    {

        public void AfterCall(stringoperationName, 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(stringoperationName, 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 forISampleService 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;

namespaceSampleService

{

    [ServiceContract]

    public interface ISampleService

    {

        [OperationContract]

        [Logging]

        int IncrementNumber(int number);

    }

}

 

Code forSampleService implementation:

Note: 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;

 

namespaceSampleService

{

    public class SampleService : ISampleService

    {

        public int IncrementNumber(int number)

        {

            return number++;

        }

    }

}