I'm Keyvan Nayyeri, a 25 years old Ph.D. student at
the Computer Science department of
the University of Texas at San Antonio.
I'm also
a Software Architect and Developer and previously held a B.Sc.
degree in Applied Mathematics.
This is my blog where I publish content about various topics specifically Programming Languages and Compilers, Software
Engineering and Programming.
As you probably know, Graffiti 1.0 Beta 1 doesn't have support for trackbacks and pingbacks. Scott had written a post regarding the exclusion of this feature and as he said trackbacks will be included in Beta 2 as an optional feature.
Even though I agree with Scott on the fact that trackbacks aren't as useful as the past but we still can't ignore them because we really need a way to show relationships between different pages on the web.
In this post I write about the necessity of having such a feature to connect two pages that are linked together and the process that I followed to create an extension for Graffiti that collects incoming links for a specific post from search engines as a replacement solution for trackbacks and pingbacks.
While trackbacks are an excellent way to connect pages on the web but there are two negative points about them:
As long as we go forward in the time, search engines have gotten better and we're seeing some professional search engines designed specifically for blogs. Some people have decided to use search engines as a replacement for trackbacks and pingbacks because they solve the abovementioned negative points to some extent:
It's over a month that I'm running on Graffiti 1.0 Beta 1 and the lack of built-in support for such a feature (as a trackback or search engine link collector) was one of the main negative points with the product for me. You know that I have a serious passion on the community and would like to show related content to my posts to let my visitors track them and get more information.
Andrew Tobin had written an extension for Graffiti that shows the links count for incoming links by fetching the data from Technorati and providing a link to Technorati search results page. But I needed something different because I wanted to show all incoming links immediately under my post content. Moreover Technorati doesn't index all links and I thought it's better to merge the results from multiple search engines to get a better result.
Thinking about the idea, I finally decided to write my own implementation for it as an extension for Graffiti.
This extension is nothing more than a custom Chalk extension that follows some simple steps to collect links from search engines.
I named this extension Community Reactions because it collects community reactions for a post. Community Reactions extension fetches data from RSS feed for search results for a specific post on Google blog search and Technorati and merges these results and removes duplicate items to have a clean list of reactions then returns this list.
Chalk extensions are easy to write (as described here) and you just need to create a class that has a Chalk attribute and also has public methods that return string value of the HTML code that should be displayed for an extension.
I'll describe the process of implementation in a moment. I'm going to use this extension on my site for a while to see how it works and will decide later if I can keep it here or not.
The implementation of Community Reactions extension was a straightforward process (at least it was easier than writing this post and took less time than it!). The first step was to create a new class library project and add a public class to it then adding a Chalk attribute to the class.
Before moving on to the actual implementation, I have to say that I created a simple helper class and named it Reaction that represents a community reaction by three properties for its title, URL and publish date and has a simple code as you see:
using System;
using System.Collections.Generic;
using System.Text;
namespace Graffiti.Reactions
{
internal class Reaction
{
public string Title { get; set; }
public string Url { get; set; }
public DateTime PubDate { get; set; }
public Reaction()
{
}
public Reaction(string title, string url, DateTime pubDate)
{
this.Title = title;
this.Url = url;
this.PubDate = pubDate;
}
}
}
The next step was to implement the main logic in the Reactions class. There is a single public method called GetReactions which gets a Graffiti.Core.Post object and a string value of the text that should be displayed if there wasn't any incoming link. This method returns the string value of the HTML code that should be shown.
public string GetReactions(Post post, string notFoundText)
{
List<Reaction> initialReactions = new List<Reaction>();
try
{
// Add Google results
initialReactions.AddRange(GetGoogleReactions(post));
// Add Technorati results
initialReactions.AddRange(GetTechnorariReactions(post));
}
catch (Exception ex)
{
Log.Error("Error in Community Reactions Extension", ex.Source);
}
// Remove duplicate items and sort final items by date
List<Reaction> finalReactions = new List<Reaction>();
finalReactions = FilterItems(initialReactions);
// Generate the output and return it
return GetHtml(finalReactions, notFoundText);
}
As you see there are a few steps to fetch data.
The first step is to call appropriate methods to get a list of Reaction items for incoming links from Google and Technorati. GetGoogleReactions and GetTechnoratiReactions methods do this and both have similar implementations.
private List<Reaction> GetGoogleReactions(Post post)
{
string rssUrl = string.Format(googleRssUrl, new Macros().FullUrl(post.Url));
List<Reaction> results = new List<Reaction>();
try
{
Feed feed = new Feed();
feed = FeedManager.GetFeed(rssUrl);
if (feed == null)
{
feed.Url = rssUrl;
feed.RequestInterval = feedUpdateInterval;
FeedManager.AddFeed(feed);
FeedManager.UpdateFeed(feed);
feed = FeedManager.GetFeed(rssUrl);
}
RssChannel channel = feed.Document.Channel;
foreach (RssItem item in channel.Items)
{
Reaction reaction = new Reaction(item.Title, item.Link,
DateTime.Parse(item.PubDate));
results.Add(reaction);
}
}
catch (Exception ex)
{
Log.Error("Error in Community Reactions Extension", ex.Source);
}
return results;
}
private List<Reaction> GetTechnorariReactions(Post post)
{
string rssUrl = string.Format(technoratiRssUrl, new Macros().FullUrl(post.Url));
List<Reaction> results = new List<Reaction>();
try
{
Feed feed = new Feed();
feed = FeedManager.GetFeed(rssUrl);
if (feed == null)
{
feed.Url = rssUrl;
feed.RequestInterval = feedUpdateInterval;
FeedManager.AddFeed(feed);
FeedManager.UpdateFeed(feed);
feed = FeedManager.GetFeed(rssUrl);
}
RssChannel channel = feed.Document.Channel;
foreach (RssItem item in channel.Items)
{
Reaction reaction = new Reaction(item.Title, item.Link,
DateTime.Parse(item.PubDate));
results.Add(reaction);
}
}
catch (Exception ex)
{
Log.Error("Error in Community Reactions Extension", ex.Source);
}
return results;
}
Graffiti applies RSS Toolkit component in order to provide a built-in feature to fetch data from feeds. It adds a list of feeds to its database and updates their data on a regular basis. The code is easy to read and understand but I have to note that I used FeedManager.GetFeed method to get an instance of the Feed object. If there wasn't any feed available then I had to add it to the system myself and then update its data. After all these steps I wrote a simple loop to iterate through feed items and add their data to results.
The next step was to filter items by removing duplicate links and sorting them by date. This was the job of the FilterItems method by getting the help of the ContainsUrl function.
private List<Reaction> FilterItems(List<Reaction> initialReactions)
{
List<Reaction> finalResults = new List<Reaction>();
foreach (Reaction item in initialReactions)
{
if (!ContainsUrl(finalResults, item.Url))
finalResults.Add(item);
}
finalResults.Sort(delegate(Reaction reaction1, Reaction reaction2)
{
return Comparer<DateTime>.Default.Compare(reaction2.PubDate, reaction1.PubDate);
});
return finalResults;
}
private bool ContainsUrl(List<Reaction> reactions, string url)
{
foreach (Reaction item in reactions)
{
if (item.Url == url)
return true;
}
return false;
}
And obviously the last step was to generate the output as a list which is the job of the GetHtml function.
private string GetHtml(List<Reaction> finalReactions, string notFoundText)
{
if (finalReactions.Count > 0)
{
StringBuilder generator = new StringBuilder("<ul>");
foreach (Reaction reaction in finalReactions)
{
generator.AppendFormat("<li><a href='{0}' title='{1}'>{2}</a></li>",
reaction.Url, reaction.PubDate.ToLongDateString(), reaction.Title);
}
generator.Append("</ul>");
return generator.ToString();
}
else
{
return notFoundText;
}
}
This was all the implementation. however there are a few points to mention. There were three constant values for the RSS feed URLs and the request interval to update feed data that you can modify for your own.
As you saw it's very easy to add other search engines or replace these current engines but I think Google and Technorati would be enough to collect all community reactions. Using more search engines can provide an overhead on the server because it has to request data for each post and each engine.
You can download the binary and source code for Community Reactions extension from here.
Using a Chalk extension is as easy as using the Chalk, itself. You just need to deploy the binary file (Graffiti.Reactions.dll) to bin folder of your site and modify your theme (most likely the post.view file) to use it.
After that you to add the appropriate header markup for the section and a line of code like what you see here:
$reactions.GetReactions($post, "Could not find any community reaction.")
The result is very nice!

As the final note please take care about your database while using this extension. Since it does store the feed data in database and updates it on a regular basis you may end up with larger databases. If the database size and its performance is a concern for you then try to test it before deploying to the production. The more posts you have, the more storage size that you need to use and more requests from your server to search engines! If you want to use other storage systems, you can write alternative implementation to store feed data such as file system but this isn't an integrated implementation for Graffiti, though!
Leave a Comment