Twitter has become a remarkable tool for building your community. Whether this means promoting your products and service, or mobilizing your followers to action, this has made it an ideal solution that cannot be ignored from freelancers to non-profits to enterprises. Who would have thought 140 characters can do so much?! Perhaps the short attention span attributed to fast pacing world may have contributed to the phenomenon.
Having a website is not enough anymore. You must have a blog, news, and even events to keep your visitors coming back for more. So how do you keep up with adding a Twitter to the mix? Well, hitting two birds with one stone of course (please don't throw real stones at real birds)! I am going to show how you can merge your existing processes of posting blogs, news, and events with automatically posting to Twitter! For this demonstration, I am going to use Sitefinity, but you can really apply these concepts to any content management system. Also, we will be using the Bit.ly service to shorten URL's to make better use of the 140 character limit on Twitter.
Let us start with the events module. First I must add extra profile fields to store the user's credentials for Twitter. I added these fields on the user level instead of the application level to give the flexibility of user's posting onto their own Twitter account. To add these fields to the user profile, I add the necessary fields to the web.config:
<profile defaultprovider="Sitefinity">
<providers>
<clear/>
<add name="Sitefinity" connectionStringName="DefaultConnection" applicationName="/" type="Telerik.DataAccess.AspnetProviders.TelerikProfileProvider, Telerik.DataAccess"/>
</providers>
<properties>
<add name="FirstName"/>
<add name="LastName"/>
<add name="Position"/>
<add name="Nickname"/>
<add name="Photo_Hidden"/>
<add name="Photo" defaultValue="~/Sitefinity/Common/Images/defaultavatar.gif"/>
<group name="InstantMessenger">
<add name="IM_Type"/>
<add name="IM_Value"/>
</group>
<group name="Phone">
<add name="Value"/>
<add name="Type"/>
</group>
<group name="Bitly">
<add name="Login"/>
<add name="Key"/>
</group>
<group name="Twitter">
<add name="Username"/>
<add name="Password"/>
</group>
</properties>
</profile>
Next thing we are going to do is add these profile fields to the user management screens. There are 2 files to do this in:
- ~/Sitefinity/Admin/CmsAdmin/MyProfile.aspx within the <cc1:ManageProfile ID="manageProfile"> tag
<h3> API </h3> <fieldset class="userinfo set"> <ol class="setIn"> <li> <cc2:FieldLabel ID="lblBitly_Login" runat="server" Text="Bit.ly Login" TargetID="Bitly_Login"> </cc2:FieldLabel> <asp:TextBox ID="Bitly_Login" runat="server"></asp:TextBox> </li> <li> <cc2:FieldLabel ID="lblBitly_Key" runat="server" Text="Bit.ly Key" TargetID="Bitly_Key"> </cc2:FieldLabel> <asp:TextBox ID="Bitly_Key" runat="server"></asp:TextBox> </li> <li> <cc2:FieldLabel ID="lblTwitter_Username" runat="server" Text="Twitter Username" TargetID="Twitter_Username"> </cc2:FieldLabel> <asp:TextBox ID="Twitter_Username" runat="server"></asp:TextBox> </li> <li> <cc2:FieldLabel ID="lblTwitter_Password" runat="server" Text="Twitter Password" TargetID="Twitter_Password"> </cc2:FieldLabel> <asp:TextBox ID="Twitter_Password" runat="server" TextMode="Password"></asp:TextBox> </li> </ol> </fieldset> <div class="bottom"> <div> <!-- --> </div> </div>
- In ~/Sitefinity/Admin/CmsAdmin/Users.aspx add the same code above in two places:
- <cc1:manageprofile id="manageProfile"><InsertTemplate>
- <cc1:manageprofile id="manageProfile"><EditTemplate>
This is all you need to do in Sitefinity to wire up the new user profile fields to the management screens. Fortunately, Sitefinity takes care of all the plumbing of persisting the data.
Now we will need some extra meta fields for the module items to store some status on the Twitter post. We do this by adding the meta fields in the web.config:
<cmsEngine defaultProvider="Generic_Content">
<metaFields>
<add key="Events.TwitterPost" valueType="Boolean" visible="True" searchable="False" sortable="False" defaultValue=""/>
<add key="Events.TwitterPostDate" valueType="DateTime" visible="True" searchable="True" sortable="True" defaultValue=""/>
<add key="Events.ShortUrl" valueType="ShortText" visible="True" searchable="True" sortable="True" defaultValue=""/>
</metaFields>
</cmsEngine>
We must now adjust the control templates for the event management screens to give the user the option to posting to Twitter. There are two files that need some markup:
- In ~/Sitefinity/Admin/ControlTemplates/Events/EventsItemNew.ascx within the <sfgcn:contentmetafields id="MetaFields"> tag
<h3> <asp:Literal ID="Literal16" runat="server" Text="<%$Resources:AdditionalInfo %>" /></h3> <fieldset class="set"> <div class="setIn"> <ol> <li> <asp:Label ID="Label6" runat="server" Text='<%$Resources:TwitterPost %>' AssociatedControlID="TwitterPost"></asp:Label> <asp:CheckBox ID="TwitterPost" runat="server"></asp:CheckBox> <p class="example"> <asp:Literal ID="Literal18" runat="server" Text="<%$Resources:TwitterPostNote %>" /> <%$Resources:LastPosted %>: <asp:Literal ID="TwitterPostDate" runat="server"></asp:Literal></p> </li> <li> <asp:Label ID="Label14" runat="server" Text='<%$Resources:TwitterHash %>' AssociatedControlID="TwitterHash"></asp:Label> <asp:TextBox ID="TwitterHash" runat="server"></asp:TextBox> <p class="example"> <asp:Literal ID="Literal30" runat="server" Text="<%$Resources:TwitterHashNote %>" /></p> </li> <li> <asp:Label ID="Label13" runat="server" Text='<%$Resources:ShortUrl %>' AssociatedControlID="ShortUrl"></asp:Label> <asp:TextBox ID="ShortUrl" runat="server"></asp:TextBox> <p class="example"> <asp:Literal ID="Literal29" runat="server" Text="<%$Resources:ShortUrlNote %>" /></p> </li> </ol> </div> </fieldset> <div class="bottom"> <div> </div> </div>
At this point, we have taken care of the interface. Here's what the screens should look like after your added these modifications:

Interface for creating items within a module. Also added here is the placeholder for a time the Twitter status was posted and hash tags that can be used for for the Twitter status for easy searching.
Now we are ready to get into some real code! We will extend the EventsItemEdit class by inheriting it. We will override the SaveContent() method to inject some code in there before it saves. What we will be doing is checking for the "Post to Twitter" flag to do the logic needed for updating Twitter.
We will be using the Yedda Twitter Library for the actual communication to Twitter and the Bit.ly API in C# for shortening URL's.
Here is the code that is overriden for EventsItemEdit.SaveContent():
public class EventsItemEdit : Telerik.Events.WebControls.Admin.EventsItemEdit
{
private const string DEFAULT_EVENT_PAGE = "~/Events/View.aspx";
protected override void InitializeControls(System.Web.UI.Control viewContainer)
{
//HANDLE PROCESSES ON EVENTS THAT INCLUDE NEWLY CREATED ITEMS.
//THIS IS NEEDED SO WE HAVE AN ID IN DB FOR THESE PROCESSES SINCE THE SAVECONTENT
//OVERRIDE EXISTS/REDIRECTS BEFORE BEING ABLE TO SEND BACK THE CREATED OBJECT
EventsManager.Executed += new EventHandler<Telerik.ExecutedEventArgs>(EventsPostSave_Executed);
}
public void EventsPostSave_Executed(object sender, Telerik.ExecutedEventArgs e)
{
if (e.CommandName == "CreateContent" || e.CommandName == "UpdateContent")
{
IContent content = ContentManager.Providers[EventsManager.DefaultContentProvider].GetContent(e.ItemID);
if (content != null)
{
//BUILD ITEM URL TO SEND TO EXTERNAL SERVICES
string eventUrl = Utility.ResolveItemUrl(DEFAULT_EVENT_PAGE, content.UrlWithExtension, true);
string shortUrl = this.BitlyHandler(content, eventUrl);
this.TwitterHandler(content, shortUrl);
}
}
}
protected string BitlyHandler(IContent content, string url)
{
if (!String.IsNullOrEmpty((string)content.GetMetaData("ShortUrl")))
{
//SHORT URL WAS DONE BEFORE
return (string)content.GetMetaData("ShortUrl");
}
else
{
//CALL SHORTEN URL SERVICE
url = Utility.ShortenUrl(url);
content.SetMetaData("ShortUrl", url);
EventsManager.Providers[EventsManager.DefaultContentProvider].SaveContent(content, false);
return url;
}
}
protected void TwitterHandler(IContent content, string url)
{
//POST TO TWITTER IF APPLICABLE
if (Convert.ToBoolean(content.GetMetaData("TwitterPost")))
{
if (Utility.UpdateTwitterStatus((string)content.GetMetaData("Title"), url, (string)content.GetMetaData("TwitterHash")))
{
//RESET FLAG IN CASE USER WOULD LIKE TO REPOST LATER
content.SetMetaData("TwitterPost", false);
//RECORD DATE OF TWITTER POST
content.SetMetaData("TwitterPostDate", DateTime.Now);
EventsManager.Providers[EventsManager.DefaultContentProvider].SaveContent(content, false);
}
}
}
}
This is the static methods used for the Twitter communication:
public static bool UpdateTwitterStatus(string content, string url, string hash)
{
return UpdateTwitterStatus(content, url, hash, false);
}
public static bool UpdateTwitterStatus(string content, string url, string hash, bool makeShortUrl)
{
//VALIDATE INPUT
if (String.IsNullOrEmpty(content))
return false;
HttpContext context = HttpContext.Current;
//CANNOT USE ProfileCommon SINCE WE ARE IN A SEPARATE LIBRARY FROM THE WEB APPLICATION
string login = (string)context.Profile.GetPropertyValue("Twitter.Username");
string password = (string)context.Profile.GetPropertyValue("Twitter.Password");
bool success = false;
if (!String.IsNullOrEmpty(login) && !String.IsNullOrEmpty(password))
{
//SHORTEN URL IF APPLICABLE
if (makeShortUrl)
{
url = ShortenUrl(url);
}
//FORMAT INPUTS
url = (!String.IsNullOrEmpty(url)) ? " " + url : String.Empty;
hash = (!String.IsNullOrEmpty(hash)) ? " " + hash : String.Empty;
//BUILD STRING ACCORDING TO TWITTER CHARACTER LIMIT
string trailer = url + hash;
int charsLeft = 140 - trailer.Length;
if (content.Length > charsLeft)
{
content = content.Substring(0, charsLeft) + trailer;
}
{
content += trailer;
}
//SEND POST TO TWITTER
ServicePointManager.Expect100Continue = false;
Twitter twit = new Twitter();
try
{
twit.Update(login, password, content, Twitter.OutputFormatType.JSON);
success = true;
}
catch (Exception ex)
{
//DO LOGGING HERE
}
}
return success;
}
This is the static method used to shorten the URL:
public static string ShortenUrl(string url)
{
//VALIDATE INPUT
if (String.IsNullOrEmpty(url))
return String.Empty;
HttpContext context = HttpContext.Current;
//CANNOT USE ProfileCommon SINCE WE ARE IN A SEPARATE LIBRARY FROM THE WEB APPLICATION
string login = (string)context.Profile.GetPropertyValue("Bitly.Login");
string apiKey = (string)context.Profile.GetPropertyValue("Bitly.Key");
string shortenedUrl = String.Empty;
if (!String.IsNullOrEmpty(login) && !String.IsNullOrEmpty(apiKey))
{
try
{
//ATTEMPT TO SHORTEN URL VIA BITLY SERVICE
shortenedUrl = API.Bit(login, apiKey, url, "Shorten");
}
catch (Exception ex)
{
//DO LOGGING HERE
}
}
return shortenedUrl;
}
This is the helper method to help resolve the content URL:
public static string ResolveItemUrl(string singleItemPage, string itemUrlWithExtension, bool resolveAsAbsoluteUrl)
{
Page page = HttpContext.Current.Handler as Page;
if (page != null)
{
singleItemPage = page.ResolveUrl(singleItemPage ?? "~/").Replace(".aspx", String.Empty) + itemUrlWithExtension;
}
if (resolveAsAbsoluteUrl)
{
singleItemPage = "http://" + page.Request.ServerVariables["SERVER_NAME"] + singleItemPage;
}
return singleItemPage;
}
Upon saving the event, the URL for the event is retrieved then shortened. We pass this into the Twitter method to do the actual status updating.
So now every time you post an event from your Sitefinity administration page, it gets sent to Twitter at the same time! Talk about efficiency. Stay tuned until next where I show how to integrate your CMS with Facebook.
HAPPY CODING!!



Awesomeness!