A good summary of all the extensibility points: https://blogs.msdn.microsoft.com/carlosfigueira/2011/03/14/wcf-extensibility/
Use IOperationInvoker
if you need to do "runtime-y" things, such as replacing the current SynchronizationContext (perhaps with one that restores OperationContext after an await
...).
But! You can only apply an IOperationInvoker
from an IOperationBehavior
, not from a IServiceBehavior
. If you try to assign an operation invoker from a service behavior, WCF will eventually just overwrite it. If you want to apply an IOperationInvoker
to every operation in a contract, you can write an IServiceBehavior
which applies the IOperationBehavior
to every operation.
You can have an attribute which can apply to a whole service, or to a single operation:
class MyInvoker : IOperationInvoker
{
readonly IOperationInvoker _originalInvoker;
public MyInvoker(IOperationInvoker originalInvoker)
{
// WCF gives us a perfectly-useful TaskMethodInvoker which knows how to do the hard work
_originalInvoker = originalInvoker;
}
public bool IsSynchronous { get { return _originalInvoker.IsSynchronous; } }
public object[] AllocateInputs() { return _originalInvoker.AllocateInputs(); }
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
// Stuff before the call
var returnValue = _originalInvoker.Invoke(instance, inputs, outputs);
// Stuff after the call
return returnValue;
}
public IAsyncResult InvokeBegin(...)
{
// Stuff here happens before the operation begins
return _originalInvoker.InvokeBegin(...);
}
public object InvokeEnd(..., IAsyncResult result)
{
// Do stuff here after the operation ends
return _originalInvoker.InvokeEnd(...);
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)]
class ApplyOperationInvokerAttribute : Attribute, IServiceBehavior, IOperationBehavior
{
// This runs when this is applied to a class or interface as an IServiceBehavior
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHost)
{
// Apply this behavior to every operation in the service
foreach (OperationDescription operation in endPoint.Contract.Operations)
{
if (!operation.OperationBehaviors.OfType<ApplyOperationInvokerAttribute>().Any())
{
operation.OperationBehaviors.Add(this);
}
}
}
// This runs when this is applied to a method as an IOperationBehavior
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new MyInvoker(dispatchInvoker.Invoker);
}
// ... then the other members of IServiceBehavior and IOperationBehavior which should just be empty methods
}
Use an IParameterInspector
. This is not necessarily called in the same
"context" as the operation, so you cannot do runtime-y things like you can with
IOperationInvoker
.
class MyParameterInspector : IParameterInspector
{
public void BeforeCall(string operationName, object[] inputs)
{
// do something
return myCorrelationState;
}
public object AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
{
// do something
}
}
class AddInspectorAttribute : Attribute, IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
{
foreach (DispatchOperation dispatchOperation in endpointDispatcher.DispatchRuntime.Operations)
{
dispatchOperation.ParameterInspectors.Add(new MyParameterInspector());
}
}
}
}
// the other methods don't matter for this purpose
}
You can instead use these (don't forget to replace the exising message with a buffered copy):
- For services:
IDispatchMessageInspector
- For clients:
IClientMessageInspector
class MyMessageInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
// do something
return myCorrelationState;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
// do something
}
}
class AddInspectorAttribute : Attribute, IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector());
}
}
}
// the other methods don't matter for this purpose
}
Provides a fault when an operation fails. Apply with an attribute:
class AddErrorHandlerAttribute : Attribute, IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler = new MyErrorHandler();
foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
{
channelDispatcher.ErrorHandlers.Add(errorHandler);
}
}
// the other methods don't matter for this purpose
}