Monday, January 26, 2015

WCF Extensibility terminology and few examples - centralized error handling in WCF



WCF gives us a way to intercept message in communication channel. This provides us the interface to invoke methods when the message gets released from proxy and get received by dispatcher at service end.

Terminologies:

ChannelDispatchers – ChannelDispatchers (ChannelDispatcher) accept incoming messages.EachChannelDispatcher is associated with exactly one ChannelListener.ChannelListener receives the message as mentioned and the message is processed by channels.After the message is processed by the associated channels then ChannelDispatcher passes it on to a suitable EndpointDispatcher

EndPointDispatchers – EndPointDispatchers are responsible for taking a message out of the channel stack and passing them to the right service operation.

One of the key element of the EndpointDispatcher is the DispatchRuntime.DispatchRuntime provides facility to alter default service behaviour using custom classes to provide enhanced functionality related to Service Instancing,Error Handling,Security etc.

 DispatchRuntime contains the DispatchOperation which is responsible for routing the message to the right operation and providing customizations facilities applicable at the operation level.

Extension points available in DispatchRuntime and DispatchOperation along with the interfaces required to be implemented in order to develop these extension components.

DispatchRuntime

1. DispatchRuntime exposes the following two properties which provide facility to customize how ChannelDispatcher accepts or closes a channel.

1. ChannelInitializers – This is a collection of System.ServiceModel.DispatchRuntime.IChannelInitializerinterface objects.This interface is for raising notifications when a channel is created.
2. InputSessionShutdownHandlers – This is a collection ofSystem.ServiceModel..DispatchRuntime.IInputSessionShutdown interface objects.This interface is used to define actions for shutdown of an input session.

The following properties are exposed for customizing message processing:

MessageInspectors – This is a collection ofSystem.ServiceModel.DispatchRuntime.IDispatchMessageInspector objects.IDispatchMessageInspector defines contract to manipulate the messagesafter a message is received but before dispatched to the operationafter a operation is executed but before reply is sent

OperationSelectors – This is a collection ofSystem.ServiceModel.DispatchRuntime.IDispatchOperationSelector objects.IDispatchOperationSelector defines contract to select an operation name based on the message content.

Operations – This is a collection of System.ServiceModel.DispatchRuntime.DispatchOperation.We will discuss about the DispatchOperation in the next section.

ErrorHandlers – This is a collection of System.ServiceModel.DispatchRuntime.IErrorHandlerobjects.IErrorHandler defines methods to control the behaviour when an application exception is thrown.

The creation and lifetime of the service instances can be controlled by

InstanceContextInitializers – This is a collection ofSystem.ServiceModel.DispatchRuntime.IInstanceContextInitializer objects.IInstanceContextInitialzer defines the contract to manipulate the InstanceContext objects.

InstanceProvider – This is an object of type System.ServiceModel.DispatchRuntime.IInstanceProvider and this interface defines methods to customize creation and release of service instances.

DispatchOperation

Formatter – This property of type System.ServiceModel.DispatchRuntime.IDispatchMessageFormatter provides explicit control over message formatting.

CallContextInitializers – This is a collection of System.ServiceModel.Dispatcher.ICallContextInitializerobjects.This interface defines methods to add additional state information that is required during execution of the call.

ParameterInspectors – This is a collection of System.ServiceModel.DispatchRuntime.IParameterInspectorobjects and this interface defines methods to inspect/modify the parameters before and after method call.

So to extend the WCF functionality we need develop a component implementing the required interfaces and then assign it with the appropriate property of DispatchRuntime/DispatchOperation.


Now the obvious question is how do we attach the these components to the runtime in declarative or config driven manner

Example 1


Implementing IInstanceProvider

The class System.Runtime.Dispatcher.DispatchRuntime exposes the property InstanceProvider of type IInstanceProvider.DispatchRuntime uses this instance to acquire and release instances of service objects.IInstanceProvider defines the following methods:

GetInstance – Returns an service object instance
ReleaseInstance – Releases a service object instance

I have developed a class ServiceObjectPool which implements this interface and internally talks to the object pool as shown below:

public class ServiceObjectPool:IInstanceProvider  
{
    private ResourcePool pool = null;

    public ServiceObjectPool(Type resourceType, int maxPoolSize, int initialSize)
    {
        pool = new ResourcePool(resourceType, maxPoolSize, initialSize); //Create the pool
    }
    public ServiceObjectPool(Type resourceType, int maxPoolSize, int initialSize, int incrementSize)
    {
       pool = new ResourcePool(resourceType,maxPoolSize,initialSize,incrementSize); //Create the pool
    }
    public object GetInstance(System.ServiceModel.InstanceContext instanceContext, System.ServiceModel.Channels.Message message)
    {
        return pool.Acquire(); //Acquire from the Pool
    }

    public object GetInstance(System.ServiceModel.InstanceContext instanceContext)
    {
        return pool.Acquire(); //Acquire from the pool
    }

    public void ReleaseInstance(System.ServiceModel.InstanceContext instanceContext, object instance)
    {
        pool.Release(instance); //Release the object back to pool
    }

    }

Attaching the InstanceProvider to DispatchRuntime

To attach the custom Instance Provider to DispatchRuntime I have developed a custom behavior class ServicePoolBehavior as shown below:

public class ServicePoolBehavior : IServiceBehavior
    {
        public int MinPoolSize { get; set; }
        public int MaxPoolSize { get; set; }
        public int IncrementSize { get; set; }

        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            return;
        }
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
        …….

        }

        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {

            return;

        }

    }

The main code goes into the ApplyDispatchBehavior method as shown below:

public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{

     //Create the ServiceObjectPool instance
     ServiceObjectPool pool = new ServiceObjectPool(serviceDescription.ServiceType,MaxPoolSize ,MinPoolSize ,IncrementSize);
     ServiceThrottle throt = null;

     //Change the ServiceThrottle Setting in line with the Object Pool Settings

     foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers)
     {
         if(chDisp.ServiceThrottle!=null)
         {
             if (throt == null)
             {
                 throt = chDisp.ServiceThrottle;

             }
             else
             {
                 chDisp.ServiceThrottle = throt;
             }

             if (throt.MaxConcurrentInstances > MaxPoolSize)
             {
                 throt.MaxConcurrentInstances = MaxPoolSize;
             }
         }
         foreach (EndpointDispatcher endDisp in chDisp.Endpoints)
         {

             //Attach the Pool to Dispatch Runtime
             endDisp.DispatchRuntime.InstanceProvider = pool;
         }
     }
}

Attaching Behavior To Host

This can be done through code,attribute or configuration.Here I chose the configuration route.To attach the service behavior through configuration we need implement a subclass of System.ServiceModel.Configuration.BehaviorExtensionElement as shown below:

public class ServicePoolBehaviorExtensionElement:BehaviorExtensionElement
{
    [ConfigurationPropertyAttribute(“minPoolSize”,DefaultValue=2)]
    [IntegerValidator(MinValue=1)]
    public int MinPoolSize { get; set; }
    [ConfigurationPropertyAttribute(“maxPoolSize”,DefaultValue=10)]
    [IntegerValidator(MinValue = 1)]
    public int MaxPoolSize { get; set; }
    [ConfigurationPropertyAttribute(“incrementSize”,DefaultValue=2)]
    [IntegerValidator(MinValue = 1)]
    public int IncrementSize { get; set; }

    public override Type BehaviorType
    {
        get { return  typeof(ServicePoolBehavior); }
    }

    protected override object CreateBehavior()
    {
        ServicePoolBehavior behavior = new ServicePoolBehavior();
        behavior.MaxPoolSize = Convert.ToInt32( this.ElementInformation.Properties[“minPoolSize”].Value);
        behavior.MinPoolSize = Convert.ToInt32( this.ElementInformation.Properties[“maxPoolSize”].Value);
        behavior.IncrementSize = Convert.ToInt32( this.ElementInformation.Properties[“incrementSize”].Value) ;
        return behavior;
    }
}

The following lines needs to be added in the configuration file of the host:

    <extensions>
        <behaviorExtensions>
            <add name=”poolBehavior” type=”SB.ServiceModel.Pool.ServicePoolBehaviorExtensionElement, SB.ServiceModel.Pool, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null” />
        </behaviorExtensions>
    </extensions>
    <behaviors>
        <serviceBehaviors>
            <behavior name=”testBehavior”>
                <serviceDebug includeExceptionDetailInFaults=”true” />
                <serviceMetadata httpGetEnabled=”true” httpGetUrl=”http://localhost:9001/Meta” />
                <poolBehavior minPoolSize=”5″ maxPoolSize=”10″ incrementSize=”2″/>
            </behavior>
        </serviceBehaviors>
    </behaviors>
     <services>
          <service behaviorConfiguration=”testBehavior” name=”SampleService.Test”>
               <endpoint address=”http://localhost:9001/Test” binding=”wsHttpBinding”
                    name=”testhttp” contract=”SampleService.ITest” />
          </service>
     </services>
</system.serviceModel>



Example 2: to create centralized error handler


·          Create a custom Service Behaviour Attribute to let WCF know that we want to use the GlobalErrorHandler class whenever an unhandled exception occurs. 


using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace CalculatorService
{
   
public class GlobalErrorHandlerBehaviourAttribute : Attribute, IServiceBehavior
    {
       
private readonly Type errorHandlerType;

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

       
public void Validate(ServiceDescription serviceDescription,
                             
ServiceHostBase serviceHostBase)
        {
        }

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

       
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
                                         
ServiceHostBase serviceHostBase)
        {
           
IErrorHandler handler = (IErrorHandler)Activator.CreateInstance(this.errorHandlerType);

           
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
               
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
               
if (channelDispatcher != null)
                    channelDispatcher.ErrorHandlers.Add(handler);
            }
        }
    }
}

[GlobalErrorHandlerBehaviour(typeof(GlobalErrorHandler))]
public class CalculatorService : ICalculatorService
{
//try
    //{
       
return Numerator / Denominator;
   
//}
    //catch (DivideByZeroException ex)
    //{
    //    DivideByZeroFault divideByZeroFault = new DivideByZeroFault();
    //    divideByZeroFault.Error = ex.Message;
    //    divideByZeroFault.Details = "Denominator cannot be ZERO";

    //    throw new FaultException<DivideByZeroFault>(divideByZeroFault);
    //}}


using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
namespace CalculatorService
{
   
public class GlobalErrorHandler : IErrorHandler
    {
       
public bool HandleError(Exception error)
        {
           
// log the actual exception for the IT Team to investigate
            // return true to indicate that the exception is handled
           
return true;
        }

       
public void ProvideFault(Exception error,
                                 System.ServiceModel.Channels.
MessageVersion version,
                                 ref System.ServiceModel.Channels.
Message fault)
        {
           
if (error is FaultException)
               
return;

            // Return a general service error message to the client
           
FaultException faultException = new FaultException("A general service error occured");
           
MessageFault messageFault = faultException.CreateMessageFault();
            fault =
Message.CreateMessage(version, messageFault, null
);
        }
    }
}



No comments:

Post a Comment