Mon Jun 20 2011

As you know Mark and I run a podcast called Developer Smackdown. One of our driving goals was and still is to write the site from scratch and build everything around it. It's one of the things that I do to keep my skills fresh and real world. Our site started as MVC back in the early days and has gone through ever upgrade as well as converting all the views to Razor. There are a number of services and supporting utilities allowing us to do things like move mp3's around and orchestrate the post production process. While Developer Smackdown is certainly no This Developer Life we still have a substantial loyal listener base and when things go bad ( which they do ) we hate it.

Why Now?

A few weeks back the server I have hosted all of my sites on finally died. To be honest part of me was glad. I have personally maintained a set of physical servers since 1999 and if I am honest while I learned a lot it was a bigger headache that I realized. That isn't a knock against the technology stacks used, and while you might assume I just ran Windows and IIS I also ran Linux and Apache until about 2 years ago. The headache stemmed from all of the things I don't know. I enjoy development, not setting up AD, DHCP, LDAP, DNS, blaa blaa blaa. While my quasi datacenter did the job, it was quickly again and slowly dying. My main WebServer was an HP DL380 with 6 scsi drives. While things like storage and memory is much cheaper than it used to be, ski drives for servers are still expensive. On the flip side the windows hosting space is drastically cheaper than it used to be. I was already debating on a migration and when the drive died on me, my mind was made up. Time to move to a proper hosting provider.

Getting Started

This should be no big deal right? It's just it's IIS to IIS, right?  Copy some files, change DNS etc. Of course that didn't turn out to be the case. We have a series of services that do things like redirect you to the correct MP3 and supply the RSS feed. We had been using the WCF Rest Starter Kit for this but now suddenly the .svc extension didn't work. I spent a few hours looking into things before I gave up. I kept getting a 404 for some odd reason and finally in the middle of the night I just said screw it.  We had just finished doing a podcast with Glenn Block about the new WCF Web Api and I said here is a perfect example to just bite the bullet and get upgraded. So I did. Installing is easy with NuGet:

Install-Package WebApi.All

Implementation?

My services are simple, I know that. Our main service is the RSS and ATOM feeds. To produce these we use a WCF object called SyndicationFeedFormatter in System.ServiceModel.Syndication. There are two objects that implement that base class, Atom10FeedFormatter and Rss20FeedFormatter. With the WCF Rest Starter Kit we basically just created one of these objects and would return it in the feed. WCF took care of the rest. It looked similar to this: 

[ServiceContract]
[ServiceKnownType(typeof(Atom10FeedFormatter<PodcastFeed>))]
[ServiceKnownType(typeof(Rss20FeedFormatter<PodcastFeed>))]
public class PodcastServices
{
    [OperationContract]
    [WebGet(UriTemplate = "GetFeed", BodyStyle = WebMessageBodyStyle.Bare)]
    public SyndicationFeedFormatter GetFeed()
    {
        PodcastFeed feed = _feedRepository.BuildFullFeed();
        SyndicationFeedFormatter formatter = new Atom10FeedFormatter<PodcastFeed>( feed );
        return formatter;
    }

}

Nothing special, mind you I cut some of the code for brevity purposes. When GetFeed is called we build up a few objects and then pass that object to the formatter that will ultimately be returned. WCF knew about the formatter and would format the object such that it was a valid RSS or ATOM feed and that was it.

BUT that now didn't work with Web Api. After talking some with Glenn he pointed me in a few directions. Since Web Api didn't have any knowledge about the formatter I was going to need to handle that explicitly and return it. At first I thought this was going to be a nightmare but it turned out to be nothing at all.

New Implementation

After installing ( Install-Package WebApi.All ) I had to rework the method a little differently, while the overall class declaration stayed the same.  The new method now didn't require the BodyStyle for the WebGet, sweet I like removing code. The bigger change was I was not going to return a HttpResponseMessage and take in a HttpRequestMessage, these are found in System.Net.Http.

[ServiceContract]
[ServiceKnownType(typeof(Atom10FeedFormatter<PodcastFeed>))]
[ServiceKnownType(typeof(Rss20FeedFormatter<PodcastFeed>))]
public class PodcastServices
{
    [OperationContract]
    [WebGet(UriTemplate = "GetFeed")]
    public HttpResponseMessage GetFeed(HttpRequestMessage httpRequestMessage)
    {
        // Do Epic Here
    }
}

The first thing to notice is I am now taking an HttpReqestMessage.  This is something that Web Api is actually passing to us.  In previous versions of WCF we would have to use WebOperationContext.Current.  While that worked it made it very hard to do things like automated testing.  Now we can actually create an HttpRequestMessage and pass it in yourself.  Vey nice! As the method signature assumes we are going to take on the responsibility to create and return the response message.

[OperationContract]
[WebGet(UriTemplate = "GetFeed")]
public HttpResponseMessage GetFeed(HttpRequestMessage httpRequestMessage)
{
    // Get the Feed.
    PodcastFeed feed = _feedRepository.BuildFullFeed();

    string query = HttpUtility.ParseQueryString(httpRequestMessage.RequestUri.Query).Get("format");

    SyndicationFeedFormatter formatter = null;

    if (string.Equals(query,
                        Enum.GetName(typeof(strings.SyndicationFormats), strings.SyndicationFormats.ATOM),
                        StringComparison.InvariantCultureIgnoreCase)) // "/?format=atom" )
        formatter = new Atom10FeedFormatter<PodcastFeed>(feed);
    else
        formatter = new Rss20FeedFormatter<PodcastFeed>(feed);

    var memoryStream = new MemoryStream();

    var settings = new XmlWriterSettings();
    settings.Encoding = Encoding.UTF8;
    settings.ConformanceLevel = ConformanceLevel.Document;
    settings.Indent = true;

    using (var xmlWriter = XmlWriter.Create(memoryStream, settings))
    {
        formatter.WriteTo(xmlWriter);
        xmlWriter.Flush();
        xmlWriter.Close();
    }

    memoryStream.Seek(0, SeekOrigin.Begin);

    HttpResponseMessage httpResponseMessage = new HttpResponseMessage();

    httpResponseMessage.Content = new StreamContent(memoryStream);
    httpResponseMessage.Content.Headers.ContentLength = memoryStream.Length;

    return httpResponseMessage;
}

Basically start with building a feed. Once we have the feed we need to get some user input to figure out which formatter to use. The formatter actually has a WriteTo method on it, we can just have the formatter write to an XmlWrite in a MemoryStream. Then we just need to create the HttpResponseMessage of which we will return to the caller.  The HttpResponseMessage has a Content property which is of course the Content. Content will take and object called StreamContent and we just need to give it the MemoryStream we created earlier. Next we just need to set some of the headers and return the message.

Now we have a service, but we need to wire it up. In our Global.asax.cs Application_Start we need to register a route. This route will map the url segment to the class which has our operations. You will also notice I am mapping the old url with the .svc extension just to make sure anyone using that url still works.

RouteTable.Routes.MapServiceRoute<PodcastServices>("Services/PodcastServices.svc");
RouteTable.Routes.MapServiceRoute<PodcastServices>("Services/PodcastServices");

That was it. I got to remove all of that old WCF configuration stuff. It just works.

More Information