<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-902968642302846105</id><updated>2011-12-27T11:06:49.876-08:00</updated><category term='asp.net mvc jquery'/><category term='AdobeAir ant'/><category term='flot'/><category term='s3'/><category term='cache'/><category term='solr search'/><category term='IronPython Umbraco'/><category term='TFS'/><category term='unit tests'/><category term='graphs'/><category term='selenium'/><category term='T4'/><category term='xVal PostSharp asp.net-mvc'/><category term='sql developmen'/><category term='c#'/><category term='jquery'/><category term='software bugs'/><category term='BDD'/><category term='twilio'/><category term='code'/><category term='asp.net mvc'/><category term='twitter linux AdobeAir'/><category term='backup'/><category term='version numbering'/><category term='web.config'/><title type='text'>Simon Online</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.simontimms.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>30</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-5107666334023579736</id><published>2011-08-18T11:25:00.000-07:00</published><updated>2011-08-22T09:53:42.941-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='solr search'/><title type='text'>Configuring the Solr Virtual Appliance</title><content type='html'>I ran into a situation today where I needed a search engine in an application I was writing. The data I was searching was pretty basic and I could have easily created an SQL query to do the lookups I wanted. I was, however, suspicious that if I implemented this correctly there would be a desire for applying the search in other places in the application. I knew a little bit about Lucene so I went and did some research on it reading a number of blogs and the project documentation. It quickly became obvious that keeping the index in sync when I was writing new documents from various nodes in our web cluster would be difficult. Being a good service bus and message queue zealot that seemed like an obvious choice: throw up some message queues and distribute updates. Done.&lt;br /&gt;&lt;br /&gt;I then came across &lt;a href="http://lucene.apache.org/solr/"&gt;Solr &lt;/a&gt;which is a web version of Lucene. Having one central search server would certainly help me out. There might be scalability issues in some imaginary future world where users made heavy use of search but in that future world I am rich and don't care about such thing being far too busy plotting to release dinosaurs onto the floor of the New York Stock Exchange.&lt;br /&gt;&lt;br /&gt;I was delighted to find that there exists a &lt;a href="http://susegallery.com/a/Kr7Ayv/blaze-appliance-for-solr"&gt;virtual appliance&lt;/a&gt; with Solr installed on it already. If you haven't seen a virtual appliance before it is an image of an entire computer which is dedicated to one task and comes preconfigured for it. I am pretty sure this is where a lot of server hosting is going to end up in the next few years. &lt;br /&gt;&lt;br /&gt;Once I had the image downloaded and running in Virtual Box I had to configure it to comply with my document schema. The installed which is installed points at the example configuration files. This can be changed in /usr/share/tomcat6/conf/Catalina/localhost/solr.xml, I pointed it at /usr/local/etc/solr. The example configuration files were copied into that directory for hackification. &lt;br /&gt;&lt;br /&gt;&lt;span style="background-color: #f3f3f3; padding-left: 10px"&gt;&lt;br /&gt; cp -r /usr/share/apache-solr-1.4.1/example/solr/* /usr/local/etc/solr/&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Once you've got these files in place you can crack open the schema.xml and remove all the fields in there which are extraneous to your needs. You'll also want to remove them from the copyField section. This section builds up a super field which contains a bunch of other fields to make searching multiple fields easier. I prefer using this &lt;a href="http://wiki.apache.org/solr/DisMaxQParserPlugin"&gt;DisMax &lt;/a&gt;query to list the fields I want to search explicitly. &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-5107666334023579736?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/5107666334023579736/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2011/08/configuring-solr-virtual-appliance.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/5107666334023579736'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/5107666334023579736'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2011/08/configuring-solr-virtual-appliance.html' title='Configuring the Solr Virtual Appliance'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-1687675982062253058</id><published>2011-01-25T06:43:00.000-08:00</published><updated>2011-01-25T07:04:41.657-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='twilio'/><title type='text'>The ONE API</title><content type='html'>My boss was pretty excited when he came back from some sort of mobile technology symposium offered in Calgary last week. I like it when he goes to these things because he comes back full of wild ideas and I get to implement them or at least I get to think about implementing them which is almost as good. In this case it was &lt;b&gt;THE ONE API&lt;/b&gt;. I kid you not, this is actually the name of the thing. It is an API for sending text messages to mobile phone users, getting their location and charging them money. We were interested in sending SMS messages, not because we're a vertical marketing firm or have some fantastic news for you about your recent win of 2000 travel dollars, we have legitimate reasons. Mostly.  &lt;br /&gt;&lt;br /&gt;Anyway I signed up for an account over at &lt;a href="https://canada.oneapi.gsmworld.com/"&gt;https://canada.oneapi.gsmworld.com/&lt;/a&gt; and waited for them to authorize my account. I guess the company is out of the UK so it took them until office hours in GMT to get me the account. No big deal. In I logged and headed over to the API documentation.  They offer a SOAP and a REST version of the API so obviously I fired up curl and headed over to the sandbox url documentation in hand. It didn't work.  Not at all.  &lt;br /&gt;&lt;div style="padding-left: 10px; background-color: #d3d3d3"&gt;&lt;br /&gt;curl https://canada.oneapi.gsmworld.com/SendSmsService/rest/sandbox/ -d version=0.91 -d address=tel:+14034111111 -d message=APITEST &lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;In theory this command should have sent me a message(I changed the phone number so you internet jerks can't actually call me) or at worst return a helpful error message, so said the API documentation.&lt;br /&gt;&lt;br /&gt;What actually happened was that it failed with a general 501 error, wrapped in XML.  Not good, the error should be in JSON, so says the API docs. It also shouldn't fail and if it does the error should be specific enough for me to fix. I traced the request and I was sending exactly what I should have been.  &lt;br /&gt;&lt;br /&gt;No big deal, I'll try the SOAP API and then take my dinosaur for a walk. The WSDL they provided contained links to other WSDLs, a pretty common practice. However the URLs in the WSDL were pointing to some machine which was clearly behind their firewall making it impossible for me to use it.  &lt;br /&gt;&lt;br /&gt;I gave up at that point. These guys are not the only people who are competing in the SMS space and if they can't get the simplest part of their service, the API, right I think we're done here. Add to this that they only support the three major telcos in Canada(Telus, Bell and Rogers) and there are much better options available. &lt;a href="http://www.twilio.com"&gt;Twilio&lt;/a&gt; supports all carriers in Canada and the US and they charge, at current exchange rates, 1.5 cents a message less than these guys. Sorry &lt;b&gt;THE ONE API&lt;/b&gt; you've been replaced by a better API, cheaper messaging and better compatibility.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-1687675982062253058?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/1687675982062253058/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2011/01/one-api.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1687675982062253058'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1687675982062253058'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2011/01/one-api.html' title='The ONE API'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-4255514932375171616</id><published>2011-01-12T11:04:00.000-08:00</published><updated>2011-01-25T14:21:21.392-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='unit tests'/><category scheme='http://www.blogger.com/atom/ns#' term='TFS'/><title type='text'>Test Categories for MSTest</title><content type='html'>The version of MSTest which comes with Visual Studio 2010 has a new feature in it: test categories.  These allow you to put your tests into different groups which can be configured to run or not run depending on your settings.  In my case this was very handy for a specific test.  Most of my database layer is mocked out and I run the tests against an in memory instance of SQLite.  In the majority of cases this gives the correct results, however I had one test which required checking that values were persisted properly across database connections.  This is problematic as the in memory SQLite database is destroyed on the close of a connection.  There were other possible workarounds for this but I chose to just have that one test run against the actual MSSQL database.  Normally you wouldn't want to do this but it is just one very small test and I'm prepared to hit the disk for it. I don't want this particular test to run on the CI server as it doesn't have the correct database configured. &lt;br /&gt;&lt;br /&gt;In order to make use of a test category start by assigning one with the TestCategory attribute. &lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;[TestMethod]&lt;br /&gt;[TestCategory("MSSQLTests")]&lt;br /&gt;public void ShouldPersistSagaDataOverReinit()&lt;br /&gt;{&lt;br /&gt;     FluentNhibernateDBSagaPersister sagaPersister = new FluentNhibernateDBSagaPersister(new Assembly[] { this.GetType().Assembly }, MsSqlConfiguration.MsSql2008.ConnectionString(ConfigurationManager.ConnectionStrings["sagaData"].ConnectionString), true);&lt;br /&gt;     ...buncha stuff...&lt;br /&gt;     Assert.AreEqual(data, newData);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next in your TFS build definition add a rule in the Category Filter box to exclude this category of tests.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_ApswAzQLHYs/TS397xADG-I/AAAAAAAAACo/PjgXCMvtcCs/s1600/notMSSQL.png"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 400px; height: 74px;" src="http://2.bp.blogspot.com/_ApswAzQLHYs/TS397xADG-I/AAAAAAAAACo/PjgXCMvtcCs/s400/notMSSQL.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5561380318080080866" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://msdn.microsoft.com/en-us/library/ms182489.aspx#category"&gt;category field&lt;/a&gt; has a few options and supports some simple logic.&lt;br /&gt;&lt;br /&gt;That is pretty much it, good work!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-4255514932375171616?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/4255514932375171616/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2011/01/test-categories-for-mstest.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4255514932375171616'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4255514932375171616'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2011/01/test-categories-for-mstest.html' title='Test Categories for MSTest'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_ApswAzQLHYs/TS397xADG-I/AAAAAAAAACo/PjgXCMvtcCs/s72-c/notMSSQL.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-1888347738524566288</id><published>2011-01-07T09:08:00.000-08:00</published><updated>2011-01-07T09:14:41.224-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='c#'/><title type='text'>Updating TCPIPListener</title><content type='html'>I still have a post on encryption for S3 backups coming but I ran into this little problem today and couldn't find a solution listed on the net so into the blog it goes.  I have some code which is using an &lt;a href="http://msdn.microsoft.com/en-us/library/1y2a362e.aspx"&gt;obsolete constructor&lt;/a&gt; on System.Net.Sockets.TcpListener.  This constructor allows you to have the underlying system figure out the address for you.  It became obsolete in .net 1.1 so this is way out of date.  In order to use one of the new constructors and still keep the same behavior just use IPAdress.Any.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Old:&lt;/b&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;    new System.Net.Sockets.TcpListner(port);//warning: obsolete &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;New:&lt;/b&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;    new System.Net.Sockets.TcpListner(IPAddress.Any, port);&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-1888347738524566288?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/1888347738524566288/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2011/01/updating-tcpiplistener.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1888347738524566288'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1888347738524566288'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2011/01/updating-tcpiplistener.html' title='Updating TCPIPListener'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-62272750056266678</id><published>2011-01-04T17:11:00.001-08:00</published><updated>2011-01-04T17:57:37.764-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='backup'/><category scheme='http://www.blogger.com/atom/ns#' term='s3'/><title type='text'>S3 backup - Part II - Bucket Policy</title><content type='html'>This wasn't going to become a series of posts but it is kind of looking like it is going to be that way.  I was a bit concerned about the access to my S3 bucket in which I was backup up my files.  By default only I have access to the bin but I do tend to be an idiot and might later change the permissions on this bucket indadvertantly.  Policy to the rescue!  You can set some pretty complex access policies for S3 buckets but really all I wanted was to add a layer of IP address protection to it.  You can set policies by right clicking on the bucket in the AWS Manager and selecting properties.  In the thing that shows up at the bottom of your screen select "Edit bucket policy".  I set up this policy&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="javascript"&gt;&lt;br /&gt;{&lt;br /&gt;    "Version": "2008-10-17",&lt;br /&gt;    "Id": "S3PolicyId1",&lt;br /&gt;    "Statement": [&lt;br /&gt;        {&lt;br /&gt;            "Sid": "IPAllow",&lt;br /&gt;            "Effect": "Allow",&lt;br /&gt;            "Principal": {&lt;br /&gt;                "AWS": "*" &lt;br /&gt;            },&lt;br /&gt;            "Action": "s3:*",&lt;br /&gt;            "Resource": "arn:aws:s3:::bucket/*",&lt;br /&gt;            "Condition" : {&lt;br /&gt;                "IpAddress" : {&lt;br /&gt;                    "aws:SourceIp": "255.256.256.256" &lt;br /&gt;                }&lt;br /&gt;            } &lt;br /&gt;        } &lt;br /&gt;    ]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Yep policies are specified in JSON, it is the new XML to be sure.  Replace the obviously fake IP address with your IP address.  &lt;br /&gt;&lt;br /&gt;This will protect anybody other than me or somebody at my IP from getting at the bucket.  I am keeping a watch on the cat just in case he is trying to get into my bucket from my IP.&lt;br /&gt;&lt;br /&gt;Next part is going to be about SSL and encryption.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-62272750056266678?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/62272750056266678/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2011/01/s3-backup-part-ii.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/62272750056266678'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/62272750056266678'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2011/01/s3-backup-part-ii.html' title='S3 backup - Part II - Bucket Policy'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-672125274204172338</id><published>2011-01-02T16:05:00.000-08:00</published><updated>2011-01-02T16:34:21.984-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='backup'/><title type='text'>On Backing up Files</title><content type='html'>I've been meaning to set up some backups for ages and at last today I got around to it.  I have a bunch of computers in the house and they kind of back each other up but if my house goes up in smoke or if some sort of subterranean mole creates invade Alberta in revenge for the oil sands I'm out of luck.  &lt;br /&gt;&lt;br /&gt;I really like this company &lt;a href="http://www.backblaze.com/"&gt;BackBlaze&lt;/a&gt; who offer infinite backups for $5 a month.  I have some issues with them which I've outlined in another blog post which is, perhaps, a bit too vitriolic to post.  Anyway they have little client which runs on OSX or Windows and steams files up to their data center.  What they lack is support for UNIX operating systems and I wanted a consistent backup solution for all my computers so something platform portable was required.  They also had some pretty serious downtime a while ago which I felt highlighted some architectural immaturity.  &lt;br /&gt;&lt;br /&gt;My next stop was this rather nifty &lt;a href="http://www.s3backupsystem.com/"&gt;S3 BackupSystem&lt;/a&gt;.  Again this was windows only and couldn't backup network drives so it was out.  &lt;br /&gt;&lt;br /&gt;Finally I came acorss &lt;a href=" http://s3sync.net"&gt;s3sync.rb&lt;/a&gt; a ruby script which was designed to work like rsync.  It is simple to use and can be shoved into cron with narry a thought.  At its most basic you need to do:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Set up Keys&lt;/b&gt;&lt;br /&gt; &lt;div style="background-color:#f3f3f3"&gt;export AWS_ACCESS_KEY_ID=&amp;lt;YOUR KEY&amp;gt; &lt;br /&gt; export AWS_SECRET_ACCESS_KEY_ID=&amp;lt;YOUR SECRET KEY&amp;gt; &lt;/div&gt;&lt;br /&gt;or set the variables in&lt;br /&gt;  &lt;div style="background-color:#f3f3f3"&gt;$HOME/.s3conf/s3config.yml&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Backup /etc to S3&lt;/b&gt;&lt;br /&gt; &lt;div style="background-color:#f3f3f3"&gt;s3sync.rb  -r  /etc  &amp;lt;BUCKET_NAME&amp;gt;:&amp;lt;TOP_LEVEL_FOLDER_NAME&amp;gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Restore /etc from S3&lt;/b&gt;&lt;br /&gt; &lt;div style="background-color:#f3f3f3"&gt;s3sync.rb  -r &amp;lt;BUCKET_NAME&amp;gt;:&amp;lt;TOP_LEVEL_FOLDER_NAME&amp;gt; /etc&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;This assumes you have set up S3 already.  If not head over to &lt;a href="http://aws-portal.amazon.com/gp/aws/developer/subscription/index.html?productCode=AmazonS3"&gt;Amazon&lt;/a&gt; and set one up.  When you sign up take note of the keys they give out during the sign up.  If you fail to do so they can still be accessed once the account is set up from Security Credentials under your account settings. S3 is pretty cheap and I think you even get 15gig for free now.  If that ins't enough then it is still something like 15 cents a gig/month in storage and 10 cents a gig in transfer.  Trivial to say the least.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-672125274204172338?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/672125274204172338/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2011/01/on-backing-up-files.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/672125274204172338'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/672125274204172338'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2011/01/on-backing-up-files.html' title='On Backing up Files'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-8882750165233252303</id><published>2010-12-23T08:49:00.000-08:00</published><updated>2010-12-23T12:49:20.224-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='TFS'/><category scheme='http://www.blogger.com/atom/ns#' term='version numbering'/><title type='text'>Setting Version Number in TFS</title><content type='html'>Yet another TFS post!  Is it because I believe that TFS is the cat's meow and that you should all run out an buy it right now?  Not so much but it is starting to grow on me and I can admit that building .net projects building it is okay.  There is really a need for a collection of workflow tasks and some better generic templates.  I'm going to see if I can convince my boss to let me open source some of the stuff I'm building for running NUnit tests and the such.&lt;br /&gt;&lt;br /&gt;One thing which is missing from TFS is the ability to embed build information in the binaries which are built.  I always like this because it is easy to get clients to right click on a file and tell you the version number which will give you an idea as to where to start looking at their problem.  The version numbers in .net builds are set using attributes which are commonly defined in Properties/AssemblyInfo.cs.  The quickest solution is to update the .cs files before running the compilation step of the build.  To do this I created a new task to plug into the windows workflow process that is run during a build in TFS2010.  Funnily enough Jim Lamb used setting the build numbers as his example for &lt;a href="http://blogs.msdn.com/b/jimlamb/archive/2010/02/12/how-to-create-a-custom-workflow-activity-for-tfs-build-2010.aspx"&gt;creating a custom TFS task&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I started with the project he provided and made changes to it because my build numbers are slightly different from his.  Jim uses the date as the build number, that's reasonable but I want to know the exact changeset. I also changed what was passed into and out of the task.  It made sense to me to pass in the IBuildDetails object rather than trying to couple task together to get out of the object what I wanted.&lt;br /&gt;&lt;br /&gt;First change the in argument to BuildDetail&lt;pre name="code" class="c#"&gt;&lt;br /&gt;[RequiredArgument]&lt;br /&gt;   public InArgument&amp;lt;IBuildDetail&amp;gt; BuildDetail&lt;br /&gt;   {&lt;br /&gt;       get;&lt;br /&gt;       set;&lt;br /&gt;   }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then the execute to set up all the fields I want&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;protected override void Execute(CodeActivityContext context)&lt;br /&gt;   {&lt;br /&gt;       String filePath = FilePath.Get(context);&lt;br /&gt;&lt;br /&gt;       if (String.IsNullOrEmpty(filePath))&lt;br /&gt;       {&lt;br /&gt;           throw new ArgumentException( "Specify a path to replace text in", "FilePath");&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       if (!File.Exists(filePath))&lt;br /&gt;       {&lt;br /&gt;           throw new FileNotFoundException("File not found", filePath);&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       IBuildDetail buildDetail = BuildDetail.Get(context);&lt;br /&gt;       if (buildDetail == null)&lt;br /&gt;       {&lt;br /&gt;           throw new ArgumentException("Specify the build detail", "BuildDetail");&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       // ensure that the file is writeable&lt;br /&gt;       FileAttributes fileAttributes = File.GetAttributes(filePath);&lt;br /&gt;       File.SetAttributes(filePath, fileAttributes &amp;amp; ~FileAttributes.ReadOnly);&lt;br /&gt;&lt;br /&gt;       string contents = File.ReadAllText(filePath);&lt;br /&gt;&lt;br /&gt;       contents = SetAssemblyConfigurationString(contents, buildDetail.BuildNumber);&lt;br /&gt;       contents = SetVersionNumberString(contents, buildDetail.SourceGetVersion);&lt;br /&gt;       contents = SetVersionFileNumberString(contents, buildDetail.SourceGetVersion);&lt;br /&gt;       contents = SetAssemblyCopyrightString(contents);&lt;br /&gt;&lt;br /&gt;       File.WriteAllText(filePath, contents);&lt;br /&gt;&lt;br /&gt;       // restore the file's original attributes&lt;br /&gt;       File.SetAttributes(filePath, fileAttributes);&lt;br /&gt;   }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Slap it into the workflow, set the properties and you're good to go.  I put my task after Get Workspace in a branched version of the default workflow.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_ApswAzQLHYs/TRO1nw5uUbI/AAAAAAAAACc/NgGIUNzKOLA/s1600/Hatt.png"&gt;&lt;img style="float: left; margin: 0pt 10px 10px 0pt; cursor: pointer; width: 400px; height: 361px;" src="http://3.bp.blogspot.com/_ApswAzQLHYs/TRO1nw5uUbI/AAAAAAAAACc/NgGIUNzKOLA/s400/Hatt.png" alt="" id="BLOGGER_PHOTO_ID_5553982460224491954" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;One additional thing to remember is that you need to check a compiled version of your task into TFS somewhere and point the build controller at the location. Thanks to this all the binaries which come out of my build are stamped properly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-8882750165233252303?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/8882750165233252303/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2010/12/setting-version-number-in-tfs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/8882750165233252303'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/8882750165233252303'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2010/12/setting-version-number-in-tfs.html' title='Setting Version Number in TFS'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_ApswAzQLHYs/TRO1nw5uUbI/AAAAAAAAACc/NgGIUNzKOLA/s72-c/Hatt.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-1253641771453432622</id><published>2010-12-21T09:36:00.000-08:00</published><updated>2010-12-21T12:07:47.455-08:00</updated><title type='text'>Web Deployment in TFS Build</title><content type='html'>I have been working the last couple of days on doing web deployment as part of one of my TFS builds.  There are &lt;a href="http://vishaljoshi.blogspot.com/2010/11/team-build-web-deployment-web-deploy-vs.html"&gt;quite&lt;/a&gt; &lt;a href="http://stackoverflow.com/questions/4041836/team-build-publish-locally-using-msdeploy"&gt;a&lt;/a&gt; &lt;a href="http://weblogs.asp.net/jdanforth/archive/2010/04/24/package-and-publish-web-sites-with-tfs-2010-build-server.aspx"&gt;few&lt;/a&gt; posts out there on how to do it and so far every last one of them is wrong.  Technically they are fine, doing what they say will for sure deploy a package to your webserver*.  However they fail to grasp the deploy only on successful build model.  Why would I want to deploy something to a webserver which I had tests proving it didn't work?  All that is gonna get you is trouble, and I have enough trouble around these parts.  In order to get the behaviour I wanted I needed to edit the build workflow.&lt;br /&gt;&lt;br /&gt;First step is get your build to produce the zip file and other files associated with deployment.  To do that you need only add&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;/p:CreatePackageOnPublish=true /p:DeployOnB&lt;/span&gt;&lt;span style="font-style: italic;"&gt;uild=true &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;to  the MSBuildArgumetns in your build definition.  Here is a picture  because it makes this post look longer and easier to follow:&lt;a href="http://1.bp.blogspot.com/_ApswAzQLHYs/TRD8sAHef7I/AAAAAAAAACE/eA6QeXh_4Mo/s1600/msbuildflags.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 175px;" src="http://1.bp.blogspot.com/_ApswAzQLHYs/TRD8sAHef7I/AAAAAAAAACE/eA6QeXh_4Mo/s400/msbuildflags.png" alt="" id="BLOGGER_PHOTO_ID_5553216173423624114" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Woo, picture!&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_ApswAzQLHYs/TRD4HdAqp0I/AAAAAAAAABk/4qZk1bWfjO4/s1600/branch.png"&gt;&lt;img style="float: left; margin: 0pt 10px 10px 0pt; cursor: pointer; width: 400px; height: 314px;" src="http://2.bp.blogspot.com/_ApswAzQLHYs/TRD4HdAqp0I/AAAAAAAAABk/4qZk1bWfjO4/s400/branch.png" alt="" id="BLOGGER_PHOTO_ID_5553211147478017858" border="0" /&gt;&lt;/a&gt;Next I branched an existing build template. These templates are located, by default, in BuildProcessTemplates folder at the root of your TFS project.  You can put them anywhere you want, however, and you might want to put them in the same directory as your solution files, just to keep them spatially proximal.  You want to branch rather than creating a new one because the default template is amazingly complex and recreating it would be painful. The default workflow is actually pretty reasonable and I have only minor quibbles with how it is put together.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Next we're going to to add some new parameters to the build so that people can easily configure the deployment &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_ApswAzQLHYs/TRD7DXLgKXI/AAAAAAAAAB8/t2mCrt2iAHE/s1600/parameters.png"&gt;&lt;img style="float: right; margin: 0pt 0pt 10px 10px; cursor: pointer; width: 400px; height: 100px;" src="http://4.bp.blogspot.com/_ApswAzQLHYs/TRD7DXLgKXI/AAAAAAAAAB8/t2mCrt2iAHE/s400/parameters.png" alt="" id="BLOGGER_PHOTO_ID_5553214375728261490" border="0" /&gt;&lt;/a&gt;from the build definition.  To do this open up your newly branched template and click on arguments.  I have yet to figure out how you can put these arguments into categories from the UI but from the XML it is &lt;a href="http://blogs.msdn.com/b/jpricket/archive/2009/12/23/tfs-2010-custom-process-parameters-part-2-metadata.aspx"&gt;pretty easy&lt;/a&gt;.  I don't care enough to put things into categories at this time, although it probably took me longer to write this sentence about not caring than it would have taken to just do it.&lt;br /&gt;&lt;br /&gt;As you can see I added 6 new properties&lt;br /&gt;&lt;ul&gt;&lt;li&gt;PerformDeploy - A boolean which denotes if we should attempt a deploy at all.  This keeps our project generic so it can be used on anything.&lt;/li&gt;&lt;li&gt;DeploymentURL - The URL to which the deployment should run.  I'm stuck on IIS6 so this is the MSDeployAgentService URL&lt;/li&gt;&lt;li&gt;DeploymentUserName - The username to use during the deploy&lt;/li&gt;&lt;li&gt;DeploymentPassword - The password to use during the deploy.  You might want to be more creative about how you get your password to comply with whatever rules exist at your site.&lt;/li&gt;&lt;li&gt;DeploymentApplicationPath - The path in IIS to which deployment should be run&lt;/li&gt;&lt;li&gt;DeploymentCommand - The name of the *deploy.cmd file to run.  This should&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Now for the fun part: adding the new workflow item.  If you have a slow computer, say anything slower than the computer from &lt;a href="http://thecia.com.au/star-trek/voyager/406a.shtml#specs"&gt;Voyager&lt;/a&gt; then you're going to be sorry you ever opened this workflow.  Scroll to the very bottom where there is a task for dealing with gated checkins.  Our new task is going to go after this.  Drag and If from the toolbox.  In the conditional we're going to check that we had no build failures and that we should run a deploy:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; font-weight: bold;"&gt;BuildDetail.TestStatus = Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Succeeded And PerformDeploy&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now drop in an invoke process to the Then.  In the arguments put&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; font-weight: bold;"&gt;String.Format("/M:{0} /U:{1} /P:{2} ""-setParam:name='IIS Web Application Name',value='{3}'"" /Y ", DeploymentURL, DeploymentUserName, DeploymentPassword, DeploymentApplicationPath)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;File name:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; font-weight: bold;"&gt;Path.Combine(BuildDetail.DropLocation, DeploymentCommand)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_ApswAzQLHYs/TRD-Rss7p3I/AAAAAAAAACM/cRHXtmEHosk/s1600/task.png"&gt;&lt;img style="float: left; margin: 0pt 10px 10px 0pt; cursor: pointer; width: 397px; height: 400px;" src="http://4.bp.blogspot.com/_ApswAzQLHYs/TRD-Rss7p3I/AAAAAAAAACM/cRHXtmEHosk/s400/task.png" alt="" id="BLOGGER_PHOTO_ID_5553217920558671730" border="0" /&gt;&lt;/a&gt;I also dropped a couple of WriteBuildMessage actions on there because that is how I roll.  Remember this: the longer the build log the more important your job.  Let's see that junior developer guy who only gets paid half what you do figure this 29Meg log file out.&lt;br /&gt;&lt;br /&gt;That is pretty much it, save it, commit it and create a new build definition which uses it.  You can either run it on commit or manually.  I am running mine manually because I don't trust our unit tests to prove correctness yet.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;*Assuming you have everything configured properly and you're not relying on black magic or even mauve magic.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-1253641771453432622?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/1253641771453432622/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2010/12/web-deployment-in-tfs-build.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1253641771453432622'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1253641771453432622'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2010/12/web-deployment-in-tfs-build.html' title='Web Deployment in TFS Build'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_ApswAzQLHYs/TRD8sAHef7I/AAAAAAAAACE/eA6QeXh_4Mo/s72-c/msbuildflags.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-4058566675981002059</id><published>2010-12-15T20:21:00.000-08:00</published><updated>2010-12-15T21:23:05.062-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='BDD'/><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><title type='text'>Selenium - UI Testing - Part 1</title><content type='html'>I had a bug to fix earlier this week which was really just a UI bug.  The details aren't all that important but it was something like a field in a popup div wasn't being properly updated.  This felt a lot like something which could use not just a test in the back end to ensure the correct data was being passed back but also a test in the front end to make sure it was being displayed.  As our front end is all webby, as most front ends tend to be these days.  Years ago I did some work with &lt;a href="http://en.wikipedia.org/wiki/HP_QuickTest_Professional"&gt;Mercury QuickTest&lt;/a&gt; as it was then called(I believe it is now owned by HP which makes it pure evil, just like HP-UX).  I looked at a couple of tools for automating the browser and decided on &lt;a href="http://seleniumhq.org/"&gt;selenium&lt;/a&gt; partially because it appeared to be the most developed and partially because I couldn't refuse a tool with an atomic mass of 78.96.&lt;br /&gt;&lt;br /&gt;This is the part where I warn you that I really have no idea what I'm doing, I'm no selenium expert and I'll probably change my mind about how to best implement it.  I'll post updates as I change my mind.&lt;br /&gt;&lt;br /&gt;I started by installing the selenium IDE and, on another computer, the remote control.  The IDE is just a plugin for firefox while the RC is a java based tool which happily runs on windows.  I opened up a port for it in the windows firewall... oh who am I kidding I turned the damn thing off.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_ApswAzQLHYs/TQmdxcXZnhI/AAAAAAAAABU/OjGFttl_njQ/s1600/seleniumIDE.png"&gt;&lt;img style="float: left; margin: 0pt 10px 10px 0pt; cursor: pointer; width: 400px; height: 267px;" src="http://2.bp.blogspot.com/_ApswAzQLHYs/TQmdxcXZnhI/AAAAAAAAABU/OjGFttl_njQ/s400/seleniumIDE.png" alt="" id="BLOGGER_PHOTO_ID_5551141488464338450" border="0" /&gt;&lt;/a&gt;I recorded a number of actions using the IDE and added a number of verifications(you can add a check by right clicking on an element and selecting one of the verify options in there).  I don't consider these to be unit tests since they tend to cross over multiple pages.  What I'm creating are work flow tests or behavioral tests.  These could be used as part of BDD in conjunction with a tool such as &lt;a href="http://cukes.info/"&gt;Cucumber&lt;/a&gt; or &lt;a href="http://www.specflow.org/"&gt;SpecFlow&lt;/a&gt;.  As such I don't really care how long these tests take to run, I don't envision them being run as part of our continuous integration process but rather as part of hourly builds.  If things really get out of hand(and they won't our application isn't that big) then the tests can be distributed over a number of machines and even run in the cloud on some Amazon instances.&lt;br /&gt;&lt;br /&gt;In the Selenium IDE I exported the test to C# so they could be run in our normal build process.  Unfortunately the default export template export nunit tests and our project makes use of the visual studio test suite.  There are no technical reasons for not having both testing frameworks but I don't want to burden the other developers too much with a variety of frameworks.  So my job for tomorrow is to explore alternatives to the standard export.  I am also not crazy about having to maintain the tests in C#, it requires that a developer be involved in testing changes when it should really be easy enough for a business person to specify the behaviour.  I have some ideas about running the transformations from Selenium file format(which is an html table) into C# using either T4 or the transformation code extracted from the IDE running in a server side javascript framework.&lt;br /&gt;&lt;br /&gt;I'll let you know what I come up with.  Chances are it will be crazy, because sensible isn't all that fun.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Useful Blogs&lt;/b&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/design-of-selenium-tests-for-asp-net/"&gt;Design of Selenium tests for Asp.net&lt;/a&gt;A blog with some specific suggestions about how to work with selenium and asp.net.  I'm not in 100% agreement with his ideas but I might change my mind later.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://adam.goucher.ca/"&gt;Adam Goucher's blog&lt;/a&gt; - Adam is a hell of a nice guy who has been helping me out with Selenium on the twitter.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-4058566675981002059?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/4058566675981002059/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2010/12/selenium-ui-testing-part-1.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4058566675981002059'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4058566675981002059'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2010/12/selenium-ui-testing-part-1.html' title='Selenium - UI Testing - Part 1'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_ApswAzQLHYs/TQmdxcXZnhI/AAAAAAAAABU/OjGFttl_njQ/s72-c/seleniumIDE.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-875827473700896747</id><published>2010-11-19T14:06:00.001-08:00</published><updated>2010-11-19T14:15:37.981-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web.config'/><category scheme='http://www.blogger.com/atom/ns#' term='c#'/><title type='text'>Web.config Tranformation Issues</title><content type='html'>As you all should I am using &lt;a href="http://msdn.microsoft.com/en-us/library/dd465326.aspx"&gt;web.config transformations&lt;/a&gt; during the packaging of my ASP.net web applications.  Today I was working with a project which didn't have transformations defined previously so I thought I would go ahead and add them.  All went well until I built a deployment package and noticed that my transformations were not being applied.  Looking at the build log I found warnings like these&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;C:\Users\stimms\Documents\Visual Studio 2010\Projects\SockPuppet\Web.Debug.config(5,2): Warning : No element in the source document matches '/configuration'&lt;br /&gt;Not executing SetAttributes (transform line 18, 12)&lt;br /&gt;Output File: obj\Debug\TransformWebConfig\transformed\Web.config&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This started a fun bit of debugging during which I removed everything from the web.config and built it from the ground up.  Eventually I traced the problem to a hold over from some previous version of .net, in the Web.config the configuration was defined as&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Changing that to just&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;configuration&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Solved the problem.  I imagine this is some sort of odd XML namespace issue, hopefully if you run into this you'll find this post and not waste an hour like me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-875827473700896747?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/875827473700896747/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2010/11/webconfig-tranformation-issues.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/875827473700896747'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/875827473700896747'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2010/11/webconfig-tranformation-issues.html' title='Web.config Tranformation Issues'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-9126532816753088393</id><published>2010-10-05T21:55:00.000-07:00</published><updated>2010-10-07T20:39:20.298-07:00</updated><title type='text'>MySQL in an NServiceBus Handler</title><content type='html'>I have an autonomous component in my NServiceBus implementation which needs to talk to a MySQL database.  When I first implemented the handler I configured the end point to be transactional, mostly because I wasn't too sure about the difference between configuring AsA_Service and AsA_Client and transactions sounded like they might be something I would like.  What the transactional endpoint does is wrap the endpoint in a distributed transaction.  A distributed transaction is a mechanism which allows you to share a transaction between a number of databases so that if you're writing to several databases when you commit to one database and it fails then the transactions in the other databases will rollback.  &lt;br /&gt;&lt;br /&gt;However when I went to test the handler it failed with an error: &lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;MySql /Net connector does not support distributed transactions&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I solved it by configuring the endpoint AsA_Client.  The problem with that configuration is that the handler wipes the queue on startup which isn't ideal.  None of the built in configurations are quite right for our situation so we override the configuration as described here: http://www.nservicebus.com/GenericHost.aspx.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-9126532816753088393?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/9126532816753088393/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2010/10/mysql-in-nservicebus-handler.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/9126532816753088393'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/9126532816753088393'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2010/10/mysql-in-nservicebus-handler.html' title='MySQL in an NServiceBus Handler'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-3276352344540857324</id><published>2010-06-28T08:42:00.000-07:00</published><updated>2010-06-28T08:50:27.330-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql developmen'/><title type='text'>Fixing Table Identities</title><content type='html'>I make heavy use of Red Gate's excellent SQL Compare tools.  I know I'm a bit of a shrill for them but they are time savers when dealing with multiple environments(development, testing, production) which is a pretty common occurrence in any sort of agile development.  One flaw in them is that they often mess up the sequences in the destination database.  Say you have a table Students with 15 records in it in development and 30 in production then performing a  copy often brings along the sequence even if you don't select syncing that table.  This results in duplicate key errors whenever a new record is inserted.&lt;br /&gt;&lt;br /&gt;For weeks I have been saying "I should write a script to check and fix that".  Well I finally did it&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="sql"&gt;&lt;br /&gt;SET ANSI_NULLS ON&lt;br /&gt;GO&lt;br /&gt;SET QUOTED_IDENTIFIER ON&lt;br /&gt;GO&lt;br /&gt;&lt;br /&gt;create PROCEDURE dbo.FixTableIdentities&lt;br /&gt; &lt;br /&gt;AS&lt;br /&gt;BEGIN&lt;br /&gt; SET NOCOUNT ON;&lt;br /&gt; declare @currentKeyValue int&lt;br /&gt; declare @currentIdentityValue int&lt;br /&gt; declare @toRun nvarchar(500)&lt;br /&gt; declare @tableToCheck nvarchar(500)&lt;br /&gt; declare @idColumnCount int&lt;br /&gt;    declare db_cursor cursor for select name from sysobjects where type='U'&lt;br /&gt;    &lt;br /&gt;    open db_cursor&lt;br /&gt;    fetch next from db_cursor into @tableToCheck&lt;br /&gt;    &lt;br /&gt;    while @@FETCH_STATUS = 0&lt;br /&gt;    BEGIN&lt;br /&gt;  select @idColumnCount = count(*) from syscolumns where id=object_id(@tableToCheck) and name='id'&lt;br /&gt;  if(@idColumnCount = 1)&lt;br /&gt;  BEGIN&lt;br /&gt;    select @currentKeyValue = ident_current(@tableToCheck) &lt;br /&gt;    set @toRun = N'select @currentIdentityValue = max(id) from ' + @tableToCheck;&lt;br /&gt;    EXEC sp_executesql @toRun, N'@currentIdentityValue int OUTPUT', @currentIdentityValue OUTPUT;&lt;br /&gt;    if(@currentIdentityValue &lt;&gt; @currentKeyValue)&lt;br /&gt;    BEGIN&lt;br /&gt;    DBCC CHECKIDENT (@tableToCheck,reseed, @currentIdentityValue) &lt;br /&gt;    END&lt;br /&gt;   END&lt;br /&gt;   FETCH NEXT FROM db_cursor into @tableToCheck&lt;br /&gt; END&lt;br /&gt; CLOSE db_cursor&lt;br /&gt; deallocate db_cursor&lt;br /&gt;END&lt;br /&gt;GO&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When run this procedure will go through all your tables and ensure that the id column is in sync with the sequence.  At the moment it just looks at the column called id and manipulates that.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-3276352344540857324?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/3276352344540857324/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2010/06/fixing-table-identities.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/3276352344540857324'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/3276352344540857324'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2010/06/fixing-table-identities.html' title='Fixing Table Identities'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-9063495160846506656</id><published>2009-12-13T09:17:00.000-08:00</published><updated>2009-12-13T09:47:04.246-08:00</updated><title type='text'>Measuring Language Productivity</title><content type='html'>I recently asked a question over at stackoverflow about the productivity gains in various languages.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Does anybody know of any research or benchmarks of how long it takes to develop the same&lt;br /&gt;application in a variety of languages?  Really I'm looking for Java vs. C++ but any&lt;br /&gt;comparisons would be useful.  I have the feeling there is a section in Code Complete&lt;br /&gt;about this but my copy is at work.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I was really asking because I wanted to help justify my use of the &lt;a href="http://clarkparsia.com/pellet/"&gt;Pellet&lt;/a&gt; semantic reasoner over the &lt;a href="http://owl.man.ac.uk/factplusplus/"&gt;FaCT++&lt;/a&gt; reasoner in a paper.&lt;br /&gt;&lt;br /&gt;What emerged from the question was that there really was not much good research into the topic of language productivity and that any research which had been done was from the 2000 time-frame.  What makes research like this difficult is finding a large sample size and finding problems which don't favour one class of language greatly over another.  That got me thinking, what better source of programmers is there than stackoverflow?  There are developers from all across the spectrum of languages and abilities; there is even a pretty good geographic disbursement.&lt;br /&gt;&lt;br /&gt;Let's do this research ourselves!  I propose a stackoverflow language programming contest.  We'll develop a suite of programming tasks which try as hard as possible to not focus on the advantages of one particular language and gather metrics.  I think we should gather&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Time taken to develop&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Lines of code required&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Runtime over the same input&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Memory usage over the same input&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Other things I haven't thought of&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;I'll set up a site to gather people's solutions to the problems and collate statistics but the problems should be proposed by the community.  We'll allow people to checkout the problem set, time how long it takes to the to complete it and then submit the code for their answers.  I'll run the code and benchmark the results and after, say two weeks of having the contest open, publish my results as well as the dataset for anybody else to analyze.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-9063495160846506656?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/9063495160846506656/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/12/measuring-language-productivity.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/9063495160846506656'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/9063495160846506656'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/12/measuring-language-productivity.html' title='Measuring Language Productivity'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-6728203011899698630</id><published>2009-12-09T07:01:00.000-08:00</published><updated>2009-12-09T07:13:46.853-08:00</updated><title type='text'>Abuse of Extension Methods</title><content type='html'>In the code base I'm working with we have a number of objects which augment existing objects.  For instance I have a User object which is generated by my ORM so it looks like&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;   string userName;&lt;br /&gt;   string firstName;&lt;br /&gt;   string lastName;&lt;br /&gt;   int companyID;&lt;br /&gt;   int locationID;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In order to display user objects it is useful to have the name of the company and the location which are stored in another table.  To limit the amount of stuff being passed around we defined an ExtendedUser which extends User and adds the fields&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;   string companyName;&lt;br /&gt;   string locationName;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Creating these extended classes requires passing in a base class and then pulling all the properties off of it and assigning them to the extended class.  This is suboptimal because it means that when a new property is added to the bass class it has to be added to the code which extracts the properties in the extended class.  To address this I created a method which iterates over the properties in the base class and assigns them to the extended class.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;public static void CopyProperties(this object destination, object source, List&amp;lt;string&amp;gt ignoreList)&lt;br /&gt;        {&lt;br /&gt;            foreach (var property in destination.GetType().GetProperties())&lt;br /&gt;            {&lt;br /&gt;                if (source.GetType().GetProperties().Select(p =&gt; p.Name).Contains(property.Name) &amp;&amp; !ignoreList.Contains(property.Name))&lt;br /&gt;                {&lt;br /&gt;                    var sourceProperty = source.GetType().GetProperty(property.Name);&lt;br /&gt;                    if (property.CanWrite &amp;&amp; sourceProperty.GetType() == property.GetType() &amp;&amp; sourceProperty.GetValue(source, null) != null)&lt;br /&gt;                        property.SetValue(destination, sourceProperty.GetValue(source, null), null);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you have sharp eyes you'll notice that I've defined this method as an extension method.  This allows me to do insane things like &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;ExpandedClass expandedClass = new ExpandedClass();&lt;br /&gt;expandedClass.CopyProperties(cls);&lt;br /&gt;expandedClass.LocationName = GetLocationNameFromID(cls.LocationID);&lt;br /&gt;expandedClass.CourseName = GetCourseNameFromID(cls.CourseID);&lt;br /&gt;expandedClass.InstructorName = GetInstructorNameFromID(cls.InstructorID);&lt;br /&gt;expandedClass.CompanyName = GetCompanyNameFromID(cls.CompanyID);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I can also do this for any other two classes which share property names.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-6728203011899698630?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/6728203011899698630/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/12/abuse-of-extension-methods.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/6728203011899698630'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/6728203011899698630'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/12/abuse-of-extension-methods.html' title='Abuse of Extension Methods'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-8392174183608837661</id><published>2009-11-14T17:27:00.000-08:00</published><updated>2009-11-15T00:10:52.619-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='T4'/><category scheme='http://www.blogger.com/atom/ns#' term='asp.net mvc'/><title type='text'>Persisting in MVC</title><content type='html'>Rob Conery, who is a giant in the ASP.net MVC world(he wrote the ASP.net store front and is also the author of a 200 page book on inversion of control) is &lt;a href="http://blog.wekeroad.com/subsonic/its-time-for-this-activerecordengine-for-asp-net-mvc/"&gt;calling for suggestions&lt;/a&gt; about an active record persistence engine.  I wanted to present how I think it should be done which is just a bit too long for a comment on the tail end of Rob's blog.  I've been reading a lot lately about &lt;a href="http://haacked.com/archive/2008/11/04/areas-in-aspnetmvc.aspx"&gt;areas in MVC2&lt;/a&gt; and the &lt;a href="http://www.lostechies.com/blogs/hex/archive/2009/11/01/asp-net-mvc-portable-areas-via-mvccontrib.aspx"&gt;portable areas project&lt;/a&gt; which is part of MVC contrib project.  Now the portable areas aren't yet finalized but the idea is that these areas will be able to be dropped into a project and will provide some set of controllers and views which will provide a swack of functionality.  &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.lostechies.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/hex/image_5F00_thumb_5F00_35686ED4.png"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 614px; height: 480px;" src="http://www.lostechies.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/hex/image_5F00_thumb_5F00_35686ED4.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The example I've seen bandied about is that of a forum.  Everybody has written a forum or two in their time now you can just drop in an area and get the functionality for free.  I can see a lot of these components becoming available on codeplex or git hub.  Component based software like this is "the future" just like SOA was the future a few years ago.  The problem with components like this is that it is difficult to keep things consistent across the various components.  At one end of the spectrum of self containment If each component is self contained then it has to provide for its own data persistence as well as any other services it consumes.  &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_ApswAzQLHYs/Sv-D8M7CTpI/AAAAAAAAAA8/iKXzEf7j2xM/s1600-h/paintdiagram.png"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 400px; height: 99px;" src="http://4.bp.blogspot.com/_ApswAzQLHYs/Sv-D8M7CTpI/AAAAAAAAAA8/iKXzEf7j2xM/s400/paintdiagram.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404183148152114834" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I have helpfully harnessed the power of MS Paint to create an illustration of the spectrum between a component being self contained and being reliant on services being provided for it.  If anybody is interested my artistic skills are available for hire.  The further to the left the more portable the further to the right the more reliant the components are on services being provided for them and the less portable.  We want to be towards the left, because left is right.  &lt;br /&gt;&lt;br /&gt;If these components really are the future then we need to find a way to couple the components and provide communication between them.  This is where MEF steps up to the plate.  What I'm proposing is that rather than spending our time creating unified interfaces for storing data we create a method agnostic object store.  Components would call out to MEF for a persistence engine and then pass in whatever it was they wanted to save.  The engine should handle the creation of database tables on the fly or files or web service callouts to a cloud.  That is what I believe should exist instead of a more concrete IActiveRecordEngine.  &lt;br /&gt;&lt;br /&gt;What's the advantage?  We keep the standard interface for which Rob is striving but we can now have that interface implemented by a plugable component rather than having it hard coded into a web.config.&lt;br /&gt;&lt;br /&gt;The second part of Rob's post is about creating &lt;a href="http://flimflan.com/blog/SampleOpinionatedController.aspx"&gt;opinionated controllers&lt;/a&gt;.  I have to say I'm dead against that.  I agree with the goal of implementing the basic CRUD operations for people, in fact I'm in love with it.  What I don't like is that it is implemented in a base class from which my controllers descend.  If I'm reading the post correctly then the base controller is implementing actual actions.  It is dangerous to implement actions willy nilly, actions which could be dangerous and people wouldn't even realize the actions exist.  Chances are very good that users are just going to leave the actions implemented rather than overriding them with noop actions.  &lt;br /&gt;&lt;br /&gt;Another option is that I'm reading this post incorrectly and the methods in the base class are private and not actions.  I like that a lot more, but even more I like generating template controllers.  Subsonic 3 follows this methodology and it is really nice to be able to twiddle with bits of the implementation.  What's more the generation doesn't have to stop at the controller.  If the implementation in the controller is known then why not generate the views as well?  &lt;br /&gt;&lt;br /&gt;All in all I like the idea of improving the object store support in ASP.net MVC but I would like it to be as flexible as possible.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-8392174183608837661?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/8392174183608837661/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/11/persisting-in-mvc.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/8392174183608837661'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/8392174183608837661'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/11/persisting-in-mvc.html' title='Persisting in MVC'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_ApswAzQLHYs/Sv-D8M7CTpI/AAAAAAAAAA8/iKXzEf7j2xM/s72-c/paintdiagram.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-3406408158388670361</id><published>2009-10-28T13:30:00.001-07:00</published><updated>2009-10-28T13:34:15.956-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='IronPython Umbraco'/><title type='text'>Quick post on getting node information from Umbraco with IronPython</title><content type='html'>I was just working with the IronPython page type in umbraco and needed to get a property from the page I was on.  This can be done by accessing the Node API found in umbraco.presentation.nodeFactory.  In order to be able to pull a value you will need pull in that part of the API&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="python"&gt;&lt;br /&gt;import umbraco.presentation.nodeFactory&lt;br /&gt;from umbraco.presentation.nodeFactory import *&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now you can get the current node and query its properties&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="python"&gt;&lt;br /&gt;print  Node.GetCurrent().GetProperty("Address").Value&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-3406408158388670361?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/3406408158388670361/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/10/quick-post-on-getting-node-information.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/3406408158388670361'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/3406408158388670361'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/10/quick-post-on-getting-node-information.html' title='Quick post on getting node information from Umbraco with IronPython'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-5191909010357417761</id><published>2009-10-07T21:37:00.000-07:00</published><updated>2009-10-07T21:56:46.778-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xVal PostSharp asp.net-mvc'/><title type='text'>xVal PostSharp 1.0 Demo Project</title><content type='html'>After my last post I thought I would look at the demo project for xVal 1.0 and see if I could get it working with PostSharp.  It was a little bit differnt in how it way set up from my projects but I figured it could still be improved with PostSharp.  My first issue was that the method I was intercepting was in the entity itself rather than in a repository.  This meant that there were methods in the entity which I didn't wish to intercept.  There were two classes of those&lt;br /&gt;&lt;ol&gt;&lt;br /&gt; &lt;li&gt;Accessor methods - we don't need to intercept getters&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Internal methods - ASP.net MVC uses reflection to examine the internals of the data classes in order to bind form results to them. We can avoid intercepting these by ignoring methods which start with '.'&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;Next because the entity already contained all of the data it needed to persist the persistance method didn't have any arguments.  In the previous post I assumed that this would always be the case.  You know what they say about assuming: if you assume you make a jerk out of everybody in Venice.  Pretty sure that is the saying.  &lt;br /&gt;&lt;br /&gt;This required and expansion of the current validator&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;public override void OnEntry(MethodExecutionEventArgs eventArgs)&lt;br /&gt;        {&lt;br /&gt;            if (IsAccessor(eventArgs) || IsInternal(eventArgs))&lt;br /&gt;                return;&lt;br /&gt;            if (HasArguments(eventArgs))&lt;br /&gt;            {&lt;br /&gt;                CheckPassedInformation(eventArgs);&lt;br /&gt;            }&lt;br /&gt;            else&lt;br /&gt;            {&lt;br /&gt;                CheckSelfContainedEntity(eventArgs);&lt;br /&gt;            }&lt;br /&gt;            base.OnEntry(eventArgs);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private static bool HasArguments(MethodExecutionEventArgs eventArgs)&lt;br /&gt;        {&lt;br /&gt;            return eventArgs.GetReadOnlyArgumentArray() != null &amp;&amp; eventArgs.GetReadOnlyArgumentArray().Count() &amp;rt; 0;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private static void CheckPassedInformation(MethodExecutionEventArgs eventArgs)&lt;br /&gt;        {&lt;br /&gt;            var toValidate = eventArgs.GetReadOnlyArgumentArray()[0];&lt;br /&gt;            var errors = DataAnnotationsValidationRunner.GetErrors(toValidate);&lt;br /&gt;            if (errors.Any())&lt;br /&gt;                throw new RulesException(errors);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private static void CheckSelfContainedEntity(MethodExecutionEventArgs eventArgs)&lt;br /&gt;        {&lt;br /&gt;            var toValidate = eventArgs.Instance;&lt;br /&gt;            var errors = DataAnnotationsValidationRunner.GetErrors(toValidate);&lt;br /&gt;            if (errors.Any())&lt;br /&gt;                throw new RulesException(errors);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public bool IsAccessor(MethodExecutionEventArgs eventArgs)&lt;br /&gt;        {&lt;br /&gt;            if (eventArgs.Method.Name.StartsWith("get_"))&lt;br /&gt;                return true;&lt;br /&gt;            return false;&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public bool IsInternal(MethodExecutionEventArgs eventArgs)&lt;br /&gt;        {&lt;br /&gt;            if (eventArgs.Method.Name.StartsWith("."))&lt;br /&gt;                return true;&lt;br /&gt;            return false;&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You can see I did a little bit of clean code refactoring in there to extract some methods.  Now two different methods of saving information are checked.&lt;br /&gt;&lt;br /&gt;The lesson here seems to be that the way in which you construct your data persistance layer has an effect on the construction of the validator such that there is no generic aspect which you can download and use.  &lt;br /&gt;&lt;br /&gt;You can download my modified xVal project &lt;a href="http://stimms.googlepages.com/PostSharp-xValDemo.zip"&gt;here&lt;/a&gt;.  In order to get it running you'll need to have &lt;a href="http://www.postsharp.org/"&gt;PostSharp&lt;/a&gt; installed but you're going to want it for lots of other stuff so get going on installing it.&lt;br /&gt;&lt;br /&gt;Oh just one more note, when you're trying it out be sure to disable javascript so that the page actually posts back and doesn't validate using the javascript validation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-5191909010357417761?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/5191909010357417761/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/10/xval-postsharp-10-demo-project.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/5191909010357417761'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/5191909010357417761'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/10/xval-postsharp-10-demo-project.html' title='xVal PostSharp 1.0 Demo Project'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-2424984806793334749</id><published>2009-09-16T21:10:00.001-07:00</published><updated>2009-10-07T20:03:24.513-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xVal PostSharp asp.net-mvc'/><title type='text'>Cleaning Up xVal Validation With PostSharp</title><content type='html'>Even though the new ASP.net MVC 2.0 framework comes with built in &lt;a href="http://haacked.com/archive/2009/10/01/asp.net-mvc-preview-2-released.aspx"&gt;validation&lt;/a&gt; it is useful to look at some alternatives.  Afterall you don't want Microsoft telling you how to do everything, do you?  One of the better validation frameworks is the xVal framework which just &lt;a href="http://blog.codeville.net/2009/09/17/xval-v10-now-available/"&gt;went 1.0&lt;/a&gt;.  In this release there has been added support for a number of new features, probably the coolest of which is AJAX based validation for complex input.  &lt;br /&gt;&lt;br /&gt;However xVal does have one drawback, it is quite verbose to implement.  In every method which alters data you will have to put something like&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;            var errors = DataAnnotationsValidationRunner.GetErrors(this).ToList();&lt;br /&gt; &lt;br /&gt;            if(errors.Any())&lt;br /&gt;                throw new RulesException(errors);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is a bit repedative and kind of tiresome.  Sure we could extract a method from this and just call that each and every time we edit data but that doesn't really solve the underlying problem of having code which is repeated.  &lt;br /&gt;&lt;br /&gt;Enter &lt;a href="http://www.postsharp.org/"&gt;PostSharp.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;PostSharp is an aspect oriented addon for .net languages.  It acutally does most of its weaving in a post build step modifying the MSIL the compiler generates.  We can extract the validation into an aspect.  &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt; [Serializable]&lt;br /&gt;    public class ValidateAttribute : OnMethodBoundaryAspect&lt;br /&gt;    {&lt;br /&gt;        public ValidateAttribute(){}&lt;br /&gt;&lt;br /&gt;        public override void OnEntry(MethodExecutionEventArgs eventArgs)&lt;br /&gt;        {&lt;br /&gt;            if (eventArgs.GetReadOnlyArgumentArray() != null &amp;&amp; eventArgs.GetReadOnlyArgumentArray().Count() &gt; 0)&lt;br /&gt;            {&lt;br /&gt;                var toValidate = eventArgs.GetReadOnlyArgumentArray()[0];&lt;br /&gt;                var errors = DataAnnotationsValidationRunner.GetErrors(toValidate);&lt;br /&gt;                if (errors.Any())&lt;br /&gt;                    throw new RulesException(errors);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here we create an OnEntry method which is called before the method we are intercepting.  We skip any method with no arguments since it isn't likely to be updating data.  Then we extract the argumetns and pass them into the validator for it to do its business.  &lt;br /&gt;&lt;br /&gt;Finaly we give the PostSharp framework a bit of information about where to use this aspect&lt;br /&gt;&lt;br /&gt;AssemblyInfo.cs&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;[assembly: CertificateSearch.Aspects.Validate(AttributeTargetTypes = "CertificateSearch.Models.*Repository")]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I have applied it to all the methods in files which end with Repository the Models namespace.  That covers all the data modification methods in the project.  I can now add new methods without the cost of ensuring that I validate each one.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-2424984806793334749?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/2424984806793334749/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/09/cleaning-up-xval-validation-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/2424984806793334749'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/2424984806793334749'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/09/cleaning-up-xval-validation-with.html' title='Cleaning Up xVal Validation With PostSharp'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-5408115802514110891</id><published>2009-09-16T20:51:00.000-07:00</published><updated>2009-09-16T21:06:14.059-07:00</updated><title type='text'>IE Caches JSON</title><content type='html'>I ran into an interesting problem today on everybody's favorite browser Internet Explorer.  At issue was a page I had which was partially populated using jQuery's &lt;a href="http://docs.jquery.com/Ajax/jQuery.getJSON"&gt;getJSON&lt;/a&gt; function.  As it turns out even though I had the caching turned to no-cache on the server IE was perfectly happy to cache the document because it was fetched using GET.  Apparently this is OK to do.  Obviously this ruins my site's functionality so I instructed jQuery to override it by setting &lt;br /&gt;&lt;br /&gt;&lt;pre class="javascript" name="code"&gt;&lt;br /&gt;   $.ajaxSetup({ cache: true });&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This function works by adding a nonsense value to the end of the request.  Looking at the actual jQuery source we can see that the current time is appened to the request which makes the browser believe it is a new URL.&lt;br /&gt;&lt;br /&gt;&lt;pre class="javascript" name="code"&gt;&lt;br /&gt;  if ( s.cache === false &amp;&amp; type === "GET" ) {&lt;br /&gt;   var ts = now();&lt;br /&gt;&lt;br /&gt;   // try replacing _= if it is there&lt;br /&gt;   var ret = s.url.replace(rts, "$1_=" + ts + "$2");&lt;br /&gt;&lt;br /&gt;   // if nothing was replaced, add timestamp to the end&lt;br /&gt;   s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&amp;" : "?") + "_=" + ts : "");&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Problem solved&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-5408115802514110891?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/5408115802514110891/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/09/ie-caches-json.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/5408115802514110891'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/5408115802514110891'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/09/ie-caches-json.html' title='IE Caches JSON'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-8428657579133578662</id><published>2009-08-28T16:24:00.000-07:00</published><updated>2009-08-28T20:30:32.887-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cache'/><title type='text'>Caching and Why You Shouldn't Listen to Blogs</title><content type='html'>A while ago I wrote and article entitled "&lt;a href="http://stimms.blogspot.com/2009/06/html-helper-for-including-and.html"&gt;HTML Helper for Including and Compressing Javascript&lt;/a&gt;", no don't click on that link because it is all wrong.  The gist of the article was that in order to save clients from downloading a bunch javascript files each time they visited the site opening up a bunch of costly connections a handler would include all those files in the HTML page and, as an added bonus, compress them.  I forgot one key thing and &lt;a href="http://leddt.myopenid.com/"&gt;leddt&lt;/a&gt; was good enough to point it out.  Because each page you load on the site has the javascript inlined there is no way to cache it.  &lt;br /&gt;&lt;br /&gt;So what do we do?  I think the best solution is to stop compressing the javascript on request.  Instead combine and compress the javascript as part of the build process and then serve it up as a separate request.  The disadvantage here is that if you use a library like jquery-ui on only one page you end up downloading it for any page the user visits.  However the price you pay in getting that is a one time cost while using the terrible solution I suggested before you pay for it again and again and again.  In that way it is much like not taking out the trash, I have to hear about it every day plus it smells.  You wouldn't believe how hard it is to get the smell out of the fur of the 40 dogs in the puppy mill I run in my garage.  &lt;br /&gt;&lt;br /&gt;How things are cached on a browser has always been a mystery to me and there isn't a whole lot on the internet about the technicalities of what browsers do and do not cache.  Basically it seems to come down to the cache-control header which govern how devices retain content.  The ever so verbose W3C HTTP 1.1 spec defines the grammar for cache-control as&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Cache-Control   = "Cache-Control" ":" 1#cache-directive&lt;br /&gt;    cache-directive = cache-request-directive&lt;br /&gt;         | cache-response-directive&lt;br /&gt;    cache-request-directive =&lt;br /&gt;           "no-cache"                          ; Section 14.9.1&lt;br /&gt;         | "no-store"                          ; Section 14.9.2&lt;br /&gt;         | "max-age" "=" delta-seconds         ; Section 14.9.3, 14.9.4&lt;br /&gt;         | "max-stale" [ "=" delta-seconds ]   ; Section 14.9.3&lt;br /&gt;         | "min-fresh" "=" delta-seconds       ; Section 14.9.3&lt;br /&gt;         | "no-transform"                      ; Section 14.9.5&lt;br /&gt;         | "only-if-cached"                    ; Section 14.9.4&lt;br /&gt;         | cache-extension                     ; Section 14.9.6&lt;br /&gt;     cache-response-directive =&lt;br /&gt;           "public"                               ; Section 14.9.1&lt;br /&gt;         | "private" [ "=" &lt;"&gt; 1#field-name &lt;"&gt; ] ; Section 14.9.1&lt;br /&gt;         | "no-cache" [ "=" &lt;"&gt; 1#field-name &lt;"&gt; ]; Section 14.9.1&lt;br /&gt;         | "no-store"                             ; Section 14.9.2&lt;br /&gt;         | "no-transform"                         ; Section 14.9.5&lt;br /&gt;         | "must-revalidate"                      ; Section 14.9.4&lt;br /&gt;         | "proxy-revalidate"                     ; Section 14.9.4&lt;br /&gt;         | "max-age" "=" delta-seconds            ; Section 14.9.3&lt;br /&gt;         | "s-maxage" "=" delta-seconds           ; Section 14.9.3&lt;br /&gt;         | cache-extension                        ; Section 14.9.6&lt;br /&gt;    cache-extension = token [ "=" ( token | quoted-string ) ]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Pretty simple.  The fields you have to watch out for are max-age and public/private.  The age is how long the cache is permitted to retain the document before it must rerequest it and public indicates the page is public and should be cached for all users.  How the actual browsers will implement these is a function of the users so all you can do is make sure your site obeys the standard.&lt;br /&gt;&lt;br /&gt;But don't trust me, I'm just a blogger and I already lied to you once.  In my next post I'll talk a bit about caching in ASP.net and how to save database trips.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-8428657579133578662?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/8428657579133578662/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/08/caching-and-why-you-shouldnt-listen-to.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/8428657579133578662'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/8428657579133578662'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/08/caching-and-why-you-shouldnt-listen-to.html' title='Caching and Why You Shouldn&apos;t Listen to Blogs'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-1059403317880168750</id><published>2009-08-19T12:37:00.000-07:00</published><updated>2009-08-19T13:08:54.209-07:00</updated><title type='text'>Bookmarklet for MSDN</title><content type='html'>Today I was cruising the old MSDN using their much better &lt;a href="http://www.hanselman.com/blog/LowBandwidthViewAndOtherHiddenAndFutureFeaturesOfMSDN.aspx"&gt;low bandwidth&lt;/a&gt; version when I stumbled across &lt;a href="http://msdn.microsoft.com/en-us/library/aa645739%28VS.71,loband%29.aspx"&gt;a page on events in C#&lt;/a&gt;.  What got my attention was the example code all grey and boring not to mention hard to follow.  What this page needed was a little bit of &lt;a href="http://alexgorbatchev.com/wiki/SyntaxHighlighter"&gt;SyntaxHighlighter&lt;/a&gt; Alex Gorbatchev's glorious javascript library which add syntax to source code.  I use it right here on my blog as does everybody else who passes the coolness test.  The test, of course, being the use of SyntaxHighlighter.  I hacked at the jQueryify bookmarklet and managed to get it to load the correct SyntaxHighlighter libraries and stylesheets.  The next time you're on MSDN squinting at a piece of code try hitting MSDN Style.  &lt;del&gt;It only works on firefox at the moment but I'll update it to work on IE as well.&lt;/del&gt;  Simply drag this link to your bookmarks bar and you're good to go:&lt;br /&gt;&lt;br /&gt;&lt;a href="javascript:%20(function(){%20function%20getScript(url,success){%20var%20script=document.createElement('script');%20script.src=url;%20var%20head=document.getElementsByTagName(%22head%22)[0],%20done=false;%20script.onload=script.onreadystatechange%20=%20function(){%20if%20(%20!done%20&amp;&amp;%20(!this.readyState%20||%20this.readyState%20==%20%22loaded%22%20||%20this.readyState%20==%20%22complete%22)%20)%20{%20done=true;%20success();%20}%20};%20head.appendChild(script);%20};%20getScript('http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shCore.js',function(){});%20getScript('http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushCSharp.js',function(){});%20getScript('http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js',function()%20{%20loadStyle('http://alexgorbatchev.com/pub/sh/2.0.320/styles/shCore.css');%20loadStyle('http://alexgorbatchev.com/pub/sh/2.0.320/styles/shThemeDefault.css');%20return%20completeLoad();%20});%20function%20loadStyle(url)%20{%20style=document.createElement('link');%20style.href%20=%20url;%20style.rel%20=%20%22stylesheet%22;%20style.type=%22text/css%22;%20document.getElementsByTagName(%22head%22)[0].appendChild(style);%20};%20function%20completeLoad()%20{%20$(%22.libCScode:not(div)%22).addClass(%22brush:%20csharp%22);%20SyntaxHighlighter.highlight();%20};%20})();"&gt;MSDN Style&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Source&lt;/h2&gt;&lt;br /&gt;&lt;pre class="javascript" name="code"&gt;&lt;br /&gt;javascript:%20(function(){&lt;br /&gt; function getScript(url,success){&lt;br /&gt;  var script=document.createElement('script');&lt;br /&gt;  script.src=url;&lt;br /&gt;  var head=document.getElementsByTagName("head")[0], done=false;&lt;br /&gt;  script.onload=script.onreadystatechange = function(){&lt;br /&gt;    if ( !done &amp;&amp; (!this.readyState ||&lt;br /&gt;     this.readyState == "loaded" || this.readyState == "complete") ) {&lt;br /&gt;   done=true;&lt;br /&gt;   success();&lt;br /&gt;    }&lt;br /&gt;  };&lt;br /&gt;  head.appendChild(script);&lt;br /&gt;  };&lt;br /&gt;  getScript('http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shCore.js',function(){});&lt;br /&gt;  getScript('http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushCSharp.js',function(){});&lt;br /&gt;  getScript('http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js',function() {&lt;br /&gt;  loadStyle('http://alexgorbatchev.com/pub/sh/2.0.320/styles/shCore.css');&lt;br /&gt;  loadStyle('http://alexgorbatchev.com/pub/sh/2.0.320/styles/shThemeDefault.css');&lt;br /&gt;      return completeLoad();&lt;br /&gt;  });&lt;br /&gt;  function loadStyle(url)&lt;br /&gt; {&lt;br /&gt;  style=document.createElement('link');&lt;br /&gt;  style.href = url;&lt;br /&gt;  style.rel = "stylesheet";&lt;br /&gt;  style.type="text/css";&lt;br /&gt;  document.getElementsByTagName("head")[0].appendChild(style);&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;  function completeLoad() {&lt;br /&gt;     $(".libCScode:not(div)").addClass("brush: csharp");&lt;br /&gt;     &lt;br /&gt;     SyntaxHighlighter.highlight();&lt;br /&gt;  };&lt;br /&gt;})();  &lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-1059403317880168750?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/1059403317880168750/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/08/bookmarklet-for-msdn.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1059403317880168750'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1059403317880168750'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/08/bookmarklet-for-msdn.html' title='Bookmarklet for MSDN'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-6850662390017077072</id><published>2009-08-07T10:42:00.001-07:00</published><updated>2009-08-07T10:44:40.137-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='code'/><title type='text'>A Wonderful Example of Comments Causing Problems</title><content type='html'>Current thinking is that comments in code should be helpful and should only document why and not how, that is what the code is for.  That wasn't always the case, for example&lt;br /&gt;&lt;br /&gt;&lt;pre class="php" name="code"&gt;&lt;br /&gt;$courseNick = $_POST['courseNick']; //Course nick&lt;br /&gt;$phone = $_POST['phone'];           //Course nick&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;See how useless that is?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-6850662390017077072?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/6850662390017077072/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/08/wonderful-example-of-comments-causing.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/6850662390017077072'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/6850662390017077072'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/08/wonderful-example-of-comments-causing.html' title='A Wonderful Example of Comments Causing Problems'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-5213701932603408461</id><published>2009-08-07T07:10:00.000-07:00</published><updated>2009-08-07T07:56:40.463-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='software bugs'/><title type='text'>A watershed moment</title><content type='html'>News in the twitterverse today is all about &lt;a href="http://infoworld.com/t/software-licensing/watch-out-developers-here-come-lawyers-436"&gt;this article&lt;/a&gt; at inforworld or, more precisely, the guidelines produced by the &lt;a href="http://en.wikipedia.org/wiki/American_Law_Institute"&gt;American Law Institute&lt;/a&gt;.  In a small section they suggest that software companies should be held liable for shipping software with known bugs.  Damn right they should.  &lt;br /&gt;&lt;br /&gt;The software world has long survived protected from their own mistakes through the EULA.  Let's look at a license agreement, perhaps the Windows XP license agreement as a typical example.  Here is a excerpt from section 2.15:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;LIMITATION ON REMEDIES; NO CONSEQUENTIAL OR OTHER DAMAGES. Your exclusive remedy for any breach of this Limited Warranty is as set forth below. Except for any refund elected by Microsoft, YOU ARE NOT ENTITLED TO ANY DAMAGES, INCLUDING BUT NOT LIMITED TO CONSEQUENTIAL DAMAGES&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;So if Windows XP crashes and you loose an assignment or if your &lt;a href="http://en.wikipedia.org/wiki/USS_Yorktown_%28CG-48%29#Smart_ship_testbed"&gt;battleship&lt;/a&gt; sinks Microsoft is sorry but they aren't going to stand up and take responsibility.  At least not more than the purchase price of Windows and I'll bet you it is a trial to get them to cough up even that.  A lot of people are upset about this guideline.  By a lot of people I, of course, mean software vendors.  I suppose I too would be upset if I shipped known bad software, but I don't because my customers deserve more than that.  I'm not saying my software is perfect, it isn't, however it has no known bugs.  And that's the key right there: know bugs.  &lt;br /&gt;&lt;br /&gt;All software is going to have bugs in it, even with the most rigorous testing and test driven development there are still going to be issues.  Most of these bugs are not covered under the guidelines because a concerted effort was made to find bugs and fix them during the development process.  This doesn't mean that you can't ship software with know issues, it just means that now the risk of doing so is spread more evenly between you and your client.  Which is only right.  &lt;br /&gt;&lt;br /&gt;Drawing again on the tired car analogy people would be outraged if Ford shipped a car which they knew imploded in the rain.  Sure the car industry isn't a perfect analogy but it is pretty good.  Lots of software is responsible for our lives in the same way as cars, heck there is a huge amount of software in cars.  Why should software vendors be any less liable?  &lt;br /&gt;&lt;br /&gt;What does this really mean for companies?  In a word: transparency.  Let's say that I ship some software with an issue and it hurts somebody and they decide to sue.  It is going to cost me money in lawyers even if I had no idea that defect existed.  I've got to show that I didn't know the defect existed at shipping time.  That is going to require a bunch of e-discovery and searching of e-mails, costly.  What can I do to try to avoid being sued in the first place?  Easy, publish all the bugs in my software in a system the public can see.  I mean internal bugs as well as external bugs, everything.  Next I fix bugs before I write new code and I fix them promptly.  If I can gain a reputation as open and responsive people are far more likely to write off my mistakes as just that.  &lt;br /&gt;&lt;br /&gt;Now somebody is going to argue that publishing every defect puts me at a PR disadvantage compared to the guys down the street who don't advertise their bugs.  I don't believe that for a second.  People who buy software are generally not dumb, they know that bugs exist and they know that they're probably going to find some in the software they buy.  Do you want to deal with a company which &lt;a href="http://autonomy.com"&gt;won't admit that they have bugs&lt;/a&gt; and even threatens to &lt;a href="http://www.channelregister.co.uk/2007/12/06/autonomy_secunia_dust_up/"&gt;sue people who bring them issues&lt;/a&gt; or with one which has shown itself to be responsive to customer issues?  I'll take responsive every time.  &lt;br /&gt;&lt;br /&gt;The time has passed when software was used only for esoteric purposes by men in white coats and it is time the industry grew up and learned that if you get a paper route you can't dump the papers in the garbage without consequences.  Stop dumping bad code on the world.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-5213701932603408461?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/5213701932603408461/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/08/watershed-moment.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/5213701932603408461'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/5213701932603408461'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/08/watershed-moment.html' title='A watershed moment'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-4573373517679443269</id><published>2009-06-23T18:29:00.000-07:00</published><updated>2009-08-28T22:51:24.611-07:00</updated><title type='text'>HTML Helper for Including and Compressing Javascript</title><content type='html'>&lt;span style="background-color: #F1425B"&gt;This article is outdated and ill advised&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;If you're like me then you probably have a whole bunch of javascript includes on your site cluttering up the top of your page.  I tend to keep most of mine in Site.Master, it isn't so much that I use them all on every page but I do make use of a large enough subset that I can't be bothered to load them in each view.  For example here is what I have at the top of a Site.Master page for my Activity Tracker project:&lt;br /&gt;&lt;br /&gt;&lt;pre class="xml" name="code"&gt;&lt;br /&gt; &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/jquery-1.3.2.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt; &amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/jquery.tablesorter.min.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt; &amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/extjs/ext-jquery-adapter.js")%&gt;" language="javascript" type='text/javascript' &gt;&amp;#060;/script&gt; &amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/extjs/ext-all-debug.js")%&gt;" language="javascript" type='text/javascript' &gt;&amp;#060;/script&gt; &amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/jquery.autocomplete/js/jquery.autocomplete.js")%&gt;" language="javascript" type='text/javascript' &gt;&amp;#060;/script&gt; &amp;#060;br/&gt;   &amp;#060;br/&gt;   &amp;#060;!--Validation--&gt;&amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/xVal.jquery.validate.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt;&amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/jquery.validate/jquery.validate.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt;&amp;#060;br/&gt;   &amp;#060;br/&gt;   &amp;#060;!--Graphs--&gt;&amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/flot/jquery.flot.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt; &amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/flot/excanvas.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt; &amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/flot/ext.flot.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt; &amp;#060;br/&gt;   &amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/jquery-ui-1.7.1.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This thing was not only a mess but it was also amazingly inefficient.  Each time the web browser opens a connection to the server there is overhead, if you put everything in one file then you can save on this overhead.  Apparently you can only have &lt;a href="http://www.ajaxperformance.com/2006/12/18/circumventing-browser-connection-limits-for-fun-and-profit/"&gt;two open connections&lt;/a&gt; per domain so you don't really get any advantage from having multiple .js file which run parallel.  I was starting to notice the overhead just opening up pages from my localhost.  Things would get bad if I deployed this to the real world.  I went through a couple of possible solutions:&lt;br /&gt;&lt;br /&gt;1.  Manually combine the javascript files, they change infrequently &lt;br /&gt;2.  Combine the javascript during the build and produce just one file&lt;br /&gt;3.  Use edge caches of the javascript files&lt;br /&gt;&lt;br /&gt;Manual combination of the files was out right away, I don't like doing work over and over again that is what computers are for.  I liked the idea of 2 because I'm a huge &lt;a href="http://www.amazon.com/Build-Master-Microsofts-Configuration-Addison-Wesley/dp/0321332059"&gt;build guy&lt;/a&gt; and to do that I would get to play with msbuild or nant or something like that.  However I liked being able to make javascript change without rebuilding the site.  &lt;br /&gt;&lt;br /&gt;Solution #3, using edge caches, seemed like a great plan.  The issue is that these systems are not always reliable, not from a always up point of view but from a changes point of view.  I couldn't govern when they made changes to the scripts and I would have to use stock versions of all the scripts.  The autocomplete script I used is slightly modified for my purposes so #3 was out.  &lt;br /&gt;&lt;br /&gt;I decided that what I needed was a home grown solution so I came up with Html.IncludeScriptLibraries.  This is a new extension method (I'm in to those at the moment) which loaded javascript files for me.  &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt; public static string IncludeScriptLibraries(this HtmlHelper helper, string basePath, params string[] scripts)&lt;br/&gt;        {&lt;br/&gt;            String contents = "";&lt;br/&gt;            foreach(String script in scripts)&lt;br/&gt;            {&lt;br/&gt;                String fileName = basePath + "\\" +  script.Replace("/", "\\");&lt;br/&gt;                if (File.Exists(fileName))&lt;br/&gt;                {&lt;br/&gt;                    StreamReader reader = File.OpenText(fileName);&lt;br/&gt;                    contents += reader.ReadToEnd();&lt;br/&gt;                reader.Close();&lt;/br&gt;            }&lt;br/&gt;            }&lt;br/&gt;            return "&amp;#060;script language=\"javascript\" type=\"text/javascript\"&gt;" + contents + "&amp;#060;/script&gt;";&lt;br/&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This simple function opens a list of script files from some directory and concatenates them into one string.  This is then returned.  The master page now has &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;&amp;#060;%= Html.IncludeScriptLibraries(Server.MapPath(ResolveClientUrl("~/Scripts")), &lt;br /&gt;        "jquery-1.3.2.js",&lt;br /&gt;        "jquery.tablesorter.min.js",&lt;br /&gt;        "extjs/ext-jquery-adapter.js",      &lt;br /&gt;        "extjs/ext-all-debug.js",&lt;br /&gt;        "jquery.autocomplete/js/jquery.autocomplete.js",&lt;br /&gt;        "xVal.jquery.validate.js",&lt;br /&gt;        "jquery.validate/jquery.validate.js",&lt;br /&gt;        "flot/jquery.flot.js",&lt;br /&gt;        "flot/excanvas.js",&lt;br /&gt;        "flot/ext.flot.js",&lt;br /&gt;        "jquery-ui-1.7.1.js"&lt;br /&gt;           )%&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Pretty nifty.  Now we have this library inclusion abstracted into a function we can add some other cool stuff.  I grabbed a .net port of the pack javascript library from &lt;a href="http://dean.edwards.name/packer/"&gt;Dean Edwards &lt;/a&gt; and turned it into a library in my project.  I changed the extension method to return &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;ECMAScriptPacker packer = new ECMAScriptPacker();&lt;br/&gt;return "&amp;#060;script language=\"javascript\" type=\"text/javascript\"&gt;" + packer.Pack(contents) + "&amp;#060;/script&gt;";&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Which should have nicely packed up the files.  However the library seems to munge javascript as does the sample application, which is a pitty.  If it had worked that would be pretty cool.  &lt;br /&gt;&lt;br /&gt;This extension method is a way to centralize the loading of javascript onto a pages and provides the ability to preprocess it through compression or anything else.  A caching level should be added to avoid hitting the disk with every query.&lt;br /&gt;&lt;br /&gt;As always I'm curious about possible improvements to this.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-4573373517679443269?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/4573373517679443269/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/06/html-helper-for-including-and.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4573373517679443269'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4573373517679443269'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/06/html-helper-for-including-and.html' title='HTML Helper for Including and Compressing Javascript'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-7612148345560753137</id><published>2009-06-22T19:29:00.000-07:00</published><updated>2009-06-24T09:15:58.352-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='graphs'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><category scheme='http://www.blogger.com/atom/ns#' term='asp.net mvc'/><title type='text'>Bar Graphs Using Flot and ASP.net MVC - Part II</title><content type='html'>I wasn't really planning a part II to this article but I left the code in something of a mess and we can do much better than having to write a bunch of javascript each time we need to make a graph.  Let's use the HtmlHelper and extension methods.  The HtmlHelper is a class in System.Web.Mvc which is often extended to supply shortcuts for writing out HTML.  xVal extends it to add client side validation and we're going to do the same thing to write out the javascript for our graphs.  We start by creating a new extension class to complement HtmlHelper&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;public static class HTMLExtensions&lt;br/&gt;    {&lt;br/&gt;        public static string BarGraph(this HtmlHelper helper, string divName, IDictionary&amp;#060;String, int&gt; dataPoints)&lt;br/&gt;        {&lt;br/&gt;            String dataPointsArrayName = divName + "Values";&lt;br/&gt;            String dataPointLabelsArrayName = divName + "Labels";&lt;br/&gt;            &lt;br/&gt;            &lt;br/&gt;            String returnText = @"&amp;#060;script language=""javascript""&gt;&lt;br/&gt;                                    var {0} = ["; &lt;br/&gt;&lt;br/&gt;            int counter = 0;&lt;br/&gt;            foreach (KeyValuePair&amp;#060;string, int&gt; kvp in dataPoints)&lt;br/&gt;            {&lt;br/&gt;&lt;br/&gt;                if (counter &gt; 0)&lt;br/&gt;                    returnText += ",";&lt;br/&gt;                returnText += "[" + counter + ".5, " + kvp.Value + "]";&lt;br/&gt;                counter++;&lt;br/&gt;            }&lt;br/&gt;            returnText += "];\n";&lt;br/&gt;&lt;br/&gt;            returnText += @"var {1} = ["; &lt;br/&gt;            counter = 1;&lt;br/&gt;&lt;br/&gt;            foreach (KeyValuePair&amp;#060;string, int&gt; kvp in dataPoints)&lt;br/&gt;            {&lt;br/&gt;                if (counter &gt; 1)&lt;br/&gt;                    returnText += ",";&lt;br/&gt;                returnText += "[" + counter + ",\"" + kvp.Key + "\"]";&lt;br/&gt;                counter++;&lt;br/&gt;            }&lt;br/&gt;            returnText += "];\n";&lt;br/&gt;&lt;br/&gt;            returnText += @"$(document).ready(function() &lt;br/&gt;                            {&lt;br/&gt;                                $.plot($(""#{2}""), [{0}], { &lt;br/&gt;                                  series: {&lt;br/&gt;                                    data: {0}, &lt;br/&gt;                                    color: ""rgb(182,188,194)""  &lt;br/&gt;                                    }, &lt;br/&gt;                                  bars: { show: true, barWidth: 1.0 }, &lt;br/&gt;                                  yaxis: { min: 0 },&lt;br/&gt;                                  xaxis: { ticks: {1}} &lt;br/&gt;                                  });&lt;br/&gt;                            });";&lt;br/&gt;&lt;br/&gt;            returnText += "&amp;#060;/script&gt;";&lt;br/&gt;&lt;br/&gt;            returnText = returnText.Replace("{0}",dataPointsArrayName).Replace("{1}", dataPointLabelsArrayName).Replace("{2}", divName);&lt;br/&gt;&lt;br/&gt;            return returnText.ToString();&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;    }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This class is a bit of a mess of string appending but hopefully changes to it will be infrequent.  I know somebody is going to jump on my string appending but before you do take a read of Jeff Atwood's excellent examination of &lt;a href="http://www.codinghorror.com/blog/archives/001218.html"&gt;micro optimization&lt;/a&gt;.  The key take away is that all the messy javascript generation which we did have in the views is now centralized into one place.  &lt;br /&gt;&lt;br /&gt;So now we have an extension method we can call from our View, so let's pop back over to that.  The page has two graphs on it and it use to have a huge amount of hand coded javascript.  That can all be replaced with &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;&amp;#060;%Dictionary&amp;#060;string, int&gt; topTeamsDictionary = (ViewData["TopTeams"] as List&amp;#060;TeamTotal&gt;).ToDictionary(n =&gt; n.team.teamName, n =&gt; n.total);&lt;br/&gt;Dictionary&amp;#060;string, int&gt; userActivityTotals = (ViewData["UserActivityTotals"] as List&amp;#060;UserActivityTotal&gt;).ToDictionary(n =&gt; n.ActivityName, n =&gt; n.TotalPoints);%&gt;&lt;/br&gt;&lt;br /&gt;&amp;#060;%= Html.BarGraph("teamPointsGraph", topTeamsDictionary)%&gt;&lt;br/&gt;&amp;#060;%= Html.BarGraph("UserActivityTotalsGraph", userActivityTotals)%&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The data in the ViewData was in a list and that list is need by other things on the page so we quickly transform the List into a Dictionary using LINQ.  That is pretty much it.  So long as the div referenced in the call to BarGraph exists you should get the same stylish graphs as yesterday.  Obviously there are a lot of other options which can be passed through to the graph, these are left as an exercise for the reader.  I've always wanted to say that.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-7612148345560753137?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/7612148345560753137/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/06/bar-graphs-using-flot-and-aspnet-mvc_22.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/7612148345560753137'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/7612148345560753137'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/06/bar-graphs-using-flot-and-aspnet-mvc_22.html' title='Bar Graphs Using Flot and ASP.net MVC - Part II'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-4660646710060618590</id><published>2009-06-21T18:51:00.001-07:00</published><updated>2009-06-21T20:00:14.129-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='graphs'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><category scheme='http://www.blogger.com/atom/ns#' term='flot'/><category scheme='http://www.blogger.com/atom/ns#' term='asp.net mvc'/><title type='text'>Bar Graphs Using Flot and ASP.net MVC</title><content type='html'>There are &lt;a href="http://blog.rebeccamurphey.com/2007/12/17/graph-table-data-jquery-flot/"&gt;a&lt;/a&gt; &lt;a href="http://blog.bobpeers.com/tag/flot/"&gt;bunch&lt;/a&gt; of posts out there about using &lt;a href="http://code.google.com/p/flot/"&gt;flot&lt;/a&gt; to create nifty HTML graphs but the Internet is all about reiteration and I need to start posting more frequently.   Flot is a javascript library which uses &lt;a href="http://en.wikipedia.org/wiki/Canvas_(HTML_element)"&gt;HTML Canvas&lt;/a&gt; from HTML 5 to draw simple vector graphics.  It works natively on good browsers and with a little bit of hacking on IE8 too.   At its most simple one needs only include the jquery libraries and the flot libraries.  Technically the excanvas library needs only be included for IE but it doesn't seem to do any harm to include it all the time and the packed version is just a few kb.   &lt;br /&gt;&lt;br /&gt;&lt;pre class="xml" name="code"&gt;&lt;br /&gt;&amp;#060;script src="&amp;#060;%=ResolveClientUrl("~/Scripts/jquery-1.3.2.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt;&lt;br /&gt;&lt;!--Graphs--&gt;&lt;br /&gt;&amp;#060;script src="&amp;#060%=ResolveClientUrl("~/Scripts/flot/jquery.flot.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt;&amp;#060;script language="javascript" type="text/javascript" src="&lt;%=ResolveClientUrl("~/Scripts/flot/excanvas.pack.js")%&gt;"&gt;&amp;#060;/script&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;As you can see I always use the ResolveClientURL function to resolve paths to javascript libraries.  This section is added to my Site.Master but if your site doesn't include graphs on every page then it would be more efficient to include it only on the pages with graphs.  For the purposes of this post we're also going to add &lt;br /&gt;&lt;br /&gt;&lt;pre class="xml" name="code"&gt;&lt;br /&gt;&amp;#060;script src="&lt;%=ResolveClientUrl("~/Scripts/flot/ext.flot.js") %&gt;" language="javascript" type="text/javascript"&gt;&amp;#060;/script&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;which adds some extensions to the normal flot package.  We're most interested in tick labels on the x-axis.  More on that later.  Now we need to get some data ready to be graphed.  I have a handy function in the repository for fetching a list of data objects, in the controller for the page I call&lt;br /&gt;&lt;br /&gt;&lt;pre class="c#" name="code"&gt;&lt;br /&gt;public ActionResult Index()&lt;br/&gt;        {&lt;br/&gt;           ViewData["UserActivityTotals"] repository.getUserActivityTotals(user.userID);&lt;br/&gt;           return View()&lt;br/&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This returns a collection of UserActivityTotal objects which are lightweight objects&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;namespace ActivityTracker.Models&lt;br /&gt;{&lt;br/&gt;    public class UserActivityTotal&lt;br/&gt;    {&lt;br/&gt;        public string ActivityName { get; set; }&lt;br/&gt;        public int TotalPoints { get; set; }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now in the view I print out this information into javascript&lt;br /&gt;&lt;br /&gt;&lt;pre class="c#" name="code"&gt;&lt;br /&gt;var userActivitiesNames = [&lt;% &lt;br/&gt;            counter = 1;&lt;br/&gt;            if(null != ViewData["UserActivityTotals"])&lt;br/&gt;            {&lt;br/&gt;                foreach(UserActivityTotal uat in ViewData["UserActivityTotals"] as List&amp;#060;UserActivityTotal&gt;)&lt;br/&gt;                {&lt;br/&gt;                    if(counter &gt; 1)&lt;br/&gt;                        Response.Write(",");&lt;br/&gt;                    Response.Write("[" + counter + ",\"" + uat.ActivityName + "\"]");&lt;br/&gt;                    counter ++;&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;        &lt;br /&gt;         %&gt;];&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That counter thing is in there because javascript can't handle a trailing comma in array definitions unlike Perl or Ruby.  That would be a really nice thing to fix for the next version of javascript.  Anyway this results in something which looks like &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="JavaScript"&gt;&lt;br /&gt;var userActivities = [[0.5, 1899],[1.5, 157],[2.5, 904]];&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We're just putting in a monatomically increasing series for the x value.  These ensure that the columns don't overlap or have large gaps between them.  Why don't we just put in the values which we have in the ActivityName field?  Because flot doesn't handle x-axis labels which are not numerical or dates.  For that we have to start using the extension to flot which we included earlier.  First let's just get the graph onto the page.  &lt;br /&gt;&lt;br /&gt;Add a div to define the size and location of the graph&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt;&lt;div id="UserActivityTotalsGraph" style="width:600px;height:300px;"&gt;&lt;/div&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and a snippet of javascript which sets up the graph object and assigns it to the div&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="JavaScript"&gt;&lt;br /&gt;$.plot($("#UserActivityTotalsGraph"), [userActivities], {&lt;br/&gt;                                label: "Points",&lt;br/&gt;                                series: {&lt;br/&gt;                                    data: userActivities,&lt;br/&gt;                                    color: "rgb(182,188,194)"&lt;br/&gt;                                },&lt;br/&gt;                                bars: { show: true, barWidth: 1.0 },&lt;br/&gt;                                yaxis: { min: 0 }&lt;br/&gt;                            });&lt;br/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_ApswAzQLHYs/Sj7seJk-r4I/AAAAAAAAAAM/MGaLurvY1kI/s1600-h/flotgraph1.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 178px;" src="http://3.bp.blogspot.com/_ApswAzQLHYs/Sj7seJk-r4I/AAAAAAAAAAM/MGaLurvY1kI/s320/flotgraph1.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5349973410073980802" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;That looks pretty nice except for the labels on the bottom.  To get those working we hop back into the view and add an xaxis line to the plot call.  &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="JavaScript"&gt;&lt;br /&gt;$.plot($("#UserActivityTotalsGraph"), [userActivities], {                                label: "Points",&lt;br/&gt;                                series: {&lt;br/&gt;                                    data: userActivities,&lt;br/&gt;                                    color: "rgb(182,188,194)"&lt;br/&gt;                                },&lt;br/&gt;                                bars: { show: true, barWidth: 1.0 },&lt;br/&gt;                                yaxis: { min: 0 },&lt;br/&gt;                                xaxis: { ticks: userActivitiesNames}&lt;br/&gt;                            });&lt;br/&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This uses another array called userActivitiesNames which we define with &lt;br /&gt;&lt;br /&gt;&lt;pre class="c#" name="code"&gt;&lt;br /&gt;var userActivitiesNames = [&lt;%&lt;br/&gt;            counter = 1;&lt;br/&gt;            if(null != ViewData["UserActivityTotals"])&lt;br/&gt;            {&lt;br/&gt;                foreach(UserActivityTotal uat in ViewData["UserActivityTotals"] as List&amp;#060;UserActivityTotal&gt;)&lt;br/&gt;                {&lt;br/&gt;                    if(counter &gt; 1)&lt;br/&gt;                        Response.Write(",");&lt;br/&gt;                    Response.Write("[" + counter + ",\"" + uat.ActivityName + "\"]");&lt;br/&gt;                    counter ++;&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;       &lt;br /&gt;         %&gt;];&lt;br/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And we're done!  The final product looks like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_ApswAzQLHYs/Sj7tuuQ2ViI/AAAAAAAAAAU/jWDZbCgEsa8/s1600-h/flotgraph2.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 400px; height: 220px;" src="http://1.bp.blogspot.com/_ApswAzQLHYs/Sj7tuuQ2ViI/AAAAAAAAAAU/jWDZbCgEsa8/s400/flotgraph2.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5349974794311194146" /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-4660646710060618590?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/4660646710060618590/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/06/bar-graphs-using-flot-and-aspnet-mvc.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4660646710060618590'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4660646710060618590'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/06/bar-graphs-using-flot-and-aspnet-mvc.html' title='Bar Graphs Using Flot and ASP.net MVC'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_ApswAzQLHYs/Sj7seJk-r4I/AAAAAAAAAAM/MGaLurvY1kI/s72-c/flotgraph1.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-2883188868649807659</id><published>2009-04-29T20:33:00.000-07:00</published><updated>2009-04-29T21:13:23.081-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='AdobeAir ant'/><title type='text'>Building Adobe Air Files</title><content type='html'>Assembling Adobe Air installers is pretty easy.  In this article I'll show you how to put together an ant build.xml file which will build for you.  &lt;br /&gt;&lt;br /&gt;For the incurably impatient here is the whole thing:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt;&lt;project default="dist"&gt;&lt;br /&gt;&lt;br /&gt;        &lt;property name="airFile" value="WopsleNet.air"&gt;&lt;/property&gt;&lt;br /&gt;        &lt;property name="keyFile" value="wopsleCert.pfx"&gt;&lt;/property&gt;&lt;br /&gt;&lt;br /&gt;        &lt;target name="clean"&gt;&lt;br /&gt;                &lt;delete file="${airFile}"&gt;&lt;/delete&gt;&lt;br /&gt;        &lt;/target&gt;&lt;br /&gt;        &lt;br /&gt;        &lt;target name="generateKey"&gt;&lt;br /&gt;                &lt;exec executable="c:\WINDOWS\system32\cmd.exe"&gt;&lt;br /&gt;                        &lt;arg value="/C"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="c:\hudson\tools\AdobeAir\bin\adt.bat"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="-certificate"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="-cn"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value=" Wopsle"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="1024-RSA"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="${keyFile}"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="helloWorld"&gt;&lt;/arg&gt;&lt;br /&gt;                    &lt;/exec&gt;&lt;br /&gt;                    &lt;sleep seconds="2"&gt;&lt;/sleep&gt;&lt;br /&gt;        &lt;/target&gt;&lt;br /&gt;&lt;br /&gt;        &lt;br /&gt;        &lt;target name="dist" depends="generateKey"&gt;&lt;br /&gt;                &lt;br /&gt;                &lt;property environment="env"&gt;&lt;/property&gt;&lt;br /&gt;&lt;br /&gt;                &lt;replace file="application.xml" token="${buildNumber}" value="${env.SVN_REVISION}"&gt;&lt;/replace&gt;&lt;br /&gt;                &lt;br /&gt;                &lt;exec executable="c:\hudson\tools\AdobeAir\bin\adt.bat"&gt;&lt;br /&gt;                   &lt;arg value="-package"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="-storetype"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="pkcs12"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="-storepass"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="helloWorld"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="-keystore"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="${keyFile}"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="${airFile}"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="application.xml"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;br /&gt;                   &lt;!--contents--&gt;&lt;br /&gt;                   &lt;arg value="css"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="html"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="icons"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="img"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="js"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="lib"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;br /&gt;                   &lt;arg value="Client.html"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="config.js"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;br /&gt;                &lt;/exec&gt;&lt;br /&gt;        &lt;/target&gt;&lt;br /&gt;&lt;/project&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Let's break it down a bit &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt;&lt;br /&gt; &lt;property name="airFile" value="WopsleNet.air"&gt;&lt;/property&gt;&lt;br /&gt; &lt;property name="keyFile" value="wopsleCert.pfx"&gt;&lt;/property&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here we set up some properties to be used in the rest of the file. I'm sure I don't have to quote &lt;a href="http://www.pragprog.com/the-pragmatic-programmer/extracts/tips"&gt;the Pragmatic Programmer&lt;/a&gt; about repeating yourself.  &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt;        &lt;target name="generateKey"&gt;&lt;br /&gt;                &lt;exec executable="c:\WINDOWS\system32\cmd.exe"&gt;&lt;br /&gt;                        &lt;arg value="/C"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="c:\hudson\tools\AdobeAir\bin\adt.bat"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="-certificate"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="-cn"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value=" Wopsle"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="1024-RSA"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="${keyFile}"&gt;&lt;/arg&gt;&lt;br /&gt;                        &lt;arg value="helloWorld"&gt;&lt;/arg&gt;&lt;br /&gt;                    &lt;/exec&gt;&lt;br /&gt;                    &lt;sleep seconds="2"&gt;&lt;/sleep&gt;&lt;br /&gt;        &lt;/target&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Air applications have to be signed.  As part of a release process you'll want to generate a proper key and keep in somewhere safe like on a cd in the cat's litter box.  However we don't want developer builds getting out into the wild so we'll just generate a key here and use it.  You'll notice on line 12 we sleep for a couple of seconds.  Why is that?  It turns out that the key generator returns before it is actually done generating so we'll give it a few more seconds otherwise you'll get an error like&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;failed while unpackaging: [ErrorEvent type="error" bubbles=false cancelable=false eventPhase=2 text="invalid package signature" errorID=5022]&lt;br /&gt;starting cleanup of temporary files&lt;br /&gt;application installer exiting&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;in the Air installation log.  (See &lt;a href="http://kb.adobe.com/selfservice/viewContent.do?externalId=kb403123"&gt;http://kb.adobe.com/selfservice/viewContent.do?externalId=kb403123&lt;/a&gt; to read how to enable logging of Air installations)&lt;br /&gt;&lt;br /&gt;Finally we come to actually building the application &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt; &lt;target name="dist" depends="generateKey"&gt;&lt;br /&gt;                &lt;br /&gt;                &lt;property environment="env"&gt;&lt;/property&gt;&lt;br /&gt;&lt;br /&gt;                &lt;replace file="application.xml" token="${buildNumber}" value="${env.SVN_REVISION}"&gt;&lt;/replace&gt;&lt;br /&gt;                &lt;br /&gt;                &lt;exec executable="c:\hudson\tools\AdobeAir\bin\adt.bat"&gt;&lt;br /&gt;                   &lt;arg value="-package"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="-storetype"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="pkcs12"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="-storepass"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="helloWorld"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="-keystore"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="${keyFile}"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="${airFile}"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="application.xml"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;br /&gt;                   &lt;!--contents--&gt;&lt;br /&gt;                   &lt;arg value="css"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="html"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="icons"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="img"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="js"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="lib"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;br /&gt;                   &lt;arg value="Client.html"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;arg value="config.js"&gt;&lt;/arg&gt;&lt;br /&gt;                   &lt;br /&gt;                &lt;/exec&gt;&lt;br /&gt;        &lt;/target&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here I'm replacing teh version number in the application.xml with the SVN_REVISION.   Our builds run from inside the wonderful &lt;a href="http://hudson.dev.java.net/"&gt;Hudson build management system&lt;/a&gt; which is kind enough to pass through a token containing the revision number from SVN.  We finally execute adt the Air packaging tool giving it a list of the files we would like to have inside the package.  We list them by hand as a double check against accidentally including extra files, like bankingInformation.xls.  &lt;br /&gt;&lt;br /&gt;That's it!  The only real trick is pausing for the key generation to complete.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-2883188868649807659?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/2883188868649807659/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/04/building-adobe-air-files.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/2883188868649807659'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/2883188868649807659'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/04/building-adobe-air-files.html' title='Building Adobe Air Files'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-8030124468506553983</id><published>2009-04-18T14:28:00.000-07:00</published><updated>2009-04-18T15:14:28.301-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='asp.net mvc jquery'/><title type='text'>ASP.net MVC returning JSONP</title><content type='html'>I've been working on a piece of code which returns &lt;a href="http://www.json.org/"&gt;JSON &lt;/a&gt;in response to some request.  It has all being going fine on the local host but I've just deployed it up to my new test server and started to access it using JQuery and it stooped working.  This is, of course, typical.  This time however, my problem was ignorance rather than a programming blunder.  As it turns out when returning JSON across domains you need to actually return JSONP in order to get call backs working.  What is &lt;a href="http://www.west-wind.com/Weblog/posts/107136.aspx"&gt;JSONP&lt;/a&gt;?  I'm glad you asked, because I had no idea either.  Basically it is the same JSON you love but wrapped with a bit of text which specifies the name of the function to call upon returning.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;JSON&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;{"userID":"00000000-0000-0000-0000-000000000000","success":"false"}&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;JSONP&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;somefunction({"userID":"00000000-0000-0000-0000-000000000000","success":"false"})&lt;br /&gt;&lt;br /&gt;Easy enough.  In JQuery you need to just add another parameter to the JSON call in order to pass the name of the function to the server&lt;br /&gt;&lt;pre style='color:#000000;background:#ffffff;'&gt;$&lt;span style='color:#808030; '&gt;.&lt;/span&gt;getJSON&lt;span style='color:#808030; '&gt;(&lt;/span&gt;URL &lt;span style='color:#808030; '&gt;+&lt;/span&gt; &lt;span style='color:#0000e6; '&gt;"/json/Message/sendMessage?userName="&lt;/span&gt; &lt;span style='color:#808030; '&gt;+&lt;/span&gt; $&lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#0000e6; '&gt;"#userName3"&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#808030; '&gt;.&lt;/span&gt;val&lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt; &lt;span style='color:#808030; '&gt;+&lt;/span&gt; &lt;br /&gt;                                                    &lt;span style='color:#0000e6; '&gt;"&amp;amp;messageText="&lt;/span&gt; &lt;span style='color:#808030; '&gt;+&lt;/span&gt; $&lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#0000e6; '&gt;"#message"&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#808030; '&gt;.&lt;/span&gt;val&lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt; &lt;span style='color:#808030; '&gt;+&lt;/span&gt;&lt;br /&gt;                                  &lt;span style='color:#0000e6; '&gt;"&amp;amp;userKey="&lt;/span&gt; &lt;span style='color:#808030; '&gt;+&lt;/span&gt; key &lt;span style='color:#808030; '&gt;+&lt;/span&gt; &lt;br /&gt;                                          &lt;span style='color:#0000e6; '&gt;"&amp;amp;jsoncallback=?"&lt;/span&gt;&lt;span style='color:#808030; '&gt;,&lt;/span&gt; &lt;br /&gt;                            &lt;span style='color:#800000; font-weight:bold; '&gt;function&lt;/span&gt;&lt;span style='color:#808030; '&gt;(&lt;/span&gt;json&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;                            &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;                            &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;                &lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;JQuery will automatically replace the ? with the name of your callback function, in this case an anonymous function.  &lt;br /&gt;&lt;br /&gt;Having discovered all of this I realized that I now had a ton of code returning JsonResults which needed to be changed.  I figured the best way to do this was to actually create a JsonpResult which was based off of the JsonResult.  So I did just that, basing it off of the now open sourced &lt;a href="http://aspnet.codeplex.com/Wiki/View.aspx?title=MVC"&gt;ASP.net MVC JsonResult&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre style='color:#000000;background:#ffffff;'&gt;&lt;span style='color:#800000; font-weight:bold; '&gt;public&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;class&lt;/span&gt; JsonpResult &lt;span style='color:#808030; '&gt;:&lt;/span&gt; System&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Web&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Mvc&lt;span style='color:#808030; '&gt;.&lt;/span&gt;JsonResult&lt;br /&gt;    &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800000; font-weight:bold; '&gt;public&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;override&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;void&lt;/span&gt; ExecuteResult&lt;span style='color:#808030; '&gt;(&lt;/span&gt;ControllerContext context&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800000; font-weight:bold; '&gt;if&lt;/span&gt; &lt;span style='color:#808030; '&gt;(&lt;/span&gt;context &lt;span style='color:#808030; '&gt;=&lt;/span&gt;&lt;span style='color:#808030; '&gt;=&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;null&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span style='color:#800000; font-weight:bold; '&gt;throw&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;new&lt;/span&gt; ArgumentNullException&lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#0000e6; '&gt;context&lt;/span&gt;&lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;br /&gt;            HttpResponseBase response &lt;span style='color:#808030; '&gt;=&lt;/span&gt; context&lt;span style='color:#808030; '&gt;.&lt;/span&gt;HttpContext&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Response&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span style='color:#800000; font-weight:bold; '&gt;if&lt;/span&gt; &lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#808030; '&gt;!&lt;/span&gt;String&lt;span style='color:#808030; '&gt;.&lt;/span&gt;IsNullOrEmpty&lt;span style='color:#808030; '&gt;(&lt;/span&gt;ContentType&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;                response&lt;span style='color:#808030; '&gt;.&lt;/span&gt;ContentType &lt;span style='color:#808030; '&gt;=&lt;/span&gt; ContentType&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800000; font-weight:bold; '&gt;else&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;                response&lt;span style='color:#808030; '&gt;.&lt;/span&gt;ContentType &lt;span style='color:#808030; '&gt;=&lt;/span&gt; &lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#0000e6; '&gt;application/json&lt;/span&gt;&lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800000; font-weight:bold; '&gt;if&lt;/span&gt; &lt;span style='color:#808030; '&gt;(&lt;/span&gt;ContentEncoding &lt;span style='color:#808030; '&gt;!&lt;/span&gt;&lt;span style='color:#808030; '&gt;=&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;null&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;                response&lt;span style='color:#808030; '&gt;.&lt;/span&gt;ContentEncoding &lt;span style='color:#808030; '&gt;=&lt;/span&gt; ContentEncoding&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800000; font-weight:bold; '&gt;if&lt;/span&gt; &lt;span style='color:#808030; '&gt;(&lt;/span&gt;Data &lt;span style='color:#808030; '&gt;!&lt;/span&gt;&lt;span style='color:#808030; '&gt;=&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;null&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span style='color:#696969; '&gt;// The JavaScriptSerializer type was marked as obsolete prior to .NET Framework 3.5 SP1&lt;/span&gt;&lt;br /&gt;#pragma warning disable &lt;span style='color:#008c00; '&gt;0618&lt;/span&gt;&lt;br /&gt;                HttpRequestBase request &lt;span style='color:#808030; '&gt;=&lt;/span&gt; context&lt;span style='color:#808030; '&gt;.&lt;/span&gt;HttpContext&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Request&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;                &lt;br /&gt;                JavaScriptSerializer serializer &lt;span style='color:#808030; '&gt;=&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;new&lt;/span&gt; JavaScriptSerializer&lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;                &lt;span style='color:#800000; font-weight:bold; '&gt;if&lt;/span&gt; &lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#800000; font-weight:bold; '&gt;null&lt;/span&gt; &lt;span style='color:#808030; '&gt;!&lt;/span&gt;&lt;span style='color:#808030; '&gt;=&lt;/span&gt; request&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Params&lt;span style='color:#808030; '&gt;[&lt;/span&gt;&lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#0000e6; '&gt;jsoncallback&lt;/span&gt;&lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#808030; '&gt;]&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;                    response&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Write&lt;span style='color:#808030; '&gt;(&lt;/span&gt;request&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Params&lt;span style='color:#808030; '&gt;[&lt;/span&gt;&lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#0000e6; '&gt;jsoncallback&lt;/span&gt;&lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#808030; '&gt;]&lt;/span&gt; &lt;span style='color:#808030; '&gt;+&lt;/span&gt; &lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#0000e6; '&gt;(&lt;/span&gt;&lt;span style='color:#800000; '&gt;"&lt;/span&gt; &lt;span style='color:#808030; '&gt;+&lt;/span&gt; serializer&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Serialize&lt;span style='color:#808030; '&gt;(&lt;/span&gt;Data&lt;span style='color:#808030; '&gt;)&lt;/span&gt; &lt;span style='color:#808030; '&gt;+&lt;/span&gt; &lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#0000e6; '&gt;)&lt;/span&gt;&lt;span style='color:#800000; '&gt;"&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;                &lt;span style='color:#800000; font-weight:bold; '&gt;else&lt;/span&gt;&lt;br /&gt;                    response&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Write&lt;span style='color:#808030; '&gt;(&lt;/span&gt;serializer&lt;span style='color:#808030; '&gt;.&lt;/span&gt;Serialize&lt;span style='color:#808030; '&gt;(&lt;/span&gt;Data&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;#pragma warning restore &lt;span style='color:#008c00; '&gt;0618&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I then extended Controller &lt;br /&gt;&lt;br /&gt;&lt;pre style='color:#000000;background:#ffffff;'&gt;&lt;span style='color:#800000; font-weight:bold; '&gt;public&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;class&lt;/span&gt; WopsleController &lt;span style='color:#808030; '&gt;:&lt;/span&gt; Controller&lt;br /&gt;    &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800000; font-weight:bold; '&gt;protected&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;internal&lt;/span&gt; JsonpResult Jsonp&lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#800000; font-weight:bold; '&gt;object&lt;/span&gt; data&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800000; font-weight:bold; '&gt;return&lt;/span&gt; Jsonp&lt;span style='color:#808030; '&gt;(&lt;/span&gt;data&lt;span style='color:#808030; '&gt;,&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;null&lt;/span&gt; &lt;span style='color:#696969; '&gt;/* contentType */&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span style='color:#800000; font-weight:bold; '&gt;protected&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;internal&lt;/span&gt; JsonpResult Jsonp&lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#800000; font-weight:bold; '&gt;object&lt;/span&gt; data&lt;span style='color:#808030; '&gt;,&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;string&lt;/span&gt; contentType&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800000; font-weight:bold; '&gt;return&lt;/span&gt; Jsonp&lt;span style='color:#808030; '&gt;(&lt;/span&gt;data&lt;span style='color:#808030; '&gt;,&lt;/span&gt; contentType&lt;span style='color:#808030; '&gt;,&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;null&lt;/span&gt;&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span style='color:#800000; font-weight:bold; '&gt;protected&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;internal&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;virtual&lt;/span&gt; JsonpResult Jsonp&lt;span style='color:#808030; '&gt;(&lt;/span&gt;&lt;span style='color:#800000; font-weight:bold; '&gt;object&lt;/span&gt; data&lt;span style='color:#808030; '&gt;,&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;string&lt;/span&gt; contentType&lt;span style='color:#808030; '&gt;,&lt;/span&gt; Encoding contentEncoding&lt;span style='color:#808030; '&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span style='color:#800000; font-weight:bold; '&gt;return&lt;/span&gt; &lt;span style='color:#800000; font-weight:bold; '&gt;new&lt;/span&gt; JsonpResult&lt;br /&gt;            &lt;span style='color:#800080; '&gt;{&lt;/span&gt;&lt;br /&gt;                Data &lt;span style='color:#808030; '&gt;=&lt;/span&gt; data&lt;span style='color:#808030; '&gt;,&lt;/span&gt;&lt;br /&gt;                ContentType &lt;span style='color:#808030; '&gt;=&lt;/span&gt; contentType&lt;span style='color:#808030; '&gt;,&lt;/span&gt;&lt;br /&gt;                ContentEncoding &lt;span style='color:#808030; '&gt;=&lt;/span&gt; contentEncoding&lt;br /&gt;            &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;span style='color:#800080; '&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span style='color:#800080; '&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and altered all my controllers to extend WopsleController rather than Controller.  Seems to work pretty well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-8030124468506553983?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/8030124468506553983/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/04/aspnet-mvc-returning-jsonp.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/8030124468506553983'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/8030124468506553983'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/04/aspnet-mvc-returning-jsonp.html' title='ASP.net MVC returning JSONP'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-4269899666142227553</id><published>2009-02-28T19:21:00.000-08:00</published><updated>2009-02-28T19:39:35.816-08:00</updated><title type='text'>Best Bug I've Written Today</title><content type='html'>Today I create a handy little class in my attempt to rewrite wordle.  It encapsulated a word and a word count and implemented comparable&lt;br /&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   1 &lt;/span&gt;&lt;span style="color:#ff3030; font-weight:bold"&gt;package&lt;/span&gt; com&lt;span style="color:#555555"&gt;.&lt;/span&gt;simontimms&lt;span style="color:#555555"&gt;.&lt;/span&gt;wordle&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   2 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   3 &lt;/span&gt;&lt;span style="color:#ff3030; font-weight:bold"&gt;public class&lt;/span&gt; WordCountElement &lt;span style="color:#ff3030; font-weight:bold"&gt;implements&lt;/span&gt; &lt;span style="color:#0000ff"&gt;Comparable&lt;/span&gt;&lt;span style="color:#555555"&gt;&amp;lt;&lt;/span&gt;WordCountElement&lt;span style="color:#555555"&gt;&amp;gt;{&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   4 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   5 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   6 &lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#f48c23"&gt;int&lt;/span&gt; count &lt;span style="color:#555555"&gt;=&lt;/span&gt; &lt;span style="color:#32ba06"&gt;0&lt;/span&gt;&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   7 &lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#0000ff"&gt;String&lt;/span&gt; word&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   8 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   9 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  10 &lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;public&lt;/span&gt; &lt;span style="color:#d11ced"&gt;WordCountElement&lt;/span&gt; &lt;span style="color:#555555"&gt;(&lt;/span&gt;&lt;span style="color:#0000ff"&gt;String&lt;/span&gt; word&lt;span style="color:#555555"&gt;,&lt;/span&gt; &lt;span style="color:#f48c23"&gt;int&lt;/span&gt; count&lt;span style="color:#555555"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  11 &lt;/span&gt; &lt;span style="color:#555555"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  12 &lt;/span&gt;  &lt;span style="color:#ff3030; font-weight:bold"&gt;this&lt;/span&gt;&lt;span style="color:#555555"&gt;.&lt;/span&gt;word &lt;span style="color:#555555"&gt;=&lt;/span&gt; word&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  13 &lt;/span&gt;  &lt;span style="color:#ff3030; font-weight:bold"&gt;this&lt;/span&gt;&lt;span style="color:#555555"&gt;.&lt;/span&gt;count &lt;span style="color:#555555"&gt;=&lt;/span&gt; count&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  14 &lt;/span&gt; &lt;span style="color:#555555"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  15 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  16 &lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;public&lt;/span&gt; &lt;span style="color:#f48c23"&gt;int&lt;/span&gt; &lt;span style="color:#d11ced"&gt;compareTo&lt;/span&gt;&lt;span style="color:#555555"&gt;(&lt;/span&gt;WordCountElement toCompare&lt;span style="color:#555555"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  17 &lt;/span&gt; &lt;span style="color:#555555"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  18 &lt;/span&gt;  &lt;span style="color:#ff3030; font-weight:bold"&gt;return this&lt;/span&gt;&lt;span style="color:#555555"&gt;.&lt;/span&gt;count &lt;span style="color:#555555"&gt;-&lt;/span&gt; toCompare&lt;span style="color:#555555"&gt;.&lt;/span&gt;&lt;span style="color:#d11ced"&gt;getCount&lt;/span&gt;&lt;span style="color:#555555"&gt;();&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  19 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  20 &lt;/span&gt; &lt;span style="color:#555555"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  21 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  22 &lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;public&lt;/span&gt; &lt;span style="color:#f48c23"&gt;void&lt;/span&gt; &lt;span style="color:#d11ced"&gt;setCount&lt;/span&gt;&lt;span style="color:#555555"&gt;(&lt;/span&gt;&lt;span style="color:#f48c23"&gt;int&lt;/span&gt; count&lt;span style="color:#555555"&gt;) {&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  23 &lt;/span&gt;  &lt;span style="color:#ff3030; font-weight:bold"&gt;this&lt;/span&gt;&lt;span style="color:#555555"&gt;.&lt;/span&gt;count &lt;span style="color:#555555"&gt;=&lt;/span&gt; count&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  24 &lt;/span&gt; &lt;span style="color:#555555"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  25 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  26 &lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;public&lt;/span&gt; &lt;span style="color:#f48c23"&gt;int&lt;/span&gt; &lt;span style="color:#d11ced"&gt;getCount&lt;/span&gt;&lt;span style="color:#555555"&gt;() {&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  27 &lt;/span&gt;  &lt;span style="color:#ff3030; font-weight:bold"&gt;return&lt;/span&gt; count&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  28 &lt;/span&gt; &lt;span style="color:#555555"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  29 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  30 &lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;public&lt;/span&gt; &lt;span style="color:#f48c23"&gt;void&lt;/span&gt; &lt;span style="color:#d11ced"&gt;setWord&lt;/span&gt;&lt;span style="color:#555555"&gt;(&lt;/span&gt;&lt;span style="color:#0000ff"&gt;String&lt;/span&gt; word&lt;span style="color:#555555"&gt;) {&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  31 &lt;/span&gt;  &lt;span style="color:#ff3030; font-weight:bold"&gt;this&lt;/span&gt;&lt;span style="color:#555555"&gt;.&lt;/span&gt;word &lt;span style="color:#555555"&gt;=&lt;/span&gt; word&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  32 &lt;/span&gt; &lt;span style="color:#555555"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  33 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  34 &lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff"&gt;String&lt;/span&gt; &lt;span style="color:#d11ced"&gt;getWord&lt;/span&gt;&lt;span style="color:#555555"&gt;() {&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  35 &lt;/span&gt;  &lt;span style="color:#ff3030; font-weight:bold"&gt;return&lt;/span&gt; word&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  36 &lt;/span&gt; &lt;span style="color:#555555"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  37 &lt;/span&gt;&lt;span style="color:#555555"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;At another point in my code I created a sorted set out of these WordCountElements&lt;br /&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   1 &lt;/span&gt;&lt;span style="color:#ff3030; font-weight:bold"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff"&gt;TreeSet&lt;/span&gt;&lt;span style="color:#555555"&gt;&amp;lt;&lt;/span&gt;WordCountElement&lt;span style="color:#555555"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#d11ced"&gt;getSortedSetOfWordCounts&lt;/span&gt;&lt;span style="color:#555555"&gt;(&lt;/span&gt;&lt;span style="color:#0000ff"&gt;String&lt;/span&gt; textToAnalyze&lt;span style="color:#555555"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   2 &lt;/span&gt; &lt;span style="color:#555555"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   3 &lt;/span&gt;  &lt;span style="color:#0000ff"&gt;HashMap&lt;/span&gt;&lt;span style="color:#555555"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0000ff"&gt;String&lt;/span&gt;&lt;span style="color:#555555"&gt;,&lt;/span&gt; WordCountElement&lt;span style="color:#555555"&gt;&amp;gt;&lt;/span&gt; unsortedSet &lt;span style="color:#555555"&gt;=&lt;/span&gt; &lt;span style="color:#d11ced"&gt;getWordCounts&lt;/span&gt;&lt;span style="color:#555555"&gt;(&lt;/span&gt;textToAnalyze&lt;span style="color:#555555"&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   4 &lt;/span&gt;  &lt;span style="color:#0000ff"&gt;TreeSet&lt;/span&gt;&lt;span style="color:#555555"&gt;&amp;lt;&lt;/span&gt;WordCountElement&lt;span style="color:#555555"&gt;&amp;gt;&lt;/span&gt; sortedSet &lt;span style="color:#555555"&gt;=&lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;new&lt;/span&gt; &lt;span style="color:#0000ff"&gt;TreeSet&lt;/span&gt;&lt;span style="color:#555555"&gt;&amp;lt;&lt;/span&gt;WordCountElement&lt;span style="color:#555555"&gt;&amp;gt;();&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   5 &lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   6 &lt;/span&gt;  &lt;span style="color:#ff3030; font-weight:bold"&gt;for&lt;/span&gt;&lt;span style="color:#555555"&gt;(&lt;/span&gt;&lt;span style="color:#0000ff"&gt;String&lt;/span&gt; s &lt;span style="color:#555555"&gt;:&lt;/span&gt; unsortedSet&lt;span style="color:#555555"&gt;.&lt;/span&gt;&lt;span style="color:#d11ced"&gt;keySet&lt;/span&gt;&lt;span style="color:#555555"&gt;())&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   7 &lt;/span&gt;  &lt;span style="color:#555555"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   8 &lt;/span&gt;   sortedSet&lt;span style="color:#555555"&gt;.&lt;/span&gt;&lt;span style="color:#d11ced"&gt;add&lt;/span&gt;&lt;span style="color:#555555"&gt;(&lt;/span&gt;unsortedSet&lt;span style="color:#555555"&gt;.&lt;/span&gt;&lt;span style="color:#d11ced"&gt;get&lt;/span&gt;&lt;span style="color:#555555"&gt;(&lt;/span&gt;s&lt;span style="color:#555555"&gt;));&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;   9 &lt;/span&gt;   &lt;span style="color:#0000ff"&gt;System&lt;/span&gt;&lt;span style="color:#555555"&gt;.&lt;/span&gt;out&lt;span style="color:#555555"&gt;.&lt;/span&gt;&lt;span style="color:#d11ced"&gt;println&lt;/span&gt;&lt;span style="color:#555555"&gt;(&lt;/span&gt;s&lt;span style="color:#555555"&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  10 &lt;/span&gt;  &lt;span style="color:#555555"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  11 &lt;/span&gt; &lt;span style="color:#ff3030; font-weight:bold"&gt;return&lt;/span&gt; sortedSet&lt;span style="color:#555555"&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#9a9a9a"&gt;  12 &lt;/span&gt; &lt;span style="color:#555555"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I couldn't get my unit tests to pass when I had more than one word in the unsortedSet.  I ended up debugging it and found that even though I was adding two different words only the first was present in the sorted set.  Well &lt;a href="http://www.pragprog.com/the-pragmatic-programmer/extracts/tips"&gt;select might not be broken&lt;/a&gt; but perhaps sorted sets were.  Can you see the bug?  &lt;br /&gt;&lt;br /&gt;That's right, the custom comparator resulted in two words with equal counts being equal.  So select wasn't broken.  Drat.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-4269899666142227553?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/4269899666142227553/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/02/best-bug-ive-written-today.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4269899666142227553'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/4269899666142227553'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/02/best-bug-ive-written-today.html' title='Best Bug I&apos;ve Written Today'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-902968642302846105.post-1879064361869663700</id><published>2009-02-26T20:39:00.000-08:00</published><updated>2009-02-27T07:22:35.973-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='twitter linux AdobeAir'/><title type='text'>Tweetdeck on 64bit Ubuntu Llinux</title><content type='html'>&lt;a href="http://www.tweetdeck.com/"&gt;Tweetdeck&lt;/a&gt; is a very powerful twitter client which is written in the rather nifty &lt;a href="http://en.wikipedia.org/wiki/Adobe_Air"&gt;Adobe Air&lt;/a&gt; framework. Unfortunately the air platform does not yet have support for 64-bit Linux.  I imagine it will come along once flash 10 for 64 bit Linux comes out of beta.  There is a rather long tutorial on how to work around the limitations and run it in 32-bit mode on 64-bit linux right &lt;a href="http://kb.adobe.com/selfservice/viewContent.do?externalId=kb408084"&gt;here&lt;/a&gt; but that isn't enough to run tweetdeck.  You'll also need to pull down a copy of 32 bit libgnome-keyring to allow it to access your keyring.&lt;br /&gt;&lt;br /&gt;&lt;div style="padding-left: 20px; margin-left: 20px; background-color: rgb(243, 243, 243); font-family: courier, serif"&gt;&lt;br /&gt;wget http://ubuntu.interlegis.gov.br/ubuntu/pool/main/g/gnome-keyring/libgnome-keyring0_2.22.2-0ubuntu1_i386.deb&lt;br /&gt;ar x libgnome-keyring0_2.22.2-0ubuntu1_i386.deb data.tar.gz&lt;br /&gt;tar -xzvf data.tar.gz&lt;br /&gt;cp usr/lib/* /usr/lib32/&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;On a related note it is a shame that the default Air page is so pedestrian.  I am filled with no excitement upon visiting it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/902968642302846105-1879064361869663700?l=blog.simontimms.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.simontimms.com/feeds/1879064361869663700/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.simontimms.com/2009/02/tweetdeck-on-64bit-linux.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1879064361869663700'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/902968642302846105/posts/default/1879064361869663700'/><link rel='alternate' type='text/html' href='http://blog.simontimms.com/2009/02/tweetdeck-on-64bit-linux.html' title='Tweetdeck on 64bit Ubuntu Llinux'/><author><name>Simon Timms</name><uri>http://www.blogger.com/profile/10694990694262832549</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
