WCF HTTP – Cool processor for enabling navigation!

17.02.2011 23:26Comments
In my previous post I have explain the request and response processor pipelines. I have also showed how you can manage the serialization of request and responses. In this post I am going to show a totally different use for Processors, navigating resources. Say for instance I want to know in which country a certain contact lives. Ideally, I’d like to be able to do something like this: http://localhost:5555/Contacts/5/Address/City/State/Country. The problem in doing a thing like this in WCF HTTP so far, is that you would have to implement a method with a WebGet attribute, and a UriTemplate similar to this one:
[WebGet(UriTemplate="{id}/Address/City/State/Country")]
        public Country GetContactsCountry(int id)
        {
            //code here
        }
So you can see the problem right away, I need to define a method for each and every possible navigation sequence, and that’s not desired at all. But what if we could do something like this:
[WebGet(UriTemplate="{id}/*")]
        public Contact GetContactsNavigation(int id)
        {
            return this.repository.Get(id);
        }
Then we could try to come up with a mechanism to evaluate the rest of the Uri and navigate through the properties of the resource accordingly.

Cheating death…

The first big problem we are going to encounter is the fact that media type processors are expecting a value of the type defined in the Operation, so in our example they are expecting a “Contact” instance. Let’s see a bit of code from the XmlProcessor shipped with WCF HTTP:
public override void WriteToStream(object instance, System.IO.Stream stream, HttpRequestMessage request)
        { 
            //IQueryable support
            if (this.usesQueryComposition)
            {
                //wrap it in a list
                instance = Activator.CreateInstance(this.queryCompositionType, instance);
                var serializer = new DataContractSerializer(this.queryCompositionType);
                serializer.WriteObject(stream, instance);
            }
            else
            {
                var serializer = new XmlSerializer(Parameter.ParameterType);
                serializer.Serialize(stream, instance);
            }
        }
So when it’s not using query composition (our case) it’s using the Parameter.ParameterType (typeof(Contact) in our example). But if we navigate away from the contact instance, we’ll need a different type in the XmlSerializer. So just because the XmlProcessor is so simple, we could easily tweak the code a little bit to use “instance.GetType()” instead (and we can rename it to CustomXmlProcessor):
public class CustomXmlProcessor : MediaTypeProcessor
    {
        private bool usesQueryComposition;
        private Type queryCompositionType;

        public XmlProcessor(HttpOperationDescription operation, MediaTypeProcessorMode mode)
            : base(operation, mode)
        {
            var returnType = operation.ReturnValue;

            //IQueryable support
            if (operation.Behaviors.Contains(typeof(QueryCompositionAttribute)))
            {
                usesQueryComposition = true;
                var queryCompositionItemType = operation.ReturnValue.ParameterType.GetGenericArguments()[0];
                queryCompositionType = typeof(List<>).MakeGenericType(queryCompositionItemType);
            }
        }

        public override IEnumerable<string> SupportedMediaTypes
        {
            get
            {
                return new List<string> { "text/xml", "application/xml" };
            }
        }

        public override void WriteToStream(object instance, System.IO.Stream stream, HttpRequestMessage request)
        { 
            //IQueryable support
            if (this.usesQueryComposition)
            {
                //wrap it in a list
                instance = Activator.CreateInstance(this.queryCompositionType, instance);
                var serializer = new DataContractSerializer(this.queryCompositionType);
                serializer.WriteObject(stream, instance);
            }
            else
            {
                var serializer = new XmlSerializer(instance.GetType());
                serializer.Serialize(stream, instance);
            }
        }

        public override object ReadFromStream(System.IO.Stream stream, HttpRequestMessage request)
        {
            var serializer = new XmlSerializer(Parameter.ParameterType);
            return serializer.Deserialize(stream);
        }
    }
  The other big problem is that the IN argument of the MediaTypeProcessors is the Operation.ReturnValue.Name which is the name given to the return type of an operation. If we want the MediaTypeProcessors to serialize the output of our freaky processor, then we need to match the type and name of the out argument and in argument. The truth is that we don’t know the type of the out argument until we finish evaluating the navigation itself in execution time, so we are going to use something generic: typeof(object). And as for the name of the argument itself, let’s see a diagram of what we need: On the left side, is the current status of the pipeline, and in the right side is the desired status of the pipeline: image So we are going to “cheat” the MediaTypeProcessors into thinking that they will be processing the result of the Dispatching of the operation, but they will be processing the result of the navigation instead. We are going to cheat by receiving a list of processors in the constructor, and accessing the InArguments, and manipulating them:
public NavigateProcessor(HttpOperationDescription operation, IList<Processor> processors)
            {
                this.operation = operation;
                this.mediaTypeProcessors = processors.Where(p => p is MediaTypeProcessor).ToList();

                //cheat the in argument name of the media type processors.
                foreach (var p in processors.OfType<MediaTypeProcessor>())
                {
                    var argument = p.InArguments.Where(a => a.Name == operation.ReturnValue.Name).First();
                    argument.Name = NAVIGATE_RETURN_VALUE;
                    argument.ArgumentType = typeof(object);
                }

            }
On the execution of the processor we are going to use the UriTemplate of the operation to search for the wildcard segments, and navigate those segments. For the navigation, since we might be accessing to a database or even a WCF DataService, we are going to abstract the navigation into a PropertyNavigator:
public interface IPropertyNavigator
        {
            object Navigate(object instance, string propertyName);
        }
And obviously we are testing this with a reflection navigator:
public class ReflectionPropertyNavigator : IPropertyNavigator
            {
                public object Navigate(object instance, string propertyName)
                {
                    if (instance == null)
                        return null;

                    var prop = instance.GetType().GetProperties().Where(p => p.Name.ToLower() == propertyName.ToLower()).FirstOrDefault();
                    if (prop != null)
                    {
                        var newReturnValue = prop.GetValue(instance, null);
                        return newReturnValue;
                    }
                    else
                        throw new InvalidOperationException(string.Format("The property name {0} was not found in the type {1}", propertyName, instance.GetType().Name));
                }
            }

The final monster

Before I show you the entire code, notice that this processor does not support collections, so I did a simple check over the IEnumerable interface on the OnExecute. So back to business:
public class NavigateProcessor : Processor
        {
            public const string NAVIGATE_RETURN_VALUE = "_cheatReturnValue";
            private HttpOperationDescription operation;
            private IPropertyNavigator navigator;

            public NavigateProcessor(HttpOperationDescription operation, IList<Processor> mediaTypeProcessors):this(operation, mediaTypeProcessors, new ReflectionPropertyNavigator())
            {
            }

            public NavigateProcessor(HttpOperationDescription operation, IList<Processor> processors, IPropertyNavigator navigator)
            {
                this.operation = operation;

                //cheat the in argument name of the media type processors.
                foreach (var p in processors.OfType<MediaTypeProcessor>())
                {
                    var argument = p.InArguments.Where(a => a.Name == operation.ReturnValue.Name).First();
                    argument.Name = NAVIGATE_RETURN_VALUE;
                    argument.ArgumentType = typeof(object);
                }

                this.navigator = navigator;

            }

            protected override ProcessorResult OnExecute(object[] input)
            {

                if (typeof(IEnumerable).IsAssignableFrom(operation.ReturnValue.ParameterType))
                {
                    return new ProcessorResult() { Output = new[] { input[0] } };
                }

                object returnValue = input[0];
                HttpRequestMessage request = (HttpRequestMessage)input[1];

                var uriTemplate = operation.GetUriTemplate();
                var baseAddress = OperationContext.Current.Host.BaseAddresses.First();
                var navigations = uriTemplate.Match(baseAddress, request.RequestUri).WildcardPathSegments;

                object newReturnValue = returnValue;
                foreach (var segment in navigations)
                {
                    newReturnValue = navigator.Navigate(newReturnValue, segment);
                }
                
                return new ProcessorResult() { Output = new[] { newReturnValue } };
                
            }

            protected override IEnumerable<ProcessorArgument> OnGetInArguments()
            {
                yield return new ProcessorArgument(operation.ReturnValue.Name, operation.ReturnValue.ParameterType);
                yield return new ProcessorArgument(HttpPipelineFormatter.ArgumentHttpRequestMessage, typeof(HttpRequestMessage));
            }

            protected override IEnumerable<ProcessorArgument> OnGetOutArguments()
            {
                yield return new ProcessorArgument(NAVIGATE_RETURN_VALUE, typeof(object)); //operation.ReturnValue.ParameterType);
            }

            public class ReflectionPropertyNavigator : IPropertyNavigator
            {
                public object Navigate(object instance, string propertyName)
                {
                    if (instance == null)
                        return null;

                    var prop = instance.GetType().GetProperties().Where(p => p.Name.ToLower() == propertyName.ToLower()).FirstOrDefault();
                    if (prop != null)
                    {
                        var newReturnValue = prop.GetValue(instance, null);
                        return newReturnValue;
                    }
                    else
                        throw new InvalidOperationException(string.Format("The property name {0} was not found in the type {1}", propertyName, instance.GetType().Name));
                }
            }

        }
So if you browse a url like this in the Contacts sample we showed before: http://localhost:5555/contacts/2/Name/Length we are going to get this response:
<?xml version="1.0" ?> 
  <int>11</int>
It’s the length on the string property Name of the contact with id 5. So how cool is that???? NOTICE: This code is provided as is, and is not intended to demonstrate good practices, it's merely a demonstration of how flexible and fun working with WCF HTTP can be :)

comments powered by Disqus