Versioning REST Services with WCF Http Processors

As I discussed in my last post, the new WCF Http Model includes http processors that you can use to inject cross cutting aspects into an existing service. We can use processors for supporting a versioning schema based on content types as it discussed here by Peter Williams. 

Supporting custom media types in a WCF Http Service is a now a piece of cake with the new MediaTypeProcessor. The only thing you need to do is to implement a new processor by deriving your  class from the base class MediaTypeProcessor or any existing implementation like XmlProcessor for example.

The implementation I showed below derives from the XmlProcessor implementation and adds support for two new content types (“application/vnd.mycompany.myapp+xml” and “application/vnd.mycompany.myapp-v2+xml”)

public class XmlVersionedProcessor : XmlProcessor
{
    public static readonly MediaTypeVersion[] Versions = new MediaTypeVersion[] 
    {
        new MediaTypeVersion { Version = 1, MediaType = "application/vnd.mycompany.myapp+xml" },
        new MediaTypeVersion { Version = 2, MediaType = "application/vnd.mycompany.myapp-v2+xml" },
    };
 
    public XmlVersionedProcessor(HttpOperationDescription operation, MediaTypeProcessorMode mode)
        : base(operation, mode)
    {
    }
 
    public override IEnumerable<string> SupportedMediaTypes
    {
        get
        {
            foreach (var mediaType in base.SupportedMediaTypes)
            {
                yield return mediaType;
            }
 
            foreach (var mediaType in Versions.Select(v => v.MediaType))
            {
                yield return mediaType;
            }
        }
    }
 
    public class MediaTypeVersion
    {
        public int Version { get; set; }
        public string MediaType { get; set; }
    }
}

That represents one part of the implementation. Now, the resource implementation somehow needs to know which message it needs to return according to the “accept” header sent by the client implementation. The resource implementation can just look for the “accept” header in the request message by passing an “HttpRequestMessage” argument to the operation. However, I decided to go with a different approach so the operation implementation did not get tied to a content type.

[ServiceContract]
public class AccountResource
{
    [OperationContract]
    [WebGet(UriTemplate = "{id}")]
    public Account Get(string id, int version)
    {
        if (version == 1)
        {
            return new Account { Name = id };
        }
        else 
        {
            return new AccountV2 { Name = id, Email = new string[] { id + "@mail.com" } };
        }
    }
}

The operation receives an argument representing the version that needs to be returned to the client. As you can see, the implementation returns Account or AccountV2 based on that version argument.

[XmlInclude(typeof(AccountV2))]
public class Account
{
    public string Name { get; set; }
}
    
public class AccountV2 : Account
{
    public string[] Email { get; set; }
}

AccountV2 adds a list of emails to the account information.

You might be asking how I did to transform the accept header into a version argument. Well, the response is another processor implementation :)

public class VersionProcessor : Processor<RequestHeaders, int>
{
    public VersionProcessor()
    {
        this.InArguments[0].Name = HttpPipelineFormatter.ArgumentRequestHeaders;
        this.OutArguments[0].Name = "version";
    }
 
    public override ProcessorResult<int> OnExecute(RequestHeaders input)
    {
        foreach (var accept in input.Accept)
        {
            var version = XmlVersionedProcessor
                .Versions.FirstOrDefault(v => v.MediaType.Equals(accept.Value, 
                    StringComparison.InvariantCultureIgnoreCase));
 
            if (version != null)
            {
                return new ProcessorResult<int>
                {
                    Output = version.Version
                };
            }
        }
 
        return new ProcessorResult<int>    { Output = 0 };
    }
}

This processor receives the RequestHeaders, looks for the Accept Header in the “OnExecute” method, and returns a version number based on the value found on that header. The version number in this example could be 1 for “application/vnd.mycompany.myapp+xml” or 2 for “application/vnd.mycompany.myapp-v2+xml”.

Published Friday, November 19, 2010 9:56 AM by cibrax
Filed under: , ,

Comments

No Comments