Using IOperationBehavior to supply a WCF parameter

Posted by Chris Kemp on Stack Overflow See other posts from Stack Overflow or by Chris Kemp
Published on 2010-06-15T13:11:14Z Indexed on 2010/06/15 13:12 UTC
Read the original article Hit count: 280

Filed under:

This is my first step into the world of stackoverflow, so apologies if I cock anything up.

I'm trying to create a WCF Operation which has a parameter that is not exposed to the outside world, but is instead automatically passed into the function.

So the world sees this: int Add(int a, int b)

But it is implemented as: int Add(object context, int a, int b)

Then, the context gets supplied by the system at run-time. The example I'm working with is completely artificial, but mimics something that I'm looking into in a real-world scenario.

I'm able to get close, but not quite the whole way there.

First off, I created a simple method and wrote an application to confirm it works. It does. It returns a + b and writes the context as a string to my debug. Yay.

    [OperationContract]
    int Add(object context, int a, int b);

I then wrote the following code:

public class SupplyContextAttribute : Attribute, IOperationBehavior
{
    public void Validate(OperationDescription operationDescription)
    {
        if (!operationDescription.Messages.Any(m => m.Body.Parts.First().Name == "context"))
            throw new FaultException("Parameter 'context' is missing.");
    }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        dispatchOperation.Invoker = new SupplyContextInvoker(dispatchOperation.Invoker);
    }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
    {
    }

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
    {
        // Remove the 'context' parameter from the inbound message
        operationDescription.Messages[0].Body.Parts.RemoveAt(0);
    }
}

public class SupplyContextInvoker : IOperationInvoker
{
    readonly IOperationInvoker _invoker;

    public SupplyContextInvoker(IOperationInvoker invoker)
    {
        _invoker = invoker;
    }

    public object[] AllocateInputs()
    {
        return _invoker.AllocateInputs().Skip(1).ToArray();
    }

    private object[] IntroduceContext(object[] inputs)
    {
        return new[] { "MyContext" }.Concat(inputs).ToArray();
    }

    public object Invoke(object instance, object[] inputs, out object[] outputs)
    {
        return _invoker.Invoke(instance, IntroduceContext(inputs), out outputs);
    }

    public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
    {
        return _invoker.InvokeBegin(instance, IntroduceContext(inputs), callback, state);
    }

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
    {
        return _invoker.InvokeEnd(instance, out outputs, result);
    }

    public bool IsSynchronous
    {
        get { return _invoker.IsSynchronous; }
    }
}

And my WCF operation now looks like this:

    [OperationContract, SupplyContext]
    int Amend(object context, int a, int b);

My updated references no longer show the 'context' parameter, which is exactly what I want.

The trouble is that whenver I run the code, it gets past the AllocateInputs and then throws an Index was outside the bounds of the Array. error somewhere in the WCF guts.

I've tried other things, and I find that I can successfully change the type of the parameter and rename it and have my code work. But the moment I remove the parameter it falls over.

Can anyone give me some idea of how to get this to work (or if it can be done at all).

© Stack Overflow or respective owner

Related posts about wcf