Creating a JSON Service with WebGet and WCF 3.5

I couldn't let it go.  Tim's post had me intrigued about how to convert RSS to JSON using some of the new goodness in .NET 3.5.  If you use the JavaScriptSerializer (which, unfortunately, is marked as obsolete), then your code is as simple as this:

 <%@ WebHandler Language="C#" Class="Handler" %>

using System;
using System.Web;
using System.Web.Script.Serialization;
using System.ServiceModel.Syndication;

public class Handler : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) 
    {
        context.Response.ContentType = "application/json";

        SyndicationFeed feed = SyndicationFeed.Load(new Uri("https://blogs.msdn.com/kaevans/rss.aspx"));

        Rss20FeedFormatter formatter = new Rss20FeedFormatter(feed);
        JavaScriptSerializer ser = new JavaScriptSerializer();
        
        context.Response.Write(ser.Serialize(feed));         
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }

}

Alas, the JavaScriptSerializer is marked as obsolete, indicating that we must implement the DataContractJsonSerializer. You might be asking yourself why, since .NET 3.5 provides syndication feed support, you can't just do something a little simpler, such as the following:

     [OperationContract]
    [WebGet(UriTemplate = "atom",ResponseFormat=WebMessageFormat.Json)]
    public Rss20FeedFormatter GetFeedAsRss()
    {        
        SyndicationFeed feed = SyndicationFeed.Load(new Uri("https://blogs.msdn.com/kaevans/rss.aspx"));        
        return feed;
    }

    [OperationContract]
    [WebGet(UriTemplate = "rss", ResponseFormat=WebMessageFormat.Json)]
    public Atom10FeedFormatter GetFeedAsAtom()
    {
        SyndicationFeed feed = SyndicationFeed.Load(new Uri("https://blogs.msdn.com/kaevans/rss.aspx"));
        return feed;
    }

The DataContractJsonSerializer follows the same constraints as the DataContractSerializer... namely, no attributes.  Since RSS and Atom have lots of attributes, the DataContractSerializer can't be used in this manner (that, and the problem that the SyndicationFeed type is not marked serializable).  In order to use the DataContractJsonSerializer, our types need to be decorated with the DataContract/DataMember attributes. 

My last post showed how you could use WCF and LINQ to XML to retrieve an RSS feed and serialize it to JSON using a generic HttpHandler.  In that post, I explicitly used the DataContractJsonSerializer to show how to use that type.  Instead of using the HttpHandler, let's use .NET 3.5 to host the code using the new HTTP API for WCF.

WCF 3.5 introduces the WebGetAttribute, which is part of the HTTP programming model for WCF 3.5.  This attribute allows us to invoke our service with an HTTP URI and provides the ability to templatize that URI.  

 using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Web;

[ServiceContract]
public interface IService
{
    [OperationContract]
    [WebGet(UriTemplate = "blog?dayCount={days}",ResponseFormat=WebMessageFormat.Json)]
    List<FeedItem> GetFeeds(int days);
}

[DataContract]
public class FeedItem
{
    [DataMember] public string Title { get; set; }
    [DataMember] public DateTime Published { get; set; }
    [DataMember] public string Url { get; set; }
    [DataMember] public int NumComments { get; set; }
}

Notice the UriTemplate parameter for the WebGetAttribute.  The URI template here is relative to the service's base address, which means our method will be tacked onto the end of the URI.  The actual URI to invoke the service is:

 https://localhost:49455/WCFServices/service.svc/blog?dayCount=2

If you don't want the service.svc in the URI, then Jon Flanders shows how to rewrite the path using an HttpModule

Now that we implemented the contract, let's see how easy it is to implement the service.  Instead of using the SyndicationFeed type in .NET 3.5, we'll stick to our LINQ example. 

 using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Linq;
using System.Xml.Linq;

public class Service : IService
{
    public List<FeedItem> GetFeeds(int days)
    {
        XNamespace slashNamespace = "https://purl.org/rss/1.0/modules/slash/";

        XDocument rssFeed = XDocument.Load("https://blogs.msdn.com/kaevans/rss.aspx");
        var posts = from item in rssFeed.Descendants("item")
                    select new
                    {
                        Title = item.Element("title").Value,
                        Published = DateTime.Parse(item.Element("pubDate").Value),
                        Url = item.Element("link").Value,
                        NumComments = int.Parse(item.Element(slashNamespace + "comments").Value)
                    };

        var newPosts = from item in posts
                       where (DateTime.Now - item.Published).Days < days
                       select item;

        List<FeedItem> itemsList = new List<FeedItem>();
        foreach (var item in newPosts)
        {
            itemsList.Add(new FeedItem { Title = item.Title, Published = item.Published, Url = item.Url, NumComments = item.NumComments });
        }
        return itemsList;
    }   
}

There ya go... a WCF service that turns XML into JSON that is accessible through a templated querystring URI.  We could do some other interesting stuff, like passing the URL as a parameter to the service. 

For more information on using Syndication, JSON, and WCF, here are a couple of links: