RSS feeds in Orchard

(c) Bertrand Le Roy When we added RSS to Orchard, we wanted to make it easy for any module to expose any contents as a feed. We also wanted the rendering of the feed to be handled by Orchard in order to minimize the amount of work from the module developer.

A typical example of such feed exposition is of course blog feeds.

We have an IFeedManager interface for which you can get the built-in implementation through dependency injection. Look at the BlogController constructor for an example:

public BlogController(
IOrchardServices services,
IBlogService blogService,
IBlogSlugConstraint blogSlugConstraint,
IFeedManager feedManager,
RouteCollection routeCollection) {

If you look a little further in that same controller, in the Item action, you’ll see a call to the Register method of the feed manager:

_feedManager.Register(blog);

This in reality is a call into an extension method that is specialized for blogs, but we could have made the two calls to the actual generic Register directly in the action instead, that is just an implementation detail:

feedManager.Register(blog.Name, "rss",
new RouteValueDictionary {
{ "containerid", blog.Id } }); feedManager.Register(blog.Name + " - Comments", "rss",
new RouteValueDictionary {
{ "commentedoncontainer", blog.Id } });

What those two effective calls are doing is to register two feeds: one for the blog itself and one for the comments on the blog. For each call, the name of the feed is provided, then we have the type of feed (“rss”) and some values to be injected into the generic RSS route that will be used later to route the feed to the right providers.

This is all you have to do to expose a new feed. If you’re only interested in exposing feeds, you can stop right there. If on the other hand you want to know what happens after that under the hood, carry on.

What happens after that is that the feedmanager will take care of formatting the link tag for the feed (see FeedManager.GetRegisteredLinks). The GetRegisteredLinks method itself will be called from a specialized filter, FeedFilter. FeedFilter is an MVC filter and the event we’re interested in hooking into is OnResultExecuting, which happens after the controller action has returned an ActionResult and just before MVC executes that action result. In other words, our feed registration has already been called but the view is not yet rendered. Here’s the code for OnResultExecuting:

model.Zones.AddAction("head:after", 
html => html.ViewContext.Writer.Write(
_feedManager.GetRegisteredLinks(html)));

This is another piece of code whose execution is differed. It is saying that whenever comes time to render the “head” zone, this code should be called right after. The code itself is rendering the link tags.

As a result of all that, here’s what can be found in an Orchard blog’s head section:

<link rel="alternate" type="application/rss+xml"
    title="Tales from the Evil Empire"
    href="/rss?containerid=5" />
<link rel="alternate" type="application/rss+xml"
    title="Tales from the Evil Empire - Comments"
    href="/rss?commentedoncontainer=5" />

The generic action that these two feeds point to is Index on FeedController. That controller has three important dependencies: an IFeedBuilderProvider, an IFeedQueryProvider and an IFeedItemProvider.

Different implementations of these interfaces can provide different formats of feeds, such as RSS and Atom. The Match method enables each of the competing providers to provide a priority for themselves based on arbitrary criteria that can be found on the FeedContext.

This means that a provider can be selected based not only on the desired format, but also on the nature of the objects being exposed as a feed or on something even more arbitrary such as the destination device (you could imagine for example giving shorter text only excerpts of posts on mobile devices, and full HTML on desktop). The key here is extensibility and dynamic competition and collaboration from unknown and loosely coupled parts. You’ll find this pattern pretty much everywhere in the Orchard architecture.

The RssFeedBuilder implementation of IFeedBuilderProvider is also a regular controller with a Process action that builds a RssResult, which is itself a thin ActionResult wrapper around an XDocument.

Let’s get back to the FeedController’s Index action.

After having called into each known feed builder to get its priority on the currently requested feed, it will select the one with the highest priority.

The next thing it needs to do is to actually fetch the data for the feed. This again is a collaborative effort from a priori unknown providers, the implementations of IFeedQueryProvider. There are several implementations by default in Orchard, the choice of which is again done through a Match method. ContainerFeedQuery for example chimes in when a “containerid” parameter is found in the context (see URL in the link tag above):

public FeedQueryMatch Match(FeedContext context) {
    var containerIdValue = 
context.ValueProvider.GetValue("containerid"); if (containerIdValue == null) return null; return new FeedQueryMatch {
FeedQuery = this, Priority = -5 }; }

The actual work is done in the Execute method, which finds the right container content item in the Orchard database and adds elements for each of them. In other words, the feed query provider knows how to retrieve the list of content items to add to the feed.

The last step is to translate each of the content items into feed entries, which is done by implementations of IFeedItemBuilder. There is no Match method this time. Instead, all providers are called with the collection of items (or more accurately with the FeedContext, but this contains the list of items, which is what’s relevant in most cases). Each provider can then choose to pick those items that it knows how to treat and transform them into the format requested.

This enables the construction of heterogeneous feeds that expose content items of various types into a single feed. That will be extremely important when you’ll want to expose a single feed for all your site.

So here are feeds in Orchard in a nutshell. The main point here is that there is a fair number of components involved, with some complexity in implementation in order to allow for extreme flexibility, but the part that you use to expose a new feed is extremely simple and light: declare that you want your content exposed as a feed and you’re done.

There are cases where you’ll have to dive in and provide new implementations for some or all of the interfaces involved, but that requirement will only arise as needed. For example, you might need to create a new feed item builder to include your custom content type but that effort will be extremely focused on the specialized task at hand. The rest of the system won’t need to change.

So what do you think?

10 Comments

  • Hi Bertrand

    I guess when you say "declare that you want your content exposed as a feed and you’re done." that you also have the need to IFeedItemProvider for your data model. Otherwise how does the mapping works?

    Cheers
    Laurent

  • @Roland and @Laurent: I suppose I could have been more explicit. We provide at least one default implementation of each interface. All you have to do is Register in most cases.
    The cases where you'd have to do your own mapping would be for unusual content types.
    Most of the times, you won't have to do that because your content type will be built from basic parts, such as the body part, which the default implementation should be able to handle just fine.

  • (and that's work you'd have to do anyways if you didn't have Orchard to help)

  • @Bertrand: Ok, I got the idea of mapping now. Thanks

  • I want to create a differing implementation in a new module, because the default does some "interesting" stuff (eg description includes HTML, which it shouldn't) and I want to add some more fields (eg tags as categories, full html as CDATA wrapped "content" extension, attached media as embed etc.
    I have had a good bash at the source, but it is fairly complex. Can you give me an overview of the minimum moving parts a a module for making a variant? happy for it to have different routes.

    many thanks!

  • @jeremy: if you want to alter how content items are transformed into feed items, you need to implement IFeedItemBuilder. You can look at existing implementations for inspiration.

  • Probably shows my ignorance - how to I make Orchard "respect" my implementation rather than the default one in Feeds/standardBuilders? Can a theme do that? Or am I better off just developing a new one on a different route?

  • @jeremy: sure, you can do anything. If you don't want to go through our RSS infrastructure you can create your own. I'm not sure what would prevent you from doing that.

  • How can you suppress a specific list from generating a feed? Is this easy to do as well based on the URL of the page?

  • @cmaorg: not sure why you would want to do that but you can do anything by overtaking that route and writing your own controller action to replace it.

Comments have been disabled for this content.