Intercepting Service Operations

Recall from Chapter 1 that in the abstract, all WCF does when intercepting calls is perform pre- and post-call operations. Adding custom steps to this interception mechanism is probably the most common way of extending WCF.

Every endpoint dispatcher has a reference to an interface called IOperationInvoker, defined as:

public interface IOperationInvoker
{
   object[] AllocateInputs();
   object Invoke(object instance,object[] inputs,out object[] outputs);

   //Asynchronous invocation methods
}

The dispatcher uses the Invoke() method to invoke the calls on the service instance. In providing for the invocation, IOperationInvoker is the right place to plug in your code. Specifically, assigning the dispatcher your implementation of IOperationInvoker will enable you to hook it in.

The Generic Invoker

The first step in implementing my generic interceptor framework was to provide an abstract implementation of IOperationInvoker that enables custom pre- and post-call steps, as shown in Example E-1.

Example E-1. The GenericInvoker class

public abstract class GenericInvoker : IOperationInvoker
{
   readonly IOperationInvoker m_OldInvoker;

   public GenericInvoker(IOperationInvoker oldInvoker)
   {
      m_OldInvoker = oldInvoker;
   }
   public virtual object[] AllocateInputs()
   {
      return m_OldInvoker.AllocateInputs();
   }
   protected virtual void PreInvoke(object instance,object[] inputs)
   {}

   //Always called, even if operation had an exception
   protected virtual void PostInvoke(object instance,object returnedValue,
                                     object[] outputs,Exception exception)
   {}

   public object Invoke(object instance,object[] inputs,out object[] outputs)
   {
      PreInvoke(instance,inputs);
      object returnedValue = null;
      object[] outputParams = new object[]{};
      Exception exception = null;
      try
      {
         returnedValue = m_OldInvoker.Invoke(instance,inputs,out outputParams);
         outputs = outputParams;
         return returnedValue;
      }
      catch(Exception operationException)
      {
         exception = operationException;
         throw;
      }
      finally
      {
         PostInvoke(instance,returnedValue,outputParams,exception);
      }
   }
   // Additional asynchronous methods
}

GenericInvoker defines two virtual methods, PreInvoke() and PostInvoke(). PreInvoke() accepts the input parameters as well as the target instance, and PostInvoke() accepts the output parameters and the returned value as well as the instance and the exception (if one took place). GenericInvoker has an empty implementation for both methods. It is up to subclasses of GenericInvoker to override one or both of these methods and add the custom steps.

GenericInvoker accepts as a construction parameter the old implementation of IOperationInvoker that was associated with the service. This old implementation does the heavy lifting of allocating the input parameters for the operations, as well as actually invoking the service. GenericInvoker aims at being as nonintrusive as possible, so it cannot interfere with that implementation; at any rate, it would not be wise to do so, as that would entail a large amount of work. GenericInvoker therefore saves the old invoker in a member variable, and delegates to it its implementation of AllocateInputs(). The heart of GenericInvoker is the Invoke() method. In it, GenericInvoker first calls the PreInvoke() method (allowing its subclass to perform some pre-call processing) and then proceeds to invoke the operation using the old invoker. GenericInvoker encases the invocation in a try/catch/finally block. Regardless of how the operation ends (with or without an exception), GenericInvoker calls the PostInvoke() method, providing it with the retuned results and the exception, and allowing the subclass to perform custom post-call processing.

Installing the Interceptor

I wanted to provide for a declarative way of installing the operation, both at the operation level and at the service level. The trick in doing that is implementing the work once at the operation level, and then having the service level install all the operation-level attributes. The IOperationBehavior interface is the operation-level extension that lets you customize the dispatcher for an operation, in the ApplyDispatchBehavior() method:

public interface IOperationBehavior
{
   void ApplyDispatchBehavior(OperationDescription operationDescription,
                              DispatchOperation dispatchOperation);
   //More methods
}

Any method-level attribute that implements IOperationBehavior will be given a chance to affect the dispatcher (in this case, setting its operation invoker) in the ApplyDispatchBehavior() method. ApplyDispatchBehavior() provides the dispatchOperation parameter of the type DispatchOperation:

public sealed class DispatchOperation
{
   public IOperationInvoker Invoker
   {get;set;}

   //More members
}

Setting the Invoker property replaces the implementation of IOperationInvoker used. It’s as simple as that.

Example E-2 shows the implementation of my OperationInterceptorBehaviorAttribute.

Example E-2. The OperationInterceptorBehavior attribute

[AttributeUsage(AttributeTargets.Method)]
public abstract class OperationInterceptorBehaviorAttribute :
                                                      Attribute,IOperationBehavior
{
   protected abstract GenericInvoker CreateInvoker(IOperationInvoker oldInvoker);

   public void ApplyDispatchBehavior(OperationDescription operationDescription,
                                     DispatchOperation dispatchOperation)
   {
      IOperationInvoker oldInvoker = dispatchOperation.Invoker;
      dispatchOperation.Invoker = CreateInvoker(oldInvoker);
   }
   //More methods
}

The OperationInterceptorBehaviorAttribute is an abstract class with an abstract protected method called CreateInvoker(). CreateInvoker() takes the old invoker and returns some implementation of GenericInvoker. The implementation of ApplyDispatchBehavior() first saves the old invoker in a local variable and then calls CreateInvoker() to provide a new invoker while wrapping the old invoker. The newly created invoker is duly set on the dispatcher as the invoker to use from now on. Having a concrete subclass of the OperationInterceptorBehavior attribute will enable you to apply the custom invoker discretely on some, but perhaps not all, of the methods of the service. If you wish to apply the attribute on all operations, it is better to enforce this design decision at the service level using my ServiceInterceptorBehaviorAttribute, defined in Example E-3.

Example E-3. The ServiceInterceptorBehavior attribute

[AttributeUsage(AttributeTargets.Class)]
public abstract class ServiceInterceptorBehaviorAttribute :
                                                        Attribute,IServiceBehavior
{
   protected abstract OperationInterceptorBehaviorAttribute
                                                      CreateOperationInterceptor();

   public void ApplyDispatchBehavior(ServiceDescription serviceDescription,...)
   {
      foreach(ServiceEndpoint endpoint in serviceDescription.Endpoints)
      {
         foreach(OperationDescription operation in endpoint.Contract.Operations)
         {
            if(operation.Behaviors.
                             Find<OperationInterceptorBehaviorAttribute>() != null)
            {
               continue;
            }
            operation.Behaviors.Add(CreateOperationInterceptor());
         }
      }
   }
   //More members
}

ServiceInterceptorBehavior, too, is an abstract attribute. It provides the abstract protected method CreateOperationInterceptor(), which returns some implementation of the OperationInterceptorBehavior attribute. ServiceInterceptorBehavior supports the IServiceBehavior interface, whose ApplyDispatchBehavior() method provides the description of the service:

public interface IServiceBehavior
{
   void ApplyDispatchBehavior(ServiceDescription serviceDescription,...);
   //More methods
}

The ServiceDescription class contains a collection of service endpoints:

public class ServiceDescription
{
   public ServiceEndpointCollection Endpoints
   {get;}

   //More members
}

public class ServiceEndpointCollection : Collection<ServiceEndpoint>
{...}

Every endpoint has a Contract property containing the contract description:

public class ServiceEndpoint
{
   public ContractDescription Contract
   {get;}

   //More members
}

The contract description has a collection of operation descriptions:

public class ContractDescription
{
   public OperationDescriptionCollection Operations
   {get;}

   //More members
}

public class OperationDescriptionCollection : Collection<OperationDescription>
{...}

Each operation description has a collection of operation behaviors:

public class OperationDescription
{
   public KeyedByTypeCollection<IOperationBehavior> Behaviors
   {get;}

   //More members
}

The service-level attribute needs to add to this collection of behaviors an OperationInterceptorBehavior attribute.

In its implementation of ApplyDispatchBehavior(), ServiceInterceptorBehavior iterates over the collection of service endpoints. For each endpoint, it iterates over its operation collection. For each operation, it checks to see whether its behavior collection already contains an implementation of the OperationInterceptorBehavior attribute. This check is required in case the developer applied (by mistake) both an operation- and a service-level attribute. If the behavior collection does not contain the OperationInterceptorBehavior attribute, ApplyDispatchBehavior() adds it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.94.249