WCF Versioning Attribute

Topics: Web Api
Oct 30, 2011 at 8:54 PM
Edited Nov 1, 2011 at 10:12 PM

Hey im wanting to create an attribute to decorate methods with for versioning with a format similar to the following:

[OperationContract]
[VersionOperation(UriTemplate = "company/{companyid}/users", MediaType = "application/vnd.user.example.com+xml"Version = 1.0)]
IEnumerable<User1_0> GetUsers(string companyId);

[OperationContract]
[VersionOperation(UriTemplate = "company/{companyid}/users", MediaType = "application/vnd.user.example.com+xml"Version = 1.1)]
IEnumerable<User1_1> GetUsers1_1(string companyId);

I think what i need to do is to create some kind of operation handler then when processing redirect based on the URL and accept headers to the correct method. 
However I'm not too sure how to set which method is responsible for processing the request. If anyone has any ideas or knows how to set the processing method for a request they would be much appreciated.

Cheers
Luke
Coordinator
Oct 30, 2011 at 9:02 PM
Edited Oct 30, 2011 at 9:02 PM

To do that you'll need to create a custom derived HttpOperationSelector.  The operation selector determines which operation is chosen to handle the request.

When you implement your own selector, you override the OnSelect method. You will then deteremine the operation name you want to route to based on the looking at the request and your attribute metadata and then specify that name.

 


Oct 30, 2011 at 9:24 PM

Awesome that looks like what im after, to wire that up do i just add it to the EndpointDispatcher.DispatcherRuntime in a custom ServiceBehavior implementation?

Cheers

Coordinator
Oct 30, 2011 at 9:30 PM
Edited Oct 30, 2011 at 9:30 PM

With web api you can now do that through a custom configuration class. If you derive from WebApiConfiguration there are two overrides OnConfigureServiceHost/OnConfigureEndpoint that you can use.

You can still use a behavior if you choose, but you can now wire it up via the config rather than needing a custom host or annotating your service with an attribute.

Oct 30, 2011 at 9:34 PM

Ok cool thanks for your help ill give that a go

Oct 31, 2011 at 5:31 AM

Great post ... thanks for this information Glenn.

Oct 31, 2011 at 7:59 PM
Edited Nov 1, 2011 at 10:17 PM

Hey

I couldn't get this to work as expected, I had to use the custom ServiceBehavior type of implementation as the HttpConfiguration path appears to require the service to be hosted inside IIS (which im not actually doing).

However im having issues with linking the OperationSelector. I use the following to link it up, however it never gets called

 

(Inside a custom IServiceBehavior class)

public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
		{
			foreach (EndpointDispatcher dispatcher in 
				serviceHostBase.ChannelDispatchers.OfType<ChannelDispatcher>()
				.SelectMany<ChannelDispatcherEndpointDispatcher>(delegate(ChannelDispatcher channelDispatcher)
				{
					return channelDispatcher.Endpoints;
				}))
			{
				dispatcher.DispatchRuntime.InstanceProvider = new NinjectInstanceProvider(serviceDescription.ServiceType, _kernel);
				dispatcher.DispatchRuntime.OperationSelector = new VersionHttpOperationSelector(dispatcher.EndpointAddress.Uri, new List<HttpOperationDescription>());
			}
		}

Im assuming im linking it in the wrong place?

Cheers
Coordinator
Nov 1, 2011 at 10:09 AM
You do not need to be in IIS to use the config. HttpServiceHost exposes a Configuration property you can set.
>
Coordinator
Nov 1, 2011 at 4:58 PM

If you use the Web API enhancements, then you can just pass the config instance into the MapServiceRoute<T>(…) call to wire up config.

Daniel Roth

Coordinator
Nov 1, 2011 at 5:43 PM

If the service is not hosted in IIS/ASP.NET then MapServiceRoute won't be available. At least not yet ;-)

Coordinator
Nov 1, 2011 at 8:09 PM

Ah, right, sorry – I read the post backwards. I thought you wanted to host in IIS.

Like Glenn said, just construct HttpServiceHost with your config instance.

Daniel Roth

Nov 1, 2011 at 8:40 PM
Edited Nov 1, 2011 at 8:49 PM

Hmm im creating the HttpServiceHost via a factory, below is how im wiring it up, 

HttpServiceHostFactory factory = new HttpServiceHostFactory();
 
factory.Configuration = new MyHttpConfiguration();

factory.Configuration.EnableTestClient = true;
_myServiceHost = factory.CreateServiceHost(""new Uri[] { new Uri("http://localhost:9090") });
_myServiceHost.Open();

the error I'm getting is:

'ServiceHostFactory.CreateServiceHost' cannot be invoked within the current hosting environment. This API requires that the calling application be hosted in IIS or WAS.

Is the solution to this to create the service directly not via the factory?

 

Thanks for your help guys

Cheers

Coordinator
Nov 1, 2011 at 9:02 PM

You should create the HttpServiceHost directly and pass the config instance into the constructor.

Daniel Roth

Nov 1, 2011 at 10:12 PM
Edited Nov 1, 2011 at 10:22 PM

I think im going round in circles a bit, from what i can tell im trying to add the operation selector too early currently (Via the HttpConfiguration) the below code is not finding any ChannelDispatchers,

however if i add this same code in a behavior there is an channel dispatcher created. Do i need to initialise the ChannelDispatcher collection somehow or am i adding this code at the wrong place in the configuration class?

		protected override void OnConfigureServiceHost(HttpServiceHost serviceHost)
		{
			base.OnConfigureServiceHost(serviceHost);
 
			foreach (EndpointDispatcher dispatcher in
				serviceHost.ChannelDispatchers.OfType<ChannelDispatcher>()
				.SelectMany<ChannelDispatcherEndpointDispatcher>(delegate(ChannelDispatcher channelDispatcher)
				{
					return channelDispatcher.Endpoints;
				}))
			{
				dispatcher.DispatchRuntime.OperationSelector = new VersionHttpOperationSelector();
			}
		}
When i use a behavior the operation selector appears to get added but i never get a callback into my OnSelectOperation function

Cheers
Nov 22, 2011 at 2:36 PM
lukemcgregor wrote:

I think im going round in circles a bit, from what i can tell im trying to add the operation selector too early currently (Via the HttpConfiguration) the below code is not finding any ChannelDispatchers,

however if i add this same code in a behavior there is an channel dispatcher created. Do i need to initialise the ChannelDispatcher collection somehow or am i adding this code at the wrong place in the configuration class?

		protected override void OnConfigureServiceHost(HttpServiceHost serviceHost)
		{
			base.OnConfigureServiceHost(serviceHost);
 
			foreach (EndpointDispatcher dispatcher in
				serviceHost.ChannelDispatchers.OfType<ChannelDispatcher>()
				.SelectMany<ChannelDispatcherEndpointDispatcher>(delegate(ChannelDispatcher channelDispatcher)
				{
					return channelDispatcher.Endpoints;
				}))
			{
				dispatcher.DispatchRuntime.OperationSelector = new VersionHttpOperationSelector();
			}
		}
When i use a behavior the operation selector appears to get added but i never get a callback into my OnSelectOperation function

Cheers

Did you ever get this working? I'm looking at how to support versions myself and I'm not sure how to proceed.

Nov 22, 2011 at 7:36 PM

Nah i sorta hit a wall and gave up until I actually need to implement this, I think i was getting close though.

Basically what i was planning was to plug into the initial request processing and use the OperationSelector to pick a method based on the request content type and a custom attribute applied to the method.

My idea was to decorate methods like this:

[OperationContract]
[VersionOperation(UriTemplate = "company/{companyid}/users", MediaType = "application/vnd.user.example.com+xml", Version = 1.0)]
IEnumerable<User1_0> GetUsers(string companyId);

The piece i got stuck on was just getting that Operation Selector to work correctly, i think i am probably just plugging into it in the wrong way.

If you have any luck around any of this i would be really keen to hear.

Nov 22, 2011 at 9:48 PM
I do had hit issues with ServiceBehavior. Adding a OperationSelector with EndpointBehavior works though.
            HttpServiceHost host = new HttpServiceHost(typeof(Service), baseAddress);
            host.AddDefaultEndpoints();

            foreach (ServiceEndpoint se in host.Description.Endpoints)
            {
                Console.WriteLine("Adding ednpointbehavior to " + se.Address.ToString());
                se.Behaviors.Add(new CustomEndpointBehavior());
            }

            host.Open();

    public class CustomOperationSelector : HttpOperationSelector
    {
        protected override string OnSelectOperation(HttpRequestMessage request)
        {            
            Console.WriteLine("in CustomOperatioNSelector:OnOperation()");
            return "GetPerson";
        }
    }

    public class CustomEndpointBehavior : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            Console.WriteLine("In CustomEndpointBehavior and adding customoperation selector");
            endpointDispatcher.DispatchRuntime.OperationSelector = new CustomOperationSelector();
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }
    }
Dec 13, 2011 at 8:00 AM

Luke, how would you go about creating the VersionOperation attribute? Webinvoke is a sealed class, would you just attempt to reimplement its functionality?

Cheers,
Chris

Dec 15, 2011 at 3:09 PM

Ok, I ended up choosing a different approach with a message handler:

 

    public class VersioningMessageHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (!request.Headers.Contains("X-VERSION"))
            {
                Regex uriVersionRegex = new Regex(@"v(\d)\.(\d)+", RegexOptions.IgnoreCase);
                var uriMatches = uriVersionRegex.Matches(request.RequestUri.ToString());
                if (uriMatches.Count > 0)
                    return Task.Factory.StartNew(() =>
                    {
                        var response = new HttpResponseMessage(HttpStatusCode.NotFound);
                        return response;
                    });
            }
            else
                UpdateUri(request);

            return base.SendAsync(request, cancellationToken);
        }

        private void UpdateUri(HttpRequestMessage request)
        {
            Uri uri = null;
            var pos = GetIndexOfNth(request.RequestUri.ToString(), "/", 0, 5);

            if (pos != -1)
                uri = new Uri(request.RequestUri.ToString().Remove(request.RequestUri.ToString().Length - 1, 1).
                    Insert(pos, string.Format("/v{0}", request.Headers.GetValues("X-VERSION").First())));
            else
                uri = new Uri(string.Format("{0}/v{1}", request.RequestUri.ToString(), request.Headers.GetValues("X-VERSION").First()));

            request.RequestUri = uri;
        }

        private int GetIndexOfNth(string input, string value, int startIndex, int nth)
        {
            if (nth < 1)
                throw new NotSupportedException("Param 'nth' must be greater than 0!");
            if (nth == 1)
                return input.IndexOf(value, startIndex);

            return GetIndexOfNth(input, value, input.IndexOf(value, startIndex) + 1, --nth);
        }
    }

 

 

I use this in conjunction with setting the corresponding Uri template on my versioned service operation like so:

        [WebGet(UriTemplate = "/Bla")]
        public IEnumerable<Contact> GetBla()
        {
            var contacts = new List<Contact>()
        {
            new Contact {ContactId = 1, Name = "Phil Haack"},
            new Contact {ContactId = 2, Name = "HongMei Ge"},
            new Contact {ContactId = 3, Name = "Glenn Block"},
            new Contact {ContactId = 4, Name = "Howard Dierking"},
            new Contact {ContactId = 5, Name = "Jeff Handley"},
            new Contact {ContactId = 6, Name = "Yavor Georgiev"}
        };
            return contacts;
        }

        [WebGet(UriTemplate = "v1.3454/Bla")]
        public IEnumerable<Contact> GetBlaOld()
        {
            var contacts = new List<Contact>()
        {
            new Contact {ContactId = 1, Name = "Test 1"},
            new Contact {ContactId = 2, Name = "Test 2"},
            new Contact {ContactId = 3, Name = "Test 3"},
            new Contact {ContactId = 4, Name = "Test 4"},
            new Contact {ContactId = 5, Name = "Test 5"},
            new Contact {ContactId = 6, Name = "Test 6"}
        };
            return contacts;
        }

So whenever I set the X-Version header to a specific version, I will get the set version of the operation. If the X-Version header is not set I will automatically get the newest version of the operation, which does not have a specific URI template.

 

Maybe that's of help to someone. Otherwise let me know how I screwed up ;-)

Cheers,
Chris