Skip to content

Instantly share code, notes, and snippets.

@jennings
Last active September 26, 2020 06:09
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jennings/112a1e27272777b3a743a225d35c2395 to your computer and use it in GitHub Desktop.
Save jennings/112a1e27272777b3a743a225d35c2395 to your computer and use it in GitHub Desktop.
I can never remember how to extend WCF in the ways that I want.

A good summary of all the extensibility points: https://blogs.msdn.microsoft.com/carlosfigueira/2011/03/14/wcf-extensibility/

Doing stuff before/after calls

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
}

If you need to inspect the operation and parameters passed to it

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
}

If you need to inspect the Message object (this is rare)

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
}

IErrorHandler

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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment