Knowledge is power. We love to share it.

News related to Mono products, services and latest developments in our community.

khorvat

Integrating Twitter with an ASP.NET site

04/19/2011Categories: MonoX
Twitter has become the most popular social networking service for writing and sharing short text messages. As you already know, these messages are referred to as tweets and are limited to 140 characters. Users can leave tweets and follow other users directly or by using the Twitter API. Due to popular demand we decided to introduce such functionality into MonoX and allow our users to easily display tweets on their sites. We initially considered using the existing Twitter integration tools such as LINQ to Twitter or Topsy's Otter Twitter API, but decided that we don't need to use any of the third party tools to develop a simple tweet feed Web part.
 
This article will show you how to build a Twitter module that allows you to display and store your tweets in MonoX.

MonoX Twitter module leverages the following MonoX functionality:

  • Web part functionality (to allow dynamic module instantiation)
  • MonoX Caching (to optimize the process of tweet retrieval and parsing)
  • MonoX Repository layer (to store / backups your tweets into the underlying data storage)

To get started we need to open a new project (ASP.NET Web Application project is the preferable project type) and add the initial Web user control.

The user control mark-up is simple because we only need to display a list of tweets:

<asp:Panel ID="pnlContainer" runat="server" CssClass="tweet-list">
<asp:ListView ID="lvItems" runat="server">
    <LayoutTemplate>
        <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
    </LayoutTemplate>
    <ItemTemplate></ItemTemplate>
</asp:ListView>
<mono:Pager runat="server" ID="pager" PageSize="10" NumericButtonCount="5" AllowCustomPaging="true" AutoPaging="false">
    <PagerTemplate></PagerTemplate>
</mono:Pager>
 
</asp:Panel>

Our user control should inherit from the BasePagedPart so that we can automatically inherit all the functionality that is built-in into MonoX Web parts.
public partial class TwitterFeedModule : BasePagedPart
{
...
}

To make our Twitter module flexible enough I like to expose a few properties that will allow end users to setup and customize the module output.

#region Properties
private Guid _listId = Guid.Empty;
/// <summary>
/// Id of the list that is displayed in this module.
/// </summary>
[WebBrowsable(true), Personalizable(true), WebEditor(typeof(ListEditorPart))]
[WebDescription("List to display")]
[WebDisplayName("List to display")]
public Guid ListId
{
    get { return _listId; }
    set { _listId = value; }
}
 
private string _listName = string.Empty;
/// <summary>
/// Name of the list that is displayed in this module.
/// </summary>
public string ListName
{
    get { return _listName; }
    set { _listName = value; }
}
 
/// <summary>
/// Pager page size.
/// </summary>
[WebBrowsable(true), Personalizable(true)]
[WebDescription("Pager page size")]
[WebDisplayName("Pager page size")]
public int PageSize
{
    get { return pager.PageSize; }
    set { pager.PageSize = value; }
}
 
private bool _hideIfEmpty = false;
/// <summary>
/// Hide part if it doesn't contain any data.
/// </summary>
[WebBrowsable(true), Personalizable(true)]
[WebDescription("Hide if empty")]
[WebDisplayName("Hide if empty")]
public bool HideIfEmpty
{
    get { return _hideIfEmpty; }
    set { _hideIfEmpty = value; }
}
 
private int _interval = 10;
/// <summary>
/// Gets or sets the refresh interval.
/// <para>
/// Default refresh interval is 10 minutes.
/// </para>
/// </summary>
[WebBrowsable(true), Personalizable(true)]
[WebDescription("Tweet Refresh Interval")]
[WebDisplayName("Tweet Refresh Interval")]
public int Interval
{
    get
    {
        return _interval;
    }
    set
    {
        _interval = value;
        this.CacheDuration = value * 60;
    }
}
 
/// <summary>
/// Gets or sets Tweet count.
/// </summary>
[WebBrowsable(true), Personalizable(true)]
[WebDescription("Tweet Display Count")]
[WebDisplayName("Tweet Display Count")]
public Int32? TweetsCount { get; set; }
 
/// <summary>
/// Gets or sets Twitter profile name.
/// </summary>
[WebBrowsable(true), Personalizable(true)]
[WebDescription("Twitter Profile Name")]
[WebDisplayName("Twitter Profile Name")]
public string ProfileName { get; set; }
 
 
#endregion


You may notice a few properties that are used to optimize the retrieval process, so that our Web part doesn't have to download a list of tweets on every request. The end user can choose the list of feeds to be shown by using the ListId Guid property that uses a specialized ListEditorPart and is intended to be used with the MonoX personalization system and is displayed in the administrative toolbar. The alternative way to set the list is to use the ListName string property - you would probably want to use it from the code markup.
 
To avoid the unnecessary network traffic and slow page loads, we need to optimize the retrieval process by following the steps described below:
  • Retrieve the tweet RSS feed:

/// <summary>
/// Loads feed from the specified URL.
/// </summary>
/// <param name="url">URL of the feed.</param>
/// <returns>Syndication feed.</returns>
public static SyndicationFeed LoadFrom(String url)
{
    if (String.IsNullOrEmpty(url))
        throw new ArgumentNullException();
 
    SyndicationFeed feed = null;
    using (XmlTextReader r = new XmlTextReader(url))
    {
        feed = SyndicationFeed.Load(r);
    }
    return feed;
}


  • Store the retrieved data into cache and sync the retrieved data with the stored tweets:

MonoXCacheManager cacheManager = MonoXCacheManager.GetInstance(TweetsCacheKey, this.CacheDuration);
 
 KeyValuePair<SyndicationFeed, int> bindContainer = cacheManager.Get<KeyValuePair<SyndicationFeed, int>>(ProfileName, TweetsCount);
 if (bindContainer.Value == 0)
 {
     try
     {
         TweetsCount = TweetsCount.HasValue ? TweetsCount : 10;
         var url = string.Format("http://api.twitter.com/1/statuses/user_timeline.rss?screen_name={0}&count={1}", ProfileName, TweetsCount);
         SyndicationFeed feed = LoadFrom(url);
         bindContainer = new KeyValuePair<SyndicationFeed, int>(feed, feed.Items.Count());
         cacheManager.Store(bindContainer, ProfileName, TweetsCount);
     }
     catch (Exception ex)
     {
         log.Error(ex);
     }
 
     try
     {
         if (Page.User.Identity.IsAuthenticated)
         {
             //Save the Tweets to the DB
             Guid listId = Guid.Empty;
             if (!Guid.Empty.Equals(ListId))
                 listId = ListId;
             else if (!String.IsNullOrEmpty(ListName))
                 listId = BaseMonoXRepository.GetInstance().GetFieldValue<Guid>(ListFields.Title, ListName, ListFields.Id);
 
             if (Guid.Empty.Equals(listId) && !String.IsNullOrEmpty(ListName))
             {
                 //Create a List
                 ListEntity list = ListRepository.GetInstance().CreateNewList();
                 list.Title = ListName;
                 list.UserId = SecurityUtility.GetUserId();
                 list.ListType = 0;
                 ListRepository.GetInstance().SaveEntity(list, true);
                 listId = list.Id;
             }
 
             if (!Guid.Empty.Equals(listId))
             {
                 foreach (var item in bindContainer.Key.Items)
                 {
                     Guid urlId = BaseMonoXRepository.GetInstance().GetFieldValue<Guid>(ListItemLocalizationFields.ItemUrl, item.Id, ListItemLocalizationFields.Id);
                     if (!Guid.Empty.Equals(urlId)) break; //Suppose that we have imported upcoming tweets
 
                     ListItemEntity listItem = ListRepository.GetInstance().CreateNewListItem(listId);
                     listItem.DateCreated = Convert.ToDateTime(item.PublishDate.ToString());
                     listItem.ItemTitle = HtmlFormatTweet(item.Title.Text);
                     listItem.ItemUrl = item.Id;
                     ListRepository.GetInstance().SaveEntity(listItem);
                 }
             }
         }
     }
     catch (Exception ex)
     {
         log.Error(ex);
     }
 }
 
 //Note: We need to perform the in-memory paging
 List<SyndicationItem> items = bindContainer.Key.Items.Skip(pager.CurrentPageIndex * pager.PageSize).Take(pager.PageSize).ToList();

  • Parse the retrieved tweets to get the formatted output:

/// <summary>
/// Format the Tweet and prepare it for Html rendering.
/// </summary>
/// <param name="item">Tweet content</param>
/// <returns>Html formatted Tweet</returns>
protected virtual string HtmlFormatTweet(string item)
{
    string newPost = item.Replace(String.Format("{0}:", ProfileName), String.Empty);
 
    List<KeyValuePair<string, string>> regExRules = new List<KeyValuePair<string, string>>();
    regExRules.Add(new KeyValuePair<string, string>(@"(http:\/\/([\w.]+\/?)\S*)", "<a href=\"$1\" target=\"_blank\">$1</a>"));
    regExRules.Add(new KeyValuePair<string, string>("(@\\w+)", "<a href=\"http://twitter.com/$1\" target=\"_blank\">$1</a> "));
    regExRules.Add(new KeyValuePair<string, string>("(#)(\\w+)", "<a href=\"http://search.twitter.com/search?q=$2\" target=\"_blank\">$1$2</a>"));
 
    foreach (var regExRule in regExRules)
    {
        Regex urlregex = new Regex(regExRule.Key, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        newPost = urlregex.Replace(newPost, regExRule.Value);
    }
 
    return newPost;
}

You can find the full source code for the MonoX Twitter Web part attached to this article. It utilizes a few standard MonoX techniques such as data paging, Web Part templating and caching. More information on these topics will be delivered in our upcoming articles.
To execute the attached sample you need to extract the source code to the root folder of the MonoX package (MonoX Demo), open the Twitter feed solution, build the solution, right click on the "TwitterFeedDefault.aspx" and choose "View in Browser".
Rated 3.75, 4 vote(s). 
By Peterg
Very nice thanks Khorvat, im sure loads of users will make use of this, we certainly will
khorvat
Thanks Peter, I hope this gives community a perspective what MonoX can do and it's a cool thing to have your tweet feed on your Web site profile :)
shawndg
By shawndg
does this still work after twitter changes ?
khorvat
Hi,

yes it is working because it is based on the Twitter RSS. Did you try to implement the Module ?
DavidGarcia
Great Article, but actually it isn't working for me its just showing me the pages but no tweets :(
khorvat
Hi,

this is strange, can you please move this discussion to public support forum and explain a bit more. I can't understand what you mean by showing only pages ?

Regards
perfect.... its works fine.