blog.anthonyburns.co.uk http://blog.anthonyburns.co.uk Windows VPS Hosting I've been reading a lot on the internets about VPS hosting lately, and although the bulk of it seems to be linux based, there are more and more hosts providing Windows VPS hosting. I was shelling out £9.38 a month each on two shared hosting plans, and had plans for another, so thought a Windows VPS host might be a better option.

After shopping about for prices online, I didn't find any particularly enticing deals; especially none that offered the unlimited bandwidth I had with my, then current, hosting company Fasthosts.

I figured it was worth giving Fasthosts a call to see if they had any future plans to provide VPS hosting. The guy I spoke to dismissed the idea of VPS hosting, saying they had no plans to introduce it as their dedicated servers were cheap enough, and that VPS was a combination of the bad sides of shared hosting and dedicated servers all rolled together - eh? He warned me that not all VPS hosts would provide full admin access so to be careful. I thought that was the nature of a VPS, that you had full admin access to your own virtual box. Never the less, his scare tactics made me do a bit more investigating before jumping from this particular shared hosting ship.

I decided to ask Twitter what their experiences of VPS hosting were, and @jesscoburn came back with a series of tweets answering different questions I posed, but warning me that he was slightly biased since he ran his own hosting company. He promised to send me an email with more details, and I asked him for some prices for hosting.

The email he sent me was a mammoth essay detailing the pros and potential pitfalls of VPS hosting. He's promised to turn it into a blog post, which I'll link to here when he does.

His company - Applied Innovations - offer a basic Windows VPS hosting package for $39.95 (around £26 in my language), which gives me 10GB storage and a whopping 500GB of monthly bandwidth. While this is not the unlimited I intially hoped to find, I can't see myself exhausting it in the short term.

I was up and running within a few hours of signing up, and accessing the server via Remote Desktop feels no different to the way I access our dedicated servers at work. The whole thing is nice and speedy and the sites run fast.

It's afforded me the ability to switch this blog to a separate subdomain (blog. rather than www.) and set it up in IIS as a separate website, enabling me to start using ASP.NET MVC as a base for the blog while keeping the rest of my site in a standard WebForms model. I also now have the facility to setup different websites in IIS for every little project idea I have, all running from a different subdomain of anthonyburns.co.uk or their own domain if needed; all for around £6 more than I was previously paying for two shared hosting plans.

If you're in the market for a VPS hosting solution, then I'd highly recommend Applied Innovations - the customer services was the best I've received in years, from any company, and the service itself is flawless.

]]>
Anthony Burns General http://blog.anthonyburns.co.uk/Windows-VPS-Hosting http://blog.anthonyburns.co.uk/Windows-VPS-Hosting Saturday, 11/15/2008 6:22:54 PM GMT
Simple Table Control in ASP.NET MVC

I'm digging the new ASP.NET MVC framework. Big style. Transitioning from classic ASP to ASP.NET left me a bit frustrated with the abstraction introduced. It was supposed to be better for us developers that the stateless nature of HTTP was being hidden away, that the framework would take care of all the state management, leaving us time to eat pizza and drink beer; but that just left me fat and drunk. Thankfully I can now dive back into having full control of my html and post data, among other things, by using MVC.

However, I don't want to hand craft every single piece of html every time, and there's a lot of things day to day that I just want rendering without a lot of effort - tables being one of them, so I set about building a simple but relatively powerful table control.

Requirements: I wanted the table to render an IEnumerable I might pull straight from a Linq query to a html table; including the ability to make each row a link, to highlight a selected row and the facility to add columns to the end of the table containing action links such as "Delete".

After Googling 'asp net mvc controls' I discovered a few different ways of creating and rendering controls under the MVC framework. I finally opted to write an extension method to the HtmlHelper class, made available through the Html property on each MVC View.

Since this probably won't be the last control I'll build, I created an interface each control could implement:

interface ISimpleControl {
    string Render(HtmlHelper helper)
}

I then created a small HtmlHelper extension method to render these simple controls:

public static string RenderControl(this HtmlHelper helper, ISimpleControl control)
{
    return control.Render(helper);
}

I can now render any simple control using:

<%=Html.RenderControl(new MyFirstControl());%>

Because my datasource could be any type of object returned from a Linq query (including anonymous types) I have to use reflection to discover the properties of these objects in order to render the columns of the table. I opted to do this as soon as the data source is set and convert it to a DataTable for easy traversal when it comes to rendering the html:

private static DataTable GetDataTableFromIEnumerable(IEnumerable dataSource)
{
    // Since IEnumerable doesn't expose an indexer to get the first element
    object obj;
    foreach (object o in data)
    {
        obj = o;
        break;
    }
    if (obj == null) return null;

    DataTable dataTable = new DataTable();

    // Use reflection to create DataColumns for each of the properties in the object
    Type type = obj.GetType();
    PropertyInfo[] propertyInfo = type.GetProperties();
    string[] keys = new string[propertyInfo.Length];
    for (int i = 0; i < keys.Length; i++)
    {
        DataColumn column = new DataColumn(propertyInfo[i].Name, typeof(string));
        dataTable.Columns.Add(column);
        keys[i] = propertyInfo[i].Name;
    }

    // Iterate through the IEnumerable and populate the DataTable from
    // each object using Reflection
    foreach (object row in dataSource)
    {
        DataRow dataRow = dataTable.NewRow();
        foreach (DataColumn column in dataTable.Columns)
        {
            object result = type.InvokeMember(column.ColumnName, BindingFlags.GetProperty, 
                                              null, row, null);
            if (result != null)
            {
        	dataRow[column] = result.ToString();
            }
        }
        dataTable.Rows.Add(dataRow);
    }

    return dataTable;
}

The first thing I do is to get the first element in the IEnumerable to perform the reflection on. Since IEnumerables don't expose an indexer, the only way I could think to do this is by performing a foreach loop and exiting as soon as I have a copy of the first element. I then get a list of the properties exposed by the object and create matching DataColumns for the DataTable. The second foreach loop populates a DataRow from each object and appends it to the table.

It's highly probable that I'll make other controls that iterate over IEnumerable data sources in this manner further down the road, so I opted to extract this functionality into an abstract base class called DataControl. This control exposes a public DataSource property for setting the IEnumerable, which converts it into a DataTable available to any derived classes through a protected property:

public abstract class DataControl : ISimpleControl
{
    protected DataTable DataTable { get; set; }

    public IEnumerable DataSource
    {
        set
        {
            DataTable = GetDataTableFromIEnumerable(value);
        }
    }

...

}

A derived class could then be used with an IEnumerable from your ViewData like so:

<%=Html.RenderControl(new MyDataControl{ DataSource = (IEnumerable)ViewData["MyLinqResult"] });%>

All that's left to do is to inherit from that DataControl and override the Render method to render the html:

public override string Render(HtmlHelper helper)
{
    if (DataTable == null) return "";

    // Use either the columns requested, or all table columns if not specified
    string[] keys = DisplayColumns ?? GetColumnKeys(DataTable);

    StringBuilder header = new StringBuilder();

    // Build the html for the table's header
    header.AppendLine("  <tr>");
    foreach (string key in keys)
    {
        header.AppendLine("    <th>" + key + "</th>");
    }
    header.AppendLine("  </tr>");

    bool altRow = true;
    StringBuilder data = new StringBuilder();

    // Render the relevant Html for each Row in the table
    foreach (DataRow row in DataTable.Rows)
    {
        // Add an 'Alt' Css class to alternating rows
        string rowClass = altRow ? "Alt" : "";

        data.AppendLine("  <tr class=\"" + rowClass + "\">");

        // Render the Html for each column of this row 
        foreach (string key in keys)
        {
            data.AppendLine("    <td>" + row[key] + "</td>");
        }

        data.AppendLine("  </tr>");

        // Switch whether or not this is an alternating row
        altRow = altRow ? false : true;
    }

    // Piece together all of the Html and return it
    StringBuilder html = new StringBuilder();
    html.AppendLine("<table>");
    html.Append(header);
    html.Append(data);
    html.AppendLine("</table>");

    return html.ToString();
}

I've also added a DisplayColumns property of type string[] to the Table class that enables me to specify the columns to render, as I might not want to render all of the properties. In line 6 I check for the existence of this property and if it hasn't been set I call GetColumnKeys() which is a private method I've written that returns a string[] of the DataColumn names from the DataTable.

I then render the html for the table header, and follow that up by rendering each row from the DataTable. I add an "Alt" class to each alternating row so I can use Css to colour the alternating rows differently.

I can now render a table in my View with a single line of code:

<%=Html.RenderControl(new Table{ DisplayColumns = new string[] { "Name", "EMail" }, 
                                 DataSource = (IEnumerable)ViewData["MyLinqResult"] });%>

I'll finish this control in the next article by adding the functionality to click a row to fire an action, highlight a selected row, and include further actions in additional columns at the end of the table.

You can download the full source code from this article here: MvcTableControl.zip

]]>
Anthony Burns Development http://blog.anthonyburns.co.uk/Simple-Table-Control-in-ASP.NET-MVC http://blog.anthonyburns.co.uk/Simple-Table-Control-in-ASP.NET-MVC Saturday, 10/18/2008 5:37:14 AM GMT
Quakk Goes Open Source

If you're just looking for the application, then you need to visit the Quakk homepage.

If you're after having a peek at the code, contributing, criticising, or even stealing some of it for use in your own Twitter application, then you're in the right place. The solution can be downloaded from CodePlex

Some ideas I have to improve Quakk going forward, that you may be interested in helping with:

  • GPS - Enable users to update their Location using GPS
  • Auto-Update Main Timeline - Enable users to set a time interval for Quakk to Update the main timline
  • Detect new software update - Quakk should check home every 25 updates to see if there's a new version
  • View X's timeline in menu - Adding "View User" to the popup menu so users can view another user's timeline
  • Unit Testing - I have yet to begin unit testing, although I've read quite a bit about it, so if someone fancies holding my hand through my first steps, that would be great!

If you're interested in participating with the development of Quakk then hit me up: @littlecharva or leave a message on CodePlex or in the comments here.

]]>
Anthony Burns Software http://blog.anthonyburns.co.uk/Quakk-Goes-Open-Source http://blog.anthonyburns.co.uk/Quakk-Goes-Open-Source Saturday, 9/20/2008 1:00:25 PM GMT
Quakk v1.0 Final Release

Yowsers! It's finally here, the final build of Quakk v1.0.

Here's a few things that have been fixed/tweaked since the last beta:

  • No longer crashes randomly when updating
  • Send Tweet window auto-closes when your Tweet is sent
  • The height of each Tweet is now ALWAYS large enough to fit the text in
  • The local time set on your device is now taken into account when displaying Tweet times
  • The direct message timeline now works
  • The drawing of each tweet is now done using double-buffering to minimize flicker

QuakkSetup.cab

Copy the CAB file to your Windows Mobile device and run it, the CAB installer will install Quakk and create a shortcut in your Programs menu.

You might need to download and install the .NET Compact Framework 2 if you're running Windows Mobile 5 - if so, you can download that here.

I don't know how much more I'm going to add to Quakk in the future, as I have other crazy contraptions I want to build, but I am planning on open sourcing it very soon, once I've had the chance to clean up the code.

Follow me on Twitter (@littlecharva) to keep abreast of any further updates.

]]>
Anthony Burns Software http://blog.anthonyburns.co.uk/Quakk-v1.0-Final-Release http://blog.anthonyburns.co.uk/Quakk-v1.0-Final-Release Tuesday, 7/29/2008 8:52:10 PM GMT
Search for an Icon In order for Quakk to move out of the Beta stage it really needs an icon. If you fancy your skills as a designer and want your work to be on the screens of hundreds of people's mobile devices then send me your best shot. Twitter me @littlecharva.

]]>
Anthony Burns Software http://blog.anthonyburns.co.uk/Search-for-an-Icon http://blog.anthonyburns.co.uk/Search-for-an-Icon Wednesday, 7/16/2008 1:23:25 PM GMT
Twitter Reply EMailer

My addiction to Twitter has grown exponentially over the last month or so. I've gone from "I just don't get it" to developing a Windows Mobile application - Quakk - so I can check my Tweets on the go.

Twitter does me the favour of sending an email when when I receive a direct message, but doesn't bother when I recieve a reply - I'd prefer it the other way around. After browsing the list of available applications at the Twitter Fan Wiki I tried a few online services that claimed to monitor your replies and send notification emails, but was satisfied by none of them. The first one I tried wouldn't let me sign up due to database connection problems, and the second one sent my notifications sporadically. So I decided I could easily write my own monitor using the parts I've already developed for Quakk.

I've made the entire code for this project available to download at the bottom of this article, but if you're not a coder and you just want a copy of the application to use yourself, then that is available for download below too.

There are four classes from Quakk I'll be using: Configuration, TwitterApi, ReplyTimeline and Tweet. I've stripped out any code that's irrelevant to this project and detailed the public interfaces of these classes below, with the main code for the project below that. Please keep in mind that Quakk is still in the beta stage, so some of this code is a bit scruffy.

Configuration

public class Configuration
{
    public string LastID
    public string Username
    public string Password
    public string EmailAddress
    public string SmtpServer
    public static Configuration LoadConfiguration()
    public void Save()
}

The Configuration class deals with loading and saving your Twitter login credentials, email details and the ID of the last received tweet to an xml file. The static method LoadConfiguration() loads and parses the Xml file and returns an instance of the Configuration class populated with the details from the Xml file. These are made available as public properties, and a Save() method enables you to save any changes you make back to the Xml file.

TwitterApi

public class TwitterApi
{
    public string Username
    public string Password
    public string GetXml(string url)
}

The TwitterApi class takes care of connecting to Twitter, authenticating your user details and returning the response as a string. Create an instance of the TwitterApi, populate the Username and Password properties from your Configuration object and then call GetXml() on the desired url from the Twitter API.

Tweet

public class Tweet
{
    public string ID
    public string DisplayName
    public DateTime TweetDateTime
    public string Message
    public Tweet(XmlNode tweetXml)
}

The Tweet class takes an XmlNode as part of its constuctor which it parses and populates the public Properties from the data provided.

ReplyTimeline

public class ReplyTimeline
{
    public List<Tweet> Tweets
    protected ReplyTimeline(TwitterApi twitterApi)
    public void Update()
}

You pass in an instance of the TwitterApi to the ReplyTimeline class when you instantiate it which gives the ReplyTimeline the ability to connect to Twitter. When you call the Update method, it obtains the Xml for the 20 most recent replies from your Twitter account, parses it and creates a List of parsed Tweet objects which it makes available from its public Tweets property.

Putting it all Together

As you can see, by making use of these objects you can get a collection of Tweets live from Twitter in very little code.

// Load Configuration from Xml file, quitting if it doesn't exist
Configuration config = Configuration.LoadConfiguration();
if(config == null) return;

TwitterApi twitter = new TwitterApi
                         {
                             Username = config.Username,
                             Password = config.Password
                         };

// Update the Reply Timeline
ReplyTimeline timeline;
try
{
    timeline = new Timeline(twitter);
    timeline.Update();
}
catch(TwitterException)
{
    // We could implement some sort of logging here,
    // but since Twitter is down more than it's up 
    // I've opted to just quit the application here
    return;
}

List<Tweet> tweets = timeline.Tweets;

At this point we have a generic List of Tweet objects from our Reply Timeline.

string emailBody = "";

// Iterate through each tweet returned from the timeline 
// until we hit a tweet we've already seen
foreach(Tweet tweet in tweets)
{
    if(tweet.ID == config.LastID)
    {
        break;
    }

    // Append the current tweet's details to our email body
    emailBody += tweet.DisplayName + " replied:\r\n" +
                 tweet.Message + "\r\n\r\nAt: " +
                 tweet.TweetDateTime + "\r\n\r\n";
}

We iterate through the Tweet list building up an email containing the tweets we haven't seen yet.

// If we have tweet notifications to send then compose the email
if(emailBody != "")
{

    MailMessage mail = new MailMessage(config.EmailAddress,
                                       config.EmailAddress,
                                       "New Reply Tweet", 
                                       emailBody);

    // Send the email using the SMTP server specified in the config
    try
    {
        SmtpClient client = new SmtpClient(config.SmtpServer);
        client.Send(mail);
    }
    catch(SmtpException)
    {
        return;
    }

    // Update the ID of the last tweet in the config so we
    // can identify if we've already seen it next time
    config.LastID = tweets[0].ID;
    config.Save();
}

Then we send out the email using the details from the config file, and update the config file with the ID of the last tweet.

You also need the following Configuration.xml file in the same directory as the application.

<?xml version="1.0"?>
<Configuration>
  <LastID></LastID>
  <Username>Your Twitter Username</Username>
  <Password>Your Twitter Password</Password>
  <EmailAddress>billy@billy.com</EmailAddress>
  <SmtpServer>smtp.mysmtpserver.com</SmtpServer>
</Configuration>

How to use the Software

You can download the source in a VS2008 project, or if you're only interested in the application itself then download the app.

The application simply connects to Twitter, downloads your Replies, compiles an email of any new Replies and sends that email to you. In order to make use of it you should set it up as a scheduled task in Windows.

]]>
Anthony Burns Software http://blog.anthonyburns.co.uk/Twitter-Reply-EMailer http://blog.anthonyburns.co.uk/Twitter-Reply-EMailer Thursday, 7/10/2008 8:17:48 PM GMT
Never forget a thing thanks to Powershell and Regular Expressions My memory sucks. I'm like a knackered old 286 with 32MB of memory; it's all stored on the hard drive somewhere, but getting it into RAM and then to the CPU is a bit of a challenge. So after discovering memory tricks in Derren Brown's book Tricks of the Mind, I set about learning as much as I could on the subject and began putting it all to work.

The premise behind most memory techniques is that the average brain remembers images far better than it remembers facts or figures; so if you can build a picture in your head - the crazier the better (the picture not the head) - you're more likely to remember it than trying to remember numbers, words or dates and times alone.

It's all fairly straight forward; you want to memorise a shopping list of milk, bread and chicken: think of a big cartoon chicken swimming around in a lake full of milk while you throw loafs of bread at it like it's some sort of funfair game. It's strange, it's vivid and it's likely to stay in your head - at least until you get to the shop.

The problem comes when you need to memorise numbers, as is often the case in this technology driven world - pin numbers, phone numbers, registration numbers, dates, etc. The trick is to encode the numbers into letters; there are various techniques you can use, but the one that I took a shine to is called the Mnemonic Major System. There are a couple of variations, but the Derren Brown system dictates:

  • 0 = S, Z or C
  • 1 = L
  • 2 = N
  • 3 = M
  • 4 = R
  • 5 = F or V
  • 6 = B or P
  • 7 = T
  • 8 = SH, CH or J
  • 9 = G

As an example, let's imagine that your PIN number is 9283. Break it up and start with the number 92; this encodes as GN - pad this out with some vowels of your own choosing and you can end up with GUN, GIN, GOON, GENE, you get the idea?

I then choose to encode the final two digits (83) as JAM. Now I build a big, colourful mental image of a gun that squirts out jam. You can also add something to the picture to remember the context, so let's imagine you're standing in your local bank, sticking the place up (no pun intended) with this gun which goes off randomly, covering the bewildered bank clerks with jam. When you need to, you can break this picture down to the words, then unencode the words back into the numbers.

The part I find tricky is packing the vowels in to find a decent word I can picture. This is where the Powershell and regular expressions come into play. I figured that given the right regular expression and a big list of English words, I could have any matching words recommended to me. For example, given the letters GN I could use the following regular expression to match any words that fit:

    ^[aeiou]*G[aeiou]*N[aeiou]*$

It basically matches any word on a line of its own that contains zero or more vowels, followed by a G then zero or more vowels, then an N, then anymore vowels that might be lying around.

All we need now is a function that takes the letters you've encoded, builds the regular expression and then finds the matches from a text file full of words. I was about to take the easy route and build something in C#, but I figured this would be a good opportunity to get my head around Powershell.

    $letters = "gn"
    $vowel = "[aeiou]"
    $regex = "^" + $vowel + "*"
    foreach($char in [char[]]$letters) { $regex += $char + $vowel + "*" }
    $regex += "$"

I declare my vowels in a regular expression character class - you can actually use any letters that don't match up to a number here, so adding in things like W and Y will likely increase the amount of words returned. Then using a foreach construct I wrap each of the letters provided in my character class.

At this point we have our regular expression pattern, all we need is a list of words and some code to find the matches. After a quick google I found a list of words designed to help people play scrabble: WordList.txt. We can now use the Powershell command Get-Content (or its shorter alias gc) to load the words, then iterate though them looking for a match with our regular expression:

    gc "WordList.txt" | foreach { if([regex]::match($_, $regex).Success) { $_ } }

We pipe the contents of the dictionary into the foreach loop, which makes each word available in the $_ variable. If we get a successful match from the [regex] then we write the word out to the Powershell console with the simple statement: $_

Wrapping the whole thing up in a function, we have our finished article:

    function GetWords([string]$letters)
    {
      $vowel = "[aeiouy]"
      $regex = "^" + $vowel + "*"
      foreach($char in [char[]]$letters) { $regex += $char + $vowel + "+" }
      $regex = $regex.substring(0, $regex.length - 1)
      $regex += "*$"
      gc "WordList.txt" | foreach { if([regex]::match($_, $regex).Success) { $_ } }
    }

Paste this into a Powershell console window and then call it with: GetWords("gn") and you'll receive the following recommendations:

    again, agene, agin, agon, agone, agony, eugenia, gaen, gain, 
    gan, gane, gaun, gen, gene, genie, genii, genoa, genu, genua, 
    gien, gin, gone, gonia, goon, gooney, goonie, goony, guan, 
    guanay, guano, guinea, gun, iguana, oogeny, oogonia, yogin, 
    yogini

If you want to learn more about memory tricks I'd highly recommend Derren Brown's book Tricks of the Mind, and anything by Dominic O'Brien.

]]>
Anthony Burns Development http://blog.anthonyburns.co.uk/Never-forget-a-thing-thanks-to-Powershell-and-Regular-Expressions http://blog.anthonyburns.co.uk/Never-forget-a-thing-thanks-to-Powershell-and-Regular-Expressions Friday, 6/13/2008 5:12:20 PM GMT
Quakk Twitter Client - New Beta Build It took a little longer than I'd hoped to get a new update sorted, and I didn't get as many new features added as I'd have liked, but when it comes to a choice of beers in the sun with the boys, or sitting inside coding, you can guess which one wins out every time.

In this release I managed to get the colours working on replies (they now show up in green); the tweets now automatically resize depending upon how much text is in them; each tweet also displays the date and time it was sent; and you can now send a direct message from the context menu by clicking on a tweet. Behind the scenes I did a lot of tidying up the code, but you don't really care about that do you?

QuakkSetup.cab

Copy the CAB file to your Windows Mobile device and run it, the CAB installer will install Quakk and create a shortcut in your Programs menu.

You might need to download and install the .NET Compact Framework 2 if you're running Windows Mobile 5 - if so, you can download that here.

I don't think there's much more I'll add to Quakk going forward. I'd like to pretty the UI up a bit and try to implement some gradients for your aesthetic pleasure; I'll probably add one or two extra options to the context menu; and there are one or two bugs I've started to notice that need fixing.

I'm suprised that no-one has left any bug reports in the comments or twittered them to me - I've had an average of 30 hits per day to the Quakk tag in this blog since I posted a link on the Twitter Wiki, and although I haven't been able to record the download counts so far, I find it hard to believe that no-one has downloaded it or encountered a bug - come on folks.

Alternatively, if you've downloaded it and like it, then follow me on Twitter (@littlecharva) and send me some love baby!

]]>
Anthony Burns Software http://blog.anthonyburns.co.uk/Quakk-Twitter-Client--New-Beta-Build http://blog.anthonyburns.co.uk/Quakk-Twitter-Client--New-Beta-Build Tuesday, 6/10/2008 8:56:49 PM GMT
Quakk Twitter Client - Beta Update

Just finished an update to my Windows Mobile Twitter client Quakk. You can download the latest version here:

QuakkSetup.cab

Copy the CAB file to your Windows Mobile device and run it, the CAB installer will install Quakk and create a shortcut in your Programs menu.

You might need to download and install the .NET Compact Framework 2 if you're running Windows Mobile 5 - if so, you can download that here.

Changes:

  • Added the ability to view your Replies as well as Direct Messages - under the Timeline menu.
  • Added a context menu that allows you to reply or open a link by clicking on the relevant Tweet.

Bugs Fixed:

  • No longer just quits when there is a problem connecting to Twitter.

I now need to fix some smaller bugs, refactor the code (it's becoming a bit of a mess under the hood), extend the context menu (View User's Timeline, Send Direct Message, Unfollow, etc.) and tart up the UI a little.

I hope to have another beta update out within a week.
Any comments, bugs or feature requests, Twitter me @littlecharva.

]]>
Anthony Burns Software http://blog.anthonyburns.co.uk/Quakk-Twitter-Client--Beta-Update http://blog.anthonyburns.co.uk/Quakk-Twitter-Client--Beta-Update Sunday, 5/25/2008 10:43:20 AM GMT
One-Click File Upload for IE in ASP.NET

DISCLAIMER: I'm in the privileged position of being able to dictate which browser our users use to access our extranet at work - I insist that IE must be used, not because I think it's a better browser, but because everyone has it installed by default and they know how to use it. By restricting to only one browser, I can speed up development times by not having to test our pages across multiple browsers. Because of that, this article is for IE only, and hasn't even been tested outside of IE7. If it proves interesting/helpful to people, then I might do an update later that works cross-browser.

I developed a webform for a project at work recently, that had a lot of form elements and that needed a file upload component adding.

The form was already too busy from a UI perspective and there wasn't a great location to plop down a file input element and submit button to upload the file. I decided I'd prefer it if I could just have an "Upload File" link, that when clicked prompted the user to select a file and caused the form to submit. I knew this was possible because Hotmail use a similar concept for attaching files to an email.

After a quick Google I discovered that the file input element in IE had a Javascript click() method - this I could use to open a file select dialog and I could then use form.submit() to upload the file. Simple.

I knocked up a quick prototype html file with the following code:

   1:  <script type="text/javascript">
   2:   
   3:  function UploadFile()
   4:  {
   5:      var form = document.UploadForm;
   6:      form.FileUpload.click();
   7:      form.SubmitButton.click();
   8:  }
   9:   
  10:  </script>
  11:   
  12:  <form id="UploadForm" name="UploadForm">
  13:    <input type="file" id="FileUpload" name="FileUpload" />
  14:    <input type="submit" value="Upload" id="SubmitButton" name="SubmitButton" />
  15:  </form>

When the link was clicked IE prompted me to select a file, but after I'd selected a file, IE gave me a Javascript "Access is denied" error. Bollocks.

After a long time spent on Google, I discovered that IE doesn't allow you to programmatically click the file input control and submit the form. Why on earth you'd want to click the control but not submit the form is beyond me. Even more bizarre is the fact that IE WILL allow you to do this, if the form is contained inside an IFrame. Bizarre, but a solution nonetheless.

So I constructed an UploadFile.aspx file containing the code below.

   1:  <form id="UploadForm" runat="server" enctype="multipart/form-data">
   2:      <asp:FileUpload ID="FileUpload" runat="server" />
   3:      <asp:HiddenField ID="UploadPath" runat="server" />
   4:      <asp:HiddenField ID="UploadLinkId" runat="server" />
   5:      <asp:Button UseSubmitBehavior="true" ID="SubmitButton" runat="server" />
   6:  </form>
   7:   
   8:  <a href="javascript:document.UploadForm.FileUpload.click();document.UploadForm.submit();">Upload File</a>

The form contains a file upload element, a submit button and two hidden fields. I plan on using javascript to set these hidden fields, one to enable me to specify a subfolder to upload the file to, and one to keep a reference to the "Upload File" link.

I then created a TestForm.aspx file containing a link and a hidden IFrame to contain my UploadFile.asp page:

   1:  <head>
   2:  <title>One-Click Upload</title>
   3:  <script type="text/javascript" src="Javascript/Upload.js"></script>
   4:  </head>
   5:  <body>
   6:   
   7:    <p><a href="#" id="UploadLink" onclick="return UploadFile(this, '');">Upload File</a></p>
   8:   
   9:    <iframe src="UploadFile.aspx" frameborder="0" id="UploadFrame" name="UploadFrame" style="display: none">
  10:    </iframe>
  11:   
  12:  </body>
  13:  </html>

In order to tie the "Upload File" link and the upload form within the IFrame together, I wrote the following Javascript function:

   1:  function UploadFile(link, uploadPath) {
   2:   
   3:    var uploadStatus = '<b>Uploading, please wait...</b>';
   4:   
   5:    // Check to see if we are already uploading a file
   6:    if(link.innerHTML.toLowerCase() == uploadStatus.toLowerCase()) {
   7:      alert('A file is currently being uploaded, please wait for it to finish before uploading another.');
   8:    } else {
   9:    
  10:      // Set our hidden fields and cause IE to prompt for a file
  11:      var form = UploadFrame.document.UploadForm;
  12:      form.UploadPath.value = uploadPath;
  13:      form.UploadLinkId.value = link.id;
  14:      form.FileUpload.click();
  15:   
  16:      // If the user has selected a file then submit the form
  17:      if(form.FileUpload.value != '') {
  18:        form.SubmitButton.click();
  19:        
  20:        // Make a copy of the link text and change it to show that the file is being uploaded
  21:        link.rel = link.innerHTML;
  22:        link.innerHTML = uploadStatus;
  23:      }
  24:    }
  25:    
  26:    return false;
  27:  }

It takes a reference to the link, and a subfolder to upload the file to as parameters. When called, it sets the ID of the link (to be utilised later on) and the upload path in the hidden form and then causes IE to prompt for a file. If a file is selected, it then submits the form and changes the text of the link to indicate that the upload is in progress.

On a subsequent click, it detects that the link contains the new text and explains to the user that an upload is already in progress.

So clicking the "Upload File" link prompts the user to select a file and if a file is selected, it submits the form upload the file.

The code behind the UploadFile.aspx file handles the uploading of the file:

   1:          protected void Page_Load(object sender, EventArgs e)
   2:          {
   3:              if (IsPostBack)
   4:              {
   5:                  string filePath = ConfigurationManager.AppSettings["OneClickUploadFilePath"];
   6:                  if (UploadPath.Value != "")
   7:                  {
   8:                      filePath = Path.Combine(filePath, UploadPath.Value);
   9:                  }
  10:   
  11:                  if (!Directory.Exists(filePath))
  12:                  {
  13:                      Directory.CreateDirectory(filePath);
  14:                  }
  15:   
  16:                  FileUpload.SaveAs(Path.Combine(filePath, FileUpload.FileName));
  17:   
  18:                  Body.Attributes["onload"] = "UploadFinished('" + UploadLinkId.Value + "');";
  19:              }
  20:          }

First we get the upload path from the Application Settings of the web.config file (obviously you set this yourself in your own web.config. This is then combined with any subfolder passed through the form, the directory is created if it doesn't already exist and the uploaded file is saved to it.

The final line sets the onload attribute to call a Javascript function called UploadFinished with the ID of the link received from the form. That function can be used to notify the user that the file has finished uploading. In my case I also wanted to offer the option of refreshing the page, as my pages contain a list of the uploaded files:

   1:  function UploadFinished(linkId)
   2:  {
   3:    if(confirm('The file has finished uploading, would you like to refresh the page?')) {
   4:      parent.location.reload(true);
   5:    } else {
   6:      var link = parent.document.getElementById(linkId);
   7:      if(link != null) {
   8:        link.innerHTML = link.rel;
   9:      }
  10:    }
  11:  }

First it asks the user if they would like to refresh the page and if they click "Yes" then the page reloads. If they click "No", then the function uses the link ID - that has been passed through the form - to set the link text back to its original text, thereby enabling the user to upload an additional file.

So there we have it: upload a file using a single link rather than a pair of ugly form controls.

]]>
Anthony Burns Development http://blog.anthonyburns.co.uk/OneClick-File-Upload-for-IE-in-ASP.NET http://blog.anthonyburns.co.uk/OneClick-File-Upload-for-IE-in-ASP.NET Wednesday, 5/21/2008 9:47:51 PM GMT