SaaS CMS has officially launched! Learn more now.

Peter Sunna
Nov 19, 2009
(2 votes)

As seen on Tech Forum: The Twitter channel gadget

Inspired by Twingly channels me, Mats Hellström and Rachel Goldthorpe decided to create a Twitter channel gadget for EPiServer.

The project consist of two parts:

  1. A scheduled job that imports tweets with a specific keyword into a dynamic data store
  2. A gadget that shows the imported tweets and lets the editors comment specific tweets (for other editors to read) as well as search among tweets and their comments

What is this good for? Well, you get to comment what’s being said about for example your brand on Twitter but you only need to share the comments with your loved ones.

Instead of 2000 more words, here are two screenshots:

Twitter channel in action - search 

Twitter channel in action - search and comment

The search result listing is of course updated as you type (to stress test the dynamic data store) and after lots of usability studies we decided that comments should be added by clicking the profile image. When finished typing, hit tab to save the comment.

Now we’re just pulling tweets from Twitter, but his could easily be extended to grab data from other services as well. Think big, think YQL,

The tweet importer

The tweet importer uses the Twitter search API which return Json objects. These objects are serialized into C# objects using the DataContractJsonSerializer that are later pushed into the dynamic data store. Note that we’re using the DataMember attribute to tell which properties the Json serializer should consider, but the attribute EPiServerDataMember to tell which properties that should be saved into the dynamic data store.

public class TwitterItem
    [DataMember(Name = "text", Order = 0)]
    public string Text { get; set; }
    [DataMember(Name = "to_user_id", Order = 1)]
    public int? ToUserId { get; set; }
    [DataMember(Name = "from_user", Order = 2)]
    public string FromUser { get; set; }
    [DataMember(Name = "id", Order = 3)]
    public long TweetId { get; set; }
    [DataMember(Name = "from_user_id", Order = 4)]
    public int? FromUserId { get; set; }
    [DataMember(Name = "iso_language_code", Order = 5)]
    public string IsoLanguageCode { get; set; }
    [DataMember(Name = "profile_image_url", Order = 6)]
    public string ProfileImageUrl { get; set; }
    [DataMember(Name = "created_at", Order = 7)]
    public string CreatedAt { get; set; }
    public DateTime StatusDate { get; set; }

Each TwitterItem is embedded in a ChannelItem, which will be the type our dynamic data store will contain . Remember that the dynamic data store can contain one and only one type. The ChannelItem contains the tweet, its comments and a last updated property. Since we want all public properties to go into the dynamic data store we don’t need to use the EPiServerDataContract attribute.

public class ChannelItem
    public ChannelItem()
        Comments = new List<String>();
    public ChannelItem(TwitterItem tweet)
        Comments = new List<String>();
        Tweet = tweet;
        LastUpdated = tweet.StatusDate;
    public TwitterItem Tweet { get; set; }
    public System.DateTime LastUpdated { get; set; }
    public List<String> Comments { get; set; }

We’re using the TweetId to make sure that we don’t import duplicates of a tweet, meaning that we can skip to implement the IDynamicData interface and its Id property which can be used if you’d like to assign Guid:s to your objects.

private static int AddTweetsToDynamicDataStore(List<TwitterItem> tweets)
    DynamicDataStore<ChannelItem> channelStore = 
    int importCount = 0;
    foreach (var item in tweets)
        var currentItems = (from c in channelStore where 
                                c.Tweet.TweetId == item.TweetId 
                            select c).ToList();
        if (currentItems.Count > 0)
        ChannelItem cItem = new ChannelItem(item);
    return importCount;


The gadget

The scheduled job makes sure that the Twitter channel is updated regularly with beautiful tweets on a particular subject. It’s time to show the tweets in a gadget and let editors search and make comments. It’s Mvc time, this beautiful little framework that EPiServer SiteCenter is built upon.

Let’s start by looking at the different Views we’ve got:


Takes a list of ChannelItems and renders the tweets and their comments (using the Comments View).


Takes a ChannelItem and renders its comments.


Takes a list of ChannelItems. Renders a search box and then passes the ChannelItems to the Tweets View.

With these Views we just need something that receives requests from the gadget, modifies and/or collects some data in the dynamic data store and then passes it to the right view. We’re talking about at the heart in out twitter gadget, the Controller. These are the main Actions in our Controller:


Responsible for fetching all channel items and passing them to the Index View.


Responsible for adding an incoming comment to a tweet and passing the resulting ChannelItem to the Comment View.

public ActionResult AddComment(string comment, string tweetId)
    ChannelItem item = (from c in channelStore
                         where c.Tweet.TweetId == long.Parse(tweetId)
                         select c).ToList()[0];
    // Add time and user to comment and modify last updated date on tweet
    item.LastUpdated = DateTime.Now;
    item.Comments.Add(string.Format("{0} [by {1} on {2}]", 
        comment, User.Identity.Name, item.LastUpdated.ToTwitterDate()));
    // Return all comments for tweet
    return View("Comments", item);


Responsible for fetching all ChannelItems that contains a specific keyword and then passing them to the Tweets View. Pay attention to how we’re using Count to search for a string in the comments collection.

public ActionResult GetFilteredTweets(Guid gadgetId, string searchTerm)
    var items = (from c in channelStore
                     c.Tweet.Text.Contains(searchTerm) ||
                     c.Tweet.FromUser.Contains(searchTerm) ||
                     c.Comments.Count(s => s.Contains(searchTerm)) > 0
                 orderby c.LastUpdated descending
                 select c).ToList();
    return View("Tweets", items);

Now we just need to add an event handler to the search box (fire off a request to the GetFilteredTweets Action whenever a user starts typing) as well as an event handler when adding a comment (send comment to AddComment Action). Looking at the code below you’ll see that we’re using the ajax method inside the gadgetContext. This method takes care of sending the gadget id so we don’t need to worry about that.

twitterChannel.init = function(e, gadgetContext) {
    twitterChannel.gadgetInstance = gadgetContext;
    $("#searchfield").keyup(function() {
twitterChannel.filterTweets = function(searchTerm) {
        type: "GET",
        url: twitterChannel.gadgetInstance.getActionPath(
            { action: "GetFilteredTweets" }),
        data: "searchTerm=" + searchTerm,
        contentType: "application/json; charset=utf-8",
        dataType: "html",
        success: function(result) {
twitterChannel.addComment = function(target, tweetId, comment) {
        type: "POST",
        url: twitterChannel.gadgetInstance.getActionPath({ action: "AddComment" }),
        data: { tweetId: tweetId, comment: comment },
        dataType: "html",
        success: function(result) {

Whenever a tweet is commented its last updated property is updated, making newly commented tweets appear on top.

The gadget also has a setting where the editor can select how many tweets to show. This is handled by the Configure View and the Configure and SaveSettings Actions. The settings are stored in, you guessed it, the dynamic data store.

Happy tweet commenting!



Don’t forget to also grab the Scheduled Jobs Monitor gadget by Björn Olsson.

Nov 19, 2009


Sep 21, 2010 10:32 AM

Nice work!
/ Pelle

Sep 21, 2010 10:32 AM

The download now includes the Css file as well...

Please login to comment.
Latest blogs
Optimizely SaaS CMS Concepts and Terminologies

Whether you're a new user of Optimizely CMS or a veteran who have been through the evolution of it, the SaaS CMS is bringing some new concepts and...

Patrick Lam | Jul 15, 2024

How to have a link plugin with extra link id attribute in TinyMce

Introduce Optimizely CMS Editing is using TinyMce for editing rich-text content. We need to use this control a lot in CMS site for kind of WYSWYG...

Binh Nguyen Thi | Jul 13, 2024

Create your first demo site with Optimizely SaaS/Visual Builder

Hello everyone, We are very excited about the launch of our SaaS CMS and the new Visual Builder that comes with it. Since it is the first time you'...

Patrick Lam | Jul 11, 2024

Integrate a CMP workflow step with CMS

As you might know Optimizely has an integration where you can create and edit pages in the CMS directly from the CMP. One of the benefits of this i...

Marcus Hoffmann | Jul 10, 2024

GetNextSegment with empty Remaining causing fuzzes

Optimizely CMS offers you to create partial routers. This concept allows you display content differently depending on the routed content in the URL...

David Drouin-Prince | Jul 8, 2024 | Syndicated blog

Product Listing Page - using Graph

Optimizely Graph makes it possible to query your data in an advanced way, by using GraphQL. Querying data, using facets and search phrases, is very...

Jonas Bergqvist | Jul 5, 2024