The new Syndication Service Library template allows you to create feed services that will generate either RSS 2.0 or Atom 1.0 feeds or both (although not at the same time, because that would be silly...).
When you create a new project using this template a default feed is created with an interface definition and a feed service to implement the interface. In this sample (“Hello World” if you will) feed service the feed is returned in RSS 2.0 format by default, but as Atom 1.0 if the service has been passed the query string “format=atom”.
The nice feature here is that the delivery format is abstracted away from the code the developer writes: no additional code is required to produce a feed in one or the other format, or as in the sample, both.
In practice I don’t currently see a lot of point in providing any given feed in multiple formats, since most feed readers are able to deal with the most widely used formats in case, so my personal recommendation would be to pick one format you’re happy with and use that - but it’s certainly a good thing that you have the freedom to make that choice without having to take care of the implementation details yourself.
The new WCF Service Host enables you to test your service directly from the library project, so you can take the generated code and start viewing the resulting feeds immediately, switching seamlessly between RSS and Atom output.
At the code level you will mainly be working with the SyndicationFeed class which corresponds with an RSS Channel or Atom Feed, and within each SyndicationFeed a SyndicationItem collection that equates to an Atom Entry or RSS Item. The object model is relatively simple, reflecting the simplicity of the syndication formats themselves (I am of course oversimplifying here so don’t get the wrong idea – all the essential features such as categories are supported).
Points to note: Date/Time values in SyndicationFeed and SyndicationItem use the new DateTimeOffset type to represent time values as an offset from UTC (Coordinated Universal Time).
It’s very easy to derive this value from your local time (if that’s how the value is stored in your data source) because DateTimeOffset has a constructor that takes an old-style DateTime structure as its parameter. When you use this method the offset from local time to UTC is derived automatically.
A second point worth noting is that properties such as Title which seem familiar enough to people used to working with RSS are not as you might expect simple strings, but are instead of type TextSyndicationContent. So if I have an item called for the sake of argument item, I could set its title by assigning it a new instance of TextSyndicationContent with my string value as its constructor – attempting to assign a string value directly to item.Title would cause an exception to be raised. Since my title should be a plain text string (no HTML) I actually prefer the static method TextSyndicationContent.CreatePlaintextContent (), with my title string as its parameter.
Extending the syndication formats
RSS 2.0 and Atom 1.0 are designed to be extensible, this extensibility being achieved by the normal XML method of providing additional attributes and elements with an associated namespace.
Microsoft’s underrated Simple List Extensions for RSS take advantage of this facility, but it’s useful as well for your run of the mill everyday garden blog feed.
For instance, I personally steer well clear of the RSS author element, since it is intended to be populated with the author’s email address, and one thing that is not high on my list of Stunningly Brilliant Things to Do is publishing my email address on the web in machine-readable form.
For this reason if I felt it necessary to identify myself as the author of content in a feed I’d prefer to use the Dublin Core creator element, which allows me to be simply “Daly, Kevin” or something similar. System.ServiceModel.Syndication enables this with the AttributeExtensions and ElementExtensions properties.
There is however a slight catch in the case of RSS: assuming I don’t want to include the full namespace declaration with each occurrence of the element (which would waste space and make me look like a bit of an idiot), I’ll need to add an entry to the AttributeExtensions of the feed, defining the prefix (usually by convention “dc” for Dublin Core) and associating it with the namespace http://purl.org/dc/elements/1.1.
The Feed object as I said earlier corresponds with the RSS channel and Atom feed. Now, while feed is the root element in an Atom feed, the root of an RSS feed is, fittingly enough, rss. This means that while in your Atom feed everything will be as expected, in the RSS feed the namespace declarations will not be on the root element as people have come to expect, but on the channel.
This is still perfectly valid XML, and unless they are unforgivably sloppy feed readers should handle it correctly, but it does differ from what we have come to expect. There may be a way to achieve the more traditional behaviour but if there is I haven’t been able to find it, so for now I just put it down as One of Those Things.
Supporting Conditional GET
Conditional GET is a feature of HTTP that enables the program requesting a file to provide information about the version of that file (if any) they last received, enabling the server to verify whether it actually has a more recent version of the data before sending bits down the line. This can save a lot of bandwidth (and time) for both parties, since in the case where there are no changes the server simply sends an HTTP status of 304 (Not Modified) and no actual content.
When you consider that a popular RSS feed could be retrieved by many feed readers and at regular (and frequent) intervals, the bandwidth wasted sending people information they already have can be huge. Or to put it another way, this is going to cost you (and them) money. And thinking of all that unnecessary traffic flying around, you may find yourself thinking the next time internet performance is getting you down: “Bugger, if only I’d followed Kevin’s advice and implemented Conditional GET”.
The best explanation I’ve seen on this subject was Charles Miller’s blog article, HTTP Conditional GET for RSS Hackers. Important things to know:
1. It’s mediated through the magic of HTTP headers (possibly the only time in my life I will ever use the phrase “magic of HTTP headers”, so treasure it).
2. The server enables the process by accompanying the feed with Last-Modified and ETag headers, the former containing the date/time of the last time anything was added to or changed in the feed (converted to GMT. Unless you start out with GMT), while the ETag contains any value you want to put in it, enclosed in quotes.
3. The client stores the values of ETag and Last-Modified, and presents them with its next request to the server in the If-Modified-Since and If-None-Match headers respectively.
4. The server checks these values against what it would be sending for ETag and Last-Modified this time, and if they both match, it should return a 304 status rather than the feed content.
None of this is rocket science, but unfortunately it tends to get left out when people are making the “Look Kids How Easy It Is to Create Your Own Feeds!!!” case.
So, how do we actually go about doing this using System.ServiceModel.Syndication and WCF?
The first step is to read the header string values from the client request.
The headers (if present) can be found in WebOperationContext.Current.IncomingRequest.Headers, assuming you already have a reference to System.ServiceModel.Web.
The HttpRequestHeader enum includes the necessary values, so we can retrieve our header strings using WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.IfModifiedSince] and WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.IfNoneMatch].
If you use DateTime.Parse to convert the value of If-Modified-Since to a DateTime structure it will be automatically converted from GMT to the local time zone. Since I’m still familiarising myself with all the new bits I haven’t tried converting it to a DateTimeOffset yet. To eliminate the potential for cross-system rounding errors you may find it useful to strip the millisecond values from DateTime values before comparing them (I do), otherwise you may find that you never seem to get a match.
If you determine that the values have not changed, it takes very little code to notify the requester accordingly:
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotModified; WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
This code sets the HTTP status to 304 and returns just the headers with no content, which is exactly what we want
Taking care of the other end – using System.ServiceModel.Syndication to retrieve feeds
The short version of this would be “Please don’t”, but that wouldn’t be very helpful. The longer version would be that SyndicationFeed has a static Load method which takes an XmlReader as a parameter, but that in my opinion this should not normally be used to retrieve feeds directly because it does not provide access to HTTP headers and therefore does not give you the opportunity to observe the client side of Conditional GET.
I suggest that you should instead use a WebClient or HttpWebRequest to obtain the data stream from the server, and create the XmlReader on top of this (this is assuming that you’re not using the Common Feed List infrastructure that comes with IE7).
There is not a lot left to say when it comes to producing your feeds – the auto-generated feed class file gives you a good hint about what you need to provide.
Where you get your content from is of course up to you – you could read it from a database of items that you have entered via whatever method takes your fancy, or generate it using a Fiendishly Cunning algorithm, the latter option having the additional benefit that it will provide years of happy fun for cranks who want to speculate about its occult significance.
But there I’m straying slightly off topic, which means it must be time to stop. So I will.
Download Visual Studio 2008 90 day trial
For detailed information and to request a free 90-day trial DVD of Visual Studio 2008 Team Suite to be sent out to you, go to the Microsoft Visual Studio webpage.
About the Author
Kevin Daly is has been programming professionally for almost 22 years. He has been using .NET and C# continuously since the Beta 2 in 2001, which probably explains the bags under the eyes. Other examples of his views, rants and even the odd code sample can be found on his blog at http://www.kevdaly.co.nz/weblog, the construction of which did in fact involve LINQ to SQL, LINQ to XML, WCF HTTP programming and Other Good Things. He will now stop talking about himself in the third person because it’s frankly a bit weird.
Other related posts:
The New Zealand ALM Conference 2011 (Application Life Cycle Management)
Writing your own Html Helpers for the ASP.NET MVC Framework
Automating Visual Studio 2008