UriTemplate parameter types

Topics: Web Api
Feb 1, 2011 at 7:36 PM

Hi,

Based on the following UriTemplate: "{id}", I can't define my operation's parameter as "int", it only works with "string". I had to go all the way down to the UriTemplateProcessor, add a reference to the HttpOperationDescription, and take the type of the parameter from there to perform the convertion. So am I missing something and can this be done in some other less "hack-y" and easier way? If there isn't, are you planning on adding this feature?

Here's the code:

        [WebGet(UriTemplate = "{id}")]
        public Contact Get(int id, HttpResponseMessage response)

Thanks!

Gustavo

Coordinator
Feb 1, 2011 at 7:38 PM

Hi Gustavo

Yes, this is only a point in time thing related to the Codeplex version. We have already committed support for native types :-)

Glenn

Feb 1, 2011 at 7:45 PM

Thanks for the quick response Glenn!

One last thing, do you think at this point, that it's correct the approach to add this logic to UriTemplateHttpProcessor? The problem I found with creating a Processor that takes an argument and converts it to a different type, is a validation in the Pipeline that looks for arguments that are not bound, and seems like it's taking the type into account. For example, I can't have these two operations in my service because of the different types in the "id" argument:

[WebGet(UriTemplate = "{id}")]
        public Contact Get(int id, HttpResponseMessage response)
        
        [WebInvoke(UriTemplate = "{id}", Method = "PUT")]
        public Contact Put(string id, Contact contact, HttpResponseMessage response)
        
So, am I in the right track?
Thanks,
Gustavo
Coordinator
Feb 1, 2011 at 7:57 PM

Yes you are right the pipeline validates the processor arguments. However, you can create arguments dynamically and specify the types. If you look at the UriTemplateHttpProcessor it does this as it populates the output argument collections, in this case all the args are created as strings. What you would do is look at the associated arguments on the operation description and make sure the argument types that you set match the types of the corresponding parameters. Take a look for example at the XmlProcessor and see how it matches the body against the body param.

 

Feb 1, 2011 at 8:07 PM

Thanks Glenn, I saw the samples you were referring, and that is how I did it in the first place from inside the UriTemplateHttpProcessor. However I was wondering if I could add a new processor to the pipeline so that I don't mess up with the UriTemplateHttpProcessor. This new processor would "fix" the argument types, and that's were I think the problem is, I think that I can't have two arguments with the same name and different types. I'll give it another try just in case.

Thanks for the help Glenn!

Gustavo

Coordinator
Feb 1, 2011 at 8:56 PM

Yes you can add a strongly typed processor that picks up the output of the UriTemplateProcessor. We match on name AND type, so it should be fine.

 

Feb 1, 2011 at 9:42 PM

Hey Glenn, it turned out that adding my custom processor worked pretty well. I thought I'd share the code since it's pretty simple:

        public class NativeTypesSupportProcessor : Processor
        {
            private HttpOperationDescription operation;
            private ProcessorArgument[] inArguments;
            private ProcessorArgument[] outArguments;

            public NativeTypesSupportProcessor(HttpOperationDescription operation)
            {
                this.operation = operation;

                outArguments = operation.GetUriTemplate().PathSegmentVariableNames
                    .Join(operation.InputParameters,
                        o => o.ToUpper(),
                        i => i.Name.ToUpper(),
                        (i, o) => new ProcessorArgument(i, o.ParameterType)).ToArray();

                inArguments = outArguments
                    .Select(x => new ProcessorArgument(x.Name, typeof(string)))
                    .ToArray();
            }

            protected override IEnumerable<ProcessorArgument> OnGetInArguments()
            {
                return inArguments;
            }

            protected override IEnumerable<ProcessorArgument> OnGetOutArguments()
            {
                return outArguments;
            }

            protected override ProcessorResult OnExecute(object[] input)
            {
                if (inArguments.Count() != input.Length)
                    throw new ArgumentException("Input does not contain the right amount of items.");

                var output = new object[input.Length];
                for (int i = 0; i < input.Length; i++)
                {
                     output[i] = Convert.ChangeType(input[i], outArguments[i].ArgumentType);
                }
                
                return new ProcessorResult() { Output = output };
            }
        }

Cheers!
Gustavo Machado
Coordinator
Feb 1, 2011 at 9:48 PM

Cool. It does beg the question though if you went that far, why not just replace it fully, I mean most of the logic is there :-)

Glenn

Feb 1, 2011 at 10:03 PM

I see your point, I was just looking for a workaround, but I might as well remove the UriTemplateProcessor from the pipeline and replace it with my custom one. Would you know how to get a hold of the baseUri from the HttpHostConfiguration so that I can plug that in at the IProcessorProvider implementation?

Thanks,

Gustavo

Coordinator
Feb 1, 2011 at 10:18 PM

Why do you need the base uri?

Feb 1, 2011 at 10:47 PM

This is how the Uri template is being currently evaluated:

UriTemplateMatch match = this.template.Match(this.baseAddress, (Uri)input[0]);