Last week I spent some time working on our Automated Build / Continuous Integration
infrastructure. We have automated email messaging, but that's somewhat onerous for
developers who are only peripherally involved on a project - you get a mountain of
messages, most of which are not related to you. Using Twitter is an alternative solution
for this - messages will slip past, but users can ignore them if they are not interested,
and there is no required action - i.e. deleting the messages from their inboxes.
So, I simply setup a "build" twitter account, and those staff who are interested can
follow that user. Every time a build occurs, a tweet is sent out that contains the
basic information - Project Name, build status, build number, and a link to the build
report on a secured server. Nice and neat.
Creating an MSBuild Task
Creating a simple task like this is really easy - from start to finish, including
this post, it took about 2 hours. Here's the code for the actual task:
using System; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace ArcDeveloper.Tasks
{ public class TweetTask : Task { public
override bool Execute() { bool val = false; try { if (this.ReportUrl
!= "") { string shortUrl
= Twitter.ShortenUrl(this.ReportUrl); this.Update
= this.Update + " Report:
" + shortUrl; } val = Twitter.SendUpdate(this.Username, this.Password, this.Update);
} catch (Exception ex)
{ Log.LogError(ex.Message); } return val; } public
string Username { get; set;
} public string Password { get; set;
} public string Update { get; set;
} public string ReportUrl { get; set;
} } }
As you can see, the real work is done by the Twitter class. Why separate this out?
Unit testing of course! But that's not the point of this post... moving on... The
Twitter class is very stripped down it does 2 things - posts updates to twitter, and
shortens URLs using is.gd.
For the post, it creates a WebRequest, adds the Basic Authentication headers, and
posts the "update" to http://twitter.com/statuses/update.xml (the
.xml indicates that I want xml as the response format). It then does a super simple
inspection of the response to determine if the tweet succeeded. The one wacky thing
I ran into was a 417 Expectation Failure. Phil Haak has a longer
explanation of this issue, but the gist of it is that by default the System.Net.WebRequest
does not sent the POST data initially - it asks the server to send a "continue" response,
and then it sends the POST data. Unfortunately not all web servers know what to do
with this. Anyhow, the solution is set ServicePointManager.Expect100Continue = false.
Here's the code...
public static bool SendUpdate(string username, string password, string update)
{ bool status = false; HttpWebRequest webRequest
= (HttpWebRequest)WebRequest.Create("http://twitter.com/statuses/update.xml"); //ServicePointManager
is required here b/c of issue with how the Expect-Continue header is handled by twitter
//more: http://haacked.com/archive/2004/05/15/http-web-request-expect-100-continue.aspx ServicePointManager.Expect100Continue
= false; //Prepare the
Authentication parameters string auth = username
+ ":" + password; byte[]
binaryData = Encoding.UTF8.GetBytes(auth); auth
= Convert.ToBase64String(binaryData); auth = "Basic
" + auth; //This specifies it to use Basic Authentication webRequest.Headers.Add("AUTHORIZATION",
auth); webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST"; string parameters
= "status=" + HttpUtility.UrlEncode(update); byte[]
bytes = Encoding.ASCII.GetBytes(parameters); Stream os
= null; try { //
send the Post webRequest.ContentLength = bytes.Length; //Count
bytes to send os = webRequest.GetRequestStream(); os.Write(bytes, 0, bytes.Length);
} catch (WebException ex)
{ throw ex; } finally { if (os
!= null) os.Close(); } WebResponse webResponse
= webRequest.GetResponse(); System.Xml.XmlDocument doc
= new System.Xml.XmlDocument();
doc.Load(webResponse.GetResponseStream()); // sr.ReadToEnd().Trim();
//Inspect the doc to see what we got... if (doc.InnerXml.Contains("created"))
{ status = true; } else {
status= false; } return status;
}
As you can see, the exception handling is pretty lean, but it does what's needed.
To use it in a build task, use the UsingTask element as shown in the example.build
file...
<
Project
xmlns
="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="TweetTask" AssemblyFile = ".\bin\Debug\ArcDeveloper.Tasks.dll" />
<Target Name="Test">
<TweetTask Username="twitteracct" Password="secretpwd" Update="Build
Message" ReportUrl=http://foo.com/build/092/>
</Target>
</Project>
When used in a build you see something like this in Twitter...
This code is available in the ArcDeveloper repository at Assembla.com - I will be
updating this as we use it, so check the repository from time to time.
To grab the source, point your Subversion client here: http://svn2.assembla.com/svn/arcdeveloper/TweetTask
davebouwman.net weblog - copyright 2005-2008 - licensed under a
Creative
Commons License.