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 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 parmeters
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.
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 discreetly 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.
3.139.97.40