Search code examples
c#.netwcfwebhttpbindinguritemplate

How can I use strongly typed parameters in the uri path in WCF with WebHttpBinding


Okay, I've searched a bunch for answers to this question and I can only seem to find articles and documents for .NET 3.5 from 2011 and back... so I'm hoping to find more recent information for WCF in .NET 4.5 (and up).

I've got a WCF service that is bound to a webHttpBinding. It uses a Uri template that passes the identity of the item to be retrieved with a Uri template as such:

[WebInvoke(Method="GET", UriTemplate = "/{identity}")]
public ResponseItem Get(string identity)
{ 
    //Convert identity to a Guid which is the correct type for the identity
    //Retrieve object
}

What I'd really like to do is remove the conversion of the value to the Guid and move it up the stack to clean up the code and have:

[WebInvoke(Method="GET", UriTemplate = "/{identity}")]
public ResponseItem Get(Guid identity)
{ 
    //Retrieve object
}

I realize with other types of binding this is possible using a custom Behaviour and QueryStringConverter. I also realize that the reason for this being string by default in webHttpBinding is that inherently values passed in the address should semantically be strings - as an address is string based. So perhaps what I'm asking may not make sense.

In the context of my application, string is not semantically correct and it's irritating me that this class is cluttered with conversion code which shouldn't be a concern of the class, but changing the binding is not an option as existing clients are using it.

Is there an extensibility point in the WCF pipeline for the current versions of WCF (for instance IParameterInspector, IServiceBehavior) where conversion of this value is both possible and appropriate so that by the time the method is invoked, the parameter can be of the correct type?


Solution

  • Updated Answer -

    After looking at your comment I got the point. So you want to supply the string and then cast it before OperationInovker comes into picture. So I got my hands dirty playing around and finally did it.

    Here's What I have done. A new class that derives from WebHttpBehavior which would give a place where we can extend or override the existing behavior of WebHttpBidning.

    public class MyWebHttpBehavior : WebHttpBehavior
    {
    }
    

    Though it's a hack and but this would work. The problem with Typed argument was that the URL Template formatter was throwing exception by checking the method signature for type string so I got rid of that by over riding the BindingInformation by overiding method GetRequestDispatchFormatter.

        protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
        {
            foreach (var item in operationDescription.Messages[0].Body.Parts)
            {
                item.Type = typeof(string);
            }
    
            return base.GetRequestDispatchFormatter(operationDescription, endpoint);
        }
    

    After applying this behavior the run time will no more throw exception for String argument check. Now I need to change the OperationInvoker because if you run this then this will throw excpetion when you invoke the opration from client saying Invalid cast.

    Now here comes the IOperationInvoker in picture. I have simply taken the value from input[] of type object converted the value from String to Guid and passed it back to the Invoker.

    public class ValueCastInvoker : IOperationInvoker
    {
        readonly IOperationInvoker _invoker;
    
        public ValueCastInvoker(IOperationInvoker invoker)
        {
            _invoker = invoker;
        }
    
        public ValueCastInvoker(IOperationInvoker invoker, Type type, Object value)
        {
            _invoker = invoker;
        }
    
        public object[] AllocateInputs()
        {
            return _invoker.AllocateInputs().ToArray();
        }
    
        private object[] CastCorrections(object[] inputs)
        {
            Guid obj;
    
            var value = inputs[0] as string;
    
            if (Guid.TryParse(value, out obj))
            {
                return new[] { (object)obj }.Concat(inputs.Skip(1)).ToArray();
            }
    
            return inputs.ToArray();
        }
    
        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            return _invoker.Invoke(instance, CastCorrections(inputs), out outputs);
        }
    
        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return _invoker.InvokeBegin(instance, 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; }
        }
    }
    

    Now here what took me a long time to figure out is how to inject this custom operation inovker in pipeline. I found a relevants stackoverflow answer here. And implemented the way person suggested and it worked.

    Adding summary here: A new IOperationBehavior has to be implemented and attach it with DispatcherRuntime.

    public class MyOperationBehavior : IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {
        }
    
        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
        }
    
        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            dispatchOperation.Invoker = new ValueCastInvoker(dispatchOperation.Invoker);
        }
    
        public void Validate(OperationDescription operationDescription)
        {
        }
    }
    

    Now in MyWebHttpBehavior override the ApplyDispatchBehavior and introduce above implemented IOperationBehavior.

        public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            foreach (var operation in endpoint.Contract.Operations)
            {
                if (operation.Behaviors.Contains(typeof(MyOperationBehavior)))
                    continue;
    
                operation.Behaviors.Add(new MyOperationBehavior());
            }
    
            base.ApplyDispatchBehavior(endpoint, endpointDispatcher);
        }
    

    Now all these hacks and extensions would make this Legal.

        [WebInvoke(Method = "GET", UriTemplate = "/{id}")]
        string GetValue(Guid id);
    

    Disclaimer: I had fun experimenting this exntesion and applying custom behavior but I havn't checked the impacted areas. So use it own your own risk and feel free to change/enhance as you may. Sorry for Typos.

    Update 2

    I have created a library to wrap this web http behavior extension. The library provides more support of other value types in method parameters(multiple). Check this out.