The building of Tweet Me

15 Sep 2009 Ralph Spandl 0 comments

Ralph Spandl pulls apart the inner workings of Tweet Me - a custom Kentico web part allowing for the sharing of blog posts via Twitter - sharing the thought processes behind the development.

The idea of developing a Kentico web part that would automatically append a Twitter update link came one raining morning. Until that point I had not planned to write a web part and I was not a big fan of Twitter.

I’d had one of my colleagues Alice O’Brien telling me that I should be using Twitter, for weeks, but I could not imagine spending any time tweeting, and I had no idea what to tweet about.

In fact, the only thing I could think of using Twitter for was to maybe spread the word about my blog posts. An idea was born. I needed a simple, easy-to-use button that would create tweets of my blog posts, usable by my readers as well - a simple one click web part.

Google confirmed that this could be achieved in one morning. The simplest solution I found used JavaScript. Turning this into a server side thing was easy enough to do - creating a twitter-status-update-link using the title information and the URL of the current page.

Ironing out the kinks

On the way to my second cup of coffee, I bumped into the first problem pretty much all my post URLs would eat up all the available 140 characters for a tweet. I had to look into URL shortener APIs. The first that came to mind was obviously, “TinyURL” (what a genius name) and surprisingly, ready to use C# code was already available.

But while researching for a solution to the TinyURL part, I stumbled upon a blog post that discussed the benefits of a shortener named bit.ly, wich also provided click statistics of the shortened URLs. This was a solid argument, and convinced me to include it in my web part. Unfortunately, because bit.ly requires a developer API, I still needed to integrate the TinyURL shortener as a plug and play alternative.

Bit.ly was quite a bit trickier to implement. The API returns the result either in JSON or XML format, both of which require extra parsing. The code from the Emad Ibrahim somehow didn`t work for me, but the Yahoo developer network offered a very nice tutorial on how to use XML returned from a web service.

like AddThis, I decided to add the option of configuring Google Analytics campaign tags for the shortened URL. On top of this, as a designer, I of course wanted complete control over the look of the button, and so I decided to allow for either a text link or an image button.I’d only just hit lunch time, and with the impression that my creation wouldn’t really offer any benefits over a solution

The thinking was done, most of the code was already written (and by someone else in most cases). I just needed to put the pieces together.

Building a Kentico web part

Building re-usable web parts in Kentico is not as complicated as it appears.

As always, it’s worth planning before getting started, and in this case the planning involved deciding what the web part should do, and more importantly which properties should be made available in order to keep the web part flexible.

To save time covering the basics, I recommend reading “Developing Web Parts” in the Kentico Developers Guide http://bit.ly/EgtD9.

The web part properties

If you want other developers to use your web part, you should document the web part - through the web part field captions, and the descriptions in both the properties tab and the documentation tab - and then submit it to the Kentico Marketplace.

When developing a web part, you usually start with the code behind, but to get an overview of all functions, I prefer to cover the web part properties first.

Each Kentico web part automatically has a set of properties, like visibility and HTML envelope included. There is no need to configure or code these properties which saves a lot of work.

Based on the concept of the Tweet Me web part I configured following properties :

  • Text to be tweeted (text box)
  • Image or text link (drop-down list)
  • Text link (used if text link was selected above)
  • Image source for image links
  • URL shortener (Drop-down list with choice between TinyURL or bit.ly)
  • bit.ly API login (for use with bit.ly shortener)
  • bitly API key (for use with bit.ly shortener)
  • Five text fields for Google Analytics campaign tags

For the status update text, or the text of the tweet, I took
advantage of the built in macros of version 4.0, deciding to use the page title.

To make your configuration window nice and clean, it is wort using the category separators which are available.

This macro or any other macro can easily be created with the macro pull-down menus. If you are wondering how to access the macro window, try to click on the tiny little black arrow to the left of each property field.

When using the page title macro, the page title for the page must be defined. The inherit checkbox will not do the job, and unfortunately the “Page Title Generator” web part from by Elijah Taylor / Aceoft Studios) won’t work in this case either. The macro that I use is:
{%cmscontext.currentdocument. documentpagetitle%}

Defining the properties

For Tweet Me, all properties used in the web part configuration box had to be defined.

public partial class CMSWebParts_r42_TweetMe: CMSAbstractWebPart
{
	#region “Public properties”
		///  		/// Text to tweet 		/// 
		public string TweetText
		{
			get
			{
				return ValidationHelper.GetString(this.GetValue(“TweetText”), “”);
			}
			set
			{
				this.SetValue(“TweetText”, value);
			}
		}

The TinyURL string queries the TinyURL web service which returns the shortened URL.

	private string TinyUrl(string Url)
	{
		try
		{
			WebRequest request = WebRequest.Create(“http://tinyurl.com/api-create.php?url=” + Url);
			WebResponse res = request.GetResponse();
			string text;
			using (StreamReader reader = new StreamReader(res.GetResponseStream()))
			{
				text = reader.ReadToEnd();
			}
			return text;
		}
		catch (Exception)
		{
			return Url;
		}
	}

The bit.ly string queries the bit.ly web service which returns the requested XML. First, the web part searches to see if the status code is “OK”. If it`s not then TinyURL is used to shorten the URL. Otherwise it returns the value of the node with the ID shortUrl, which is the shortened URL.

The only other thing to note here is that the bit.ly history is turned on. This means that if you log onto your bit.ly account, you can see which URLs have been shortened with your API key and you will also have access to the statistics of each. By default, history is turned off when shortening URLs through the API.

	private string BitlyUrl(string Url)
	{
		string requestUrl;
		
		try 
		{
			
			requestUrl = string.Format(“http://api.bit.ly/shorten?format=xml&version=2.0.1&history=1&longUrl={0}&login={1}&apiKey={2}”, HttpUtility.UrlEncode(Url), BitlyAPILogin, BitlyAPIKey);
			
			XmlDocument doc = new XmlDocument();  
			doc.Load(requestUrl);  
			
			XmlNodeList nodes = doc.GetElementsByTagName(“statusCode”); 
			foreach(XmlNode node in nodes)  
			{  
				statusCode += node.InnerText;  
			} 
			
			if (statusCode == “OK”) {
			
				XmlNodeList nodes_2 = doc.GetElementsByTagName(“shortUrl”);
				foreach(XmlNode node in nodes_2)  
				{  
					shortURL += node.InnerText;  
				} 
				return shortURL;	
			} else {
				// Fall back to TinyURL if Bit.ly fails (no API key etc)
				Url = TinyUrl(Url);
				return Url;
			}
		}
		
		catch(Exception e)
		{
			// Fall back to TinyURL if Bit.ly fails (no API key etc)
			Url = TinyUrl(Url);
			return Url;
		}
	}

I have to admit that I am not too proud of the Google Analytics String. It is a bit cumbersome, but it works.
The code loops through all query strings in the URL and keeps all but the one for Google Analytics, because we want to add our own Google Analytics code to it, if they have been defined in the web part.

	private string createGAString()
	{
		try
		{
			string GAString = “?”;

			// Typical GA query string
			//	?utm_source=source
			//	&utm_medium=medium
			//	&utm_term=term
			//	&utm_content=content
			//	&utm_campaign=name

			// Get querystring collection and add all querystrings except GA and aliaspath
			
			HttpRequest q = Request;
			NameValueCollection collection  = q.QueryString;
			
			foreach (string key in collection.AllKeys) // <-- No duplicates returned.
			{
				switch (key)
				{
					case “utm_source”:
						break;
					case “utm_medium”:
						break;
					case “utm_campaign”:
						break;
					case “utm_term”:
						break;
					case “utm_content”:
						break;
					case “aliaspath”:
						break;
					default:
						GAString += key + “=” + collection[key] + “&”;
						break;
				}	
			}

			// Now add own GA querystring
				
			if (GA_CampaignSource.Length > 0)
				{ GAString += “utm_source=” + GA_CampaignSource + “&”; }
			if (GA_CampaignMedium.Length > 0)
				{ GAString += “utm_medium=” + GA_CampaignMedium + “&”; }
			if (GA_CampaignName.Length > 0)
				{ GAString += “utm_campaign=” + GA_CampaignName + “&”; }
			if (GA_CampaignTerm.Length > 0)
				{ GAString += “utm_term=” + GA_CampaignTerm + “&”; }
			if (GA_CampaignContent.Length > 0)
				{ GAString += “utm_content=” + GA_CampaignContent + “&”; }
			
			if (GAString.Length == 1) {
				return “”;
			} else {
				// TRIM TRAILING AMPERSAMP
				GAString = GAString.Substring(0, GAString.Length - 1);
				return GAString;
			}
		}
		catch (Exception)
		{
			return “”;
		}
	}

Tying the pieces together

Once these pieces had been built, I had to bring all the parts together.

First I had to stop the web part from being executed, when the web part is not actually visible.

This is not done automatically by Kentico and if this line is not included, the web part will query the web service, even if it is not rendered. For example, if you were to include the web part in a template, configuring it to be shown only for blog posts, without this code the web part would be processed for ALL pages using this template, regardless of whether or not they are blog posts.
		// Don’t process if web part is set to be not show on doc type 
		if (StopProcessing) return;

Next, I had to get rid of the query string and create a new one:

		// Trim all querystrings - we will add on when we create the GA querystring
		string[] urlArray = new string[2];
		char[] splitter  = {‘?’};
		urlArray = currentURL.Split(splitter);		
			
		string GAString = createGAString();
		currentURL = urlArray[0] + GAString;

Then I used one of the two web services to create the shortened URL, unless I am in the CMS Desk, in which case bit.ly would create shortened URLs of your CMS Desk pages.

		// Don’t create shortURL in preview mode
		if (ViewMode != CMS.PortalEngine.ViewModeEnum.LiveSite) {
			tinyURL = “(No URL in preview mode.)”;
		} else {
			// What service to use
			if (TinyURLService == 2) {
				tinyURL = TinyUrl(currentURL);
			} else if (TinyURLService == 1) {
				tinyURL = BitlyUrl(currentURL);
			}	
		}

Then I made sure that the total message is not longer than 140 characters including the shortened URL.

		// Make sure complete message is not longer than 140 characters
		int MAX_CHAR_ALLOWED = 140;
		int TweetTextLength	= tinyURL.Length + TweetText.Length + 1;
		// 1 is for additional space between text and URL

		if (TweetTextLength > MAX_CHAR_ALLOWED) {
			TrimCharacter = TweetTextLength - MAX_CHAR_ALLOWED;
			CharLength	= TweetText.Length - TrimCharacter - 3;
			TweetText = TweetText.Substring(0, CharLength) + “...”;
		}

Finally I created the hyperlink. Either an image or a text link, make sure that the page opens in a new window.

		if (ImageOrTextLink == 1){
			// Text link
			tweetMeLink.Text = TextLink;	
			
		} else if (ImageOrTextLink == 2) {
			// IMAGE LINK
			tweetMeLink.ImageUrl = ImgSource;
		}
		
		tweetMeLink.Target = “_blank”;
		tweetMeLink.NavigateUrl = “http://twitter.com/home?status=” + TweetText + “ “ + tinyURL;

The last part was the Web User Control - it contains only the hyperlink. The CSS class and the IDs allow the html to be styled properly.


<%@ Control Language=”C#” AutoEventWireup=”true” CodeFile=”~\CMSWebParts\r42\TweetMe.ascx.cs” Inherits=”CMSWebParts_r42_TweetMe” %>

<div id=”tweetMe”>
	<asp:HyperLink ID=”tweetMeLink” runat=”server” CssClass=”tweetMeLink” />
</div>

The bit.ly effect

Nobody likes bugs. But finding and fixing bugs can help you to discover amazing things. I had such a bug, when I developed the Tweet Me web part for Kentico CMS.

The one I came across was that I had forgotten to remove any existing Google Analytics campaign tags before adding new ones. The result of this was that if a user landed via the shortened URL on my site, the new, un-shortened URL had all of the campaign parameters included twice. In practice this should make no difference, except that it would create long URLs that would only get longer as users keep the snowball rolling.

Analytics check

Two hours after implemented Tweet Me on my blog though, Google Analytics indicated that I’d had over 200 visits.

I checked the bit.ly history and found that bit.ly had shortened the same page many, many times, even though this was not supposed to happen.

This was how I discovered that each shortened page had a very long query string.

I looked into my Google Analytics statistics and found out that the visits to my site were from Linux systems, and that all had a network location of amazon.com. This triggered something, and I remembered reading somewhere that bit.ly stores a cached copy of every page who’s URL is shortened.

Digging a little, I found out that these thumbnails are stored on Amazon’s S3 storage and the processing is done on Amazon Elastic Compute Cloud (Amazon EC2).

Thumbnail creation

Now it began to make sense. Amazon alone was responsible for the traffic. Every time the service visited my page to create a thumbnail a new URL (same base link but different query string) was created. This led to another thumbnail, leading the service to visit the page over and over again.

The bug is now fixed, but don’t be surprised if with Tweet Me installed on your site and bit.ly as the shortener, you get quite a few visits from Amazon.

A rewarding experience

And that’s it. Tweet Me is complete.

As with almost any software development, it happens that every now and then you run into bugs that double your estimated development time, but outside of this it is actually quite easy to built Kentico web parts.

Just don’t expect as I did, to finish it off in one morning.

Tweet Me download

Trackback URL: http://www.kenticodeveloper.com/trackback/c8525e7a-45d6-4aec-ad14-469eeb701e62/The-building-of-Tweet-Me.aspx

Comments
Blog post currently doesn't have any comments.
Leave comment Subscribe
Name:

E-mail:

Your URL:
Comments:

Enter security code:
 Security code