Filtered RSS feed using the Sitecore rules engine

by aweber1. 0 Comments

I recently had a need to create a custom RSS feed in Sitecore that could dynamically exclude certain items from the feed based on specific conditions.

My first thought was to simply use a Sitecore query in the feed item’s “Items” field, as the field allows you to either specify a root item whose descendants will be included in the feed or use Sitecore query to specify which items will be included in the feed. This seemed like a valid approach until I started writing the query and it quickly turned into a garbled mess – I’m not a fan of complex xpath queries. On top of that, I could never expect an average content author to understand the Sitecore query syntax or maintain a complex query.

I thought “it would be nice if an average content author could use an intuitive, somewhat familiar interface to specify the logic/conditions that should be used to exclude items from the RSS feed“. Enter the ever powerful but woefully under-used Sitecore rules engine and the Rules field type. By creating a custom RSS feed template, adding a Rules field, and extending the standard Sitecore RSS feed class, this turned out to be a pretty easy task.

Create a new RSS feed template

Create a new template that inherits the “/sitecore/templates/system/feeds/RSS Feed” template. It’s important that this inheritance occurs otherwise the standard Sitecore RSS feed rendering will not work and you would need to roll your own feed manager (let’s try to avoid that).

New RSS feed template

With your new template created, add a field of type “Rules”. I named the field “Exclude Item Rules” but feel free to rename to suit your needs.

Extend the standard Sitecore RSS public feed class

The Sitecore.Syndication.PublicFeed class has a number of methods available for overriding to assist your efforts in creating custom RSS feeds. Fortunately, for the scope of this post, we only need to override the GetSourceItems method.

The gist of the code below is to first retrieve all the items for the given RSS feed, then retrieve the exclusion rules defined in the RSS feed item, apply those rules to the RSS feed items and ignore any items matching the exclusion rules. (Hat tip to Reflector for the assist in reflecting through the Sitecore rules engine code to determine how to retrieve, parse and apply rules)


using System.Collections.Generic;
using System.Linq;
using Sitecore.Data.Items;
using Sitecore.Rules;

namespace Sitecore.SharedSource.Syndication
{
	public class RulesFilterFeed : Sitecore.Syndication.PublicFeed
	{
		public override IEnumerable<Item> GetSourceItems()
		{
			//Get the unfiltered items from the RSS feed
			IEnumerable<Item> unfilteredItems = base.GetSourceItems();

			//Get the rules to process from the current feed item.
			//If no rules exist, return the unfiltered items.
			var excludeRules = GetExclusionRules<RuleContext>();
			if (excludeRules == null)
				return unfilteredItems;

			//Filter the items based on the defined exclusion rules.
			//The expression below basically loops through each unfiltered item and evaluates each defined exclusion rule against the item.
			//If the unfiltered item matches one of the exclusion rules, don't add it to the filtered items list.
			IEnumerable<Item> filteredItems = (from item in unfilteredItems
											   let ruleContext = new RuleContext { Item = item }
											   from rule in excludeRules.Rules
											   where !rule.Evaluate(ruleContext)
											   select item);
			return filteredItems;
		}

		protected virtual RuleList<T> GetExclusionRules<T>() where T : RuleContext
		{
			//Retrieve the rules field defining the exclusion rules.
			//If the field doesn't exist, exit the method.
			var excludeField = FeedItem.Fields["Exclude Item Rules"];
			if (excludeField == null)
				return null;

			//Use the Sitecore.Rules.RuleFactory class to get the rules from the field
			return RuleFactory.GetRules<T>(excludeField);
		}
	}
}

Create a RSS Feed

The final step in the process is to create a new RSS feed item based on the RSS feed template you created earlier. Prior to doing so, though, I would recommend editing the RSS feed template Standard Values item – populating the Type field with the name of the extended public RSS feed class you created. Populating this field instructs Sitecore to use your extended class to process and render the RSS feed.

While you do have the option of populating this field at the RSS feed item level, populating this field in the Standard Values item ensures that all RSS feed items based on your new RSS feed template automatically use your extended class.

In your newly created RSS feed item (based on your newly created RSS feed template), you should set the “Items” field as you normally would – indicating the item to use as the root for your RSS feed items. You should also see the “Exclude Item Rules” field (or whatever you ended up naming it). You (or better yet, your content authors) can use this field like any other item rules field to specify rules for excluding items from the RSS feed.

Conclusion

And that’s it. Pretty easy right? Just another example of the flexiblity and extensibility Sitecore provides. Enjoy!

Sitecore defensive coding practices

by aweber1. 2 Comments

I recently inherited a Sitecore solution which, after some review, I found to contain what I like to call “Future Adam is going to hate me” code, i.e. code which is easy to hammer out for a deadline, but often quite brittle, difficult to maintain, and which I will ultimately kick myself for writing.

As I begin to remedy the solution I’ve taken over, some patterns have started to emerge that I thought would be good to share. These are very simple practices that are easy to adopt and employ on a regular basis. Obviously you, dear reader, already write flawless code, but perhaps you have a “friend” who may find some value in the tips below.

1. Always check if an item is null

This is just good practice in general, but even more so when writing code for Sitecore as it is often the #1 reason for a YSOD. Any time you use Database.GetItem() or any other item-retrieval methods, you should always check whether or not the retrieved object is null. For instance:

Item item = Sitecore.Context.Database.GetItem("MyItemGUID or MyItemPath");
if (item == null)
    return;
//continue on safely

Never (ever) assume that the item you are retrieving exists (yes, even that static, fixed item that no one is supposed to touch). Other users can and will delete or move the item you thought was there but now isn’t.

2. Use the Sitecore.Data.Items.Item field collection indexer to retrieve field values instead of Field.Value whenever possible

If you just need the string value of a field, use the Item field collection indexer like so:

string fieldValue = item["FieldName"];

as opposed to

string fieldValue = item.Fields["FieldName"].Value;

The former will always return a string value, even if the field doesn’t exist. Basically, this shortcut null checks the requested field for you. The latter will throw a null exception if the field you’re requesting doesn’t exist in the item’s field collection.

So unless you’re also checking a field for existence before attempting to access it’s value, it’s safer (and requires less code) to use the item’s field collection indexer to retrieve a field value. Here are expanded examples to demonstrate the efficiency of the first approach vs the second approach:

string fieldValue = item["FieldName"];
if (fieldValue == string.Empty)
    return;
//continue on safely

vs

Field f = item.Fields["FieldName"];
if (f == null)
    return;

string fieldValue = f.Value;
if (fieldValue == string.Empty)
    return;
//continue on safely

Again, this approach is largely relevant when you just need the string value of a field. If you need strongly-typed access to field values, then you’ll need to cast the field to the proper field type and retrieve the value using the relevant field type properties.

3. Minimize usage of hard-coded field names and item GUIDs

Hard-coded field names and item GUIDs make for hard-to-maintain code. For instance, if a field name changes for a given template, you need to find all references to that field name in ALL of your code. Sure you can try to use find and replace but that’s a process which is tedious, cumbersome and often leads to unhandled runtime errors.

At the very least you should have a single reference point for all field names and item GUIDs in your code. While it’s hard to avoid hard-coded field names and item GUIDs completely, having them in one place means you only have to change them in one place when the time comes (and it will). A better approach, though, is #4:

4. Use object mapping, domain modeling, item decorating, etc..

If you’re not using some form of object mapping, domain modeling or item decorating for your Sitecore solutions yet, I strongly urge you to spend some time getting familiar with the various patterns/approaches and consider using one for all future projects. Any of these patterns/approaches will help you write cleaner, more maintainable code. There are a number of tools available to help you on your way, which I’ve listed below. Keep in mind these are just the tools I’m aware of, there are probably more.

This isn’t an exhaustive list by any means and I’ll keep adding to it as I think of more. In the meantime, if you have a suggested practice please post a comment and I’ll consider adding it to the list.