Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using IOperationBehavior to supply a WCF parameter

Tags:

wcf

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).

like image 759
Chris Kemp Avatar asked Jun 15 '10 13:06

Chris Kemp


1 Answers

Well, I figured it out on my own. The MessagePartDescription has an Index property. I just need to resynch these values.

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
    {
        var parts = operationDescription.Messages[0].Body.Parts;
        parts.RemoveAt(0);
        for (int i = 0; i < parts.Count; i++)
            parts[i].Index = i;
    }
like image 116
Chris Kemp Avatar answered Oct 04 '22 00:10

Chris Kemp