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 parameters as well as the target instance, and Post
Invoke()
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 IOperation
Invoker
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 Allocate
Inputs()
. The heart of GenericInvoker
is the Invoke()
method. In it, Generic
Invoker
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 Operation
Interceptor
Behavior
Attribute
.
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
Apply
Dispatch
Behavior()
first saves the old invoker in a
local variable and then calls Create
Invoker()
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 ServiceInterceptor
Behavior
Attribute
, 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 Operation
Interceptor
Behavior
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 Operation
Interceptor
Behavior
attribute, ApplyDispatchBehavior()
adds it.
18.191.94.249