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);
//}}
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);
}
}
}
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