Simple Silverlight Uploader with Progress Bar using HttpRequest or WCF

I thought I’d write a little bit about a simple Silverlight file uploader I’ve been playing with. I see a lot of postings out there asking how to do a file uploader that can show upload progress using Silverlight. There are a few issues to overcome like: how to you actually measure progress, and while using the HttpRequest, how do you update the UI from the worker thread?  So this project will hopefully answer some of your questions.

Client-Side

image

image

I should make a quick mention that this can be done using either WCF or a simple HTTP request. In my opinion, I think using HTTP request provides better performance and comes with less deployment and maintenance hassle; but likewise it leaves a few other loose ends like security and message integrity (if left unhandled). For this example, I’ll mostly only discuss the HTTP request method. My sample code includes a WCF implementation for reference. (Maybe some of you WCF gurus can tell me how that looks). ;)

image

So first of all, progress...  How can we measure it?  Due to the max request length constraints in your web application, 4MB by default in Asp.Net, we’ll need to chunk the file by hand to accommodate large files. And in doing so, we can count each time a chunk was successfully sent to the server. So progress is simply counting the number of chunks sent divided by the total number of chunks that need to be sent.  In most cases, on smaller files, you probably won’t have too many chunks. Also, larger chunks seem to perform the better during upload. I tried several different chunk sizes, and it seemed around 100-500k gave me the best of both worlds with having a good progress measurement and not being too chatty where it slows down the upload too much.

All of the file handling and server communication is managed by a FileSender object, which is responsible for a single file upload. ListItem encapsulates the FileSender to make it easier to work with. It’s also initialized using the ListItem control’s Dispatcher property for UI thread callbacks later on during its Completed and ProgressChanged events.

During upload, every chunk that is sent to the server is hashed before sending it and after receiving it to make sure it was a valid transfer. The entire file is also hashed after all chunks are sent to validate the end result matches the original file. If any chunk fails to pass the hash test, it’s kicked back and re-sent. It will attempt to resend the same chunk up to four times (by default) before failing completely and killing the upload.  If the full file hash fails, it will not try to resend the data; it will only notify the UI that the upload has failed.

This diagram shows the basic structure of the client-side program flow.

image

When the upload has finished, it will invoke the Completed event to let the caller know it’s done uploading. It will pass along the new filename or any errors that occurred during the upload. One of the challenges I mentioned earlier was related to invoking code in the UI after using the HttpRequest object. For this, we used the parent control’s Dispatcher property to invoke Completed and ProgressChanged. With the custom event delegates already defined, this worked out real well.

void OnCompleted(SendStatus status, string newFilename, string message)  
{
    if (_Completed != null)
    {
        Delegate d = new FileSenderCompletedEventHandler(_Completed);

        FileSenderCompletedEventArgs e = new FileSenderCompletedEventArgs();
        e.NewFilename = newFilename;
        e.Status = status;
        e.Message = message;

       this._UIDispatcher.BeginInvoke(d, null, e);//use the UI Dispatcher to invoke the event
    }
}

Server-side

So now for the server side.  The destination folder is configured via an AppSettings variable called StorageFolder.  I set the default value to ~/Repo.  I built a generic handler to handle the request and pass data around using serialized XML of the request and response objects.

As requests come in, the handler peeks at the first element of the message, identifies its type, deserializes it, and hands it off to a processor function. The basic idea is to receive all the chunks identified by the same token and append them to a file. When the handler receives a FinishRequest for that token, the file is renamed and moved to a more permanent location, and the new file information is returned in a response.  For new files, an empty Guid is passed along with the first chunk. The handler starts a brand new file and responds with a new Guid token, which the caller will use in subsequent requests.

NOTE: There are many enhancements that can be made to this code. Like for example dead uploads.  If one fails, the guid temp file may live out there forever unless you clean it up. So a good tool to build would be one to go through and wipe files that haven’t been touched for awhile. Another important enhancement is to include security. Since this is just a sample; it's totally open ended. It would be a good idea to lock out anonymous requests by using authentication/authorization or another security mechanism.

Anyway, the serialization works pretty consistently between Silverlight and full .Net, so I copied my request and response objects to both projects (client and server) and use them to serialize and deserialize the messages. This is where a small chunk size can hurt the upload performance since there is a little bit of overhead data using XML.  A higher performance alternative to XML would be to use query string parameters for all the details and write the file data to the request stream. As I said before, it’s pretty open ended at this point.

Where’s The Code!

(NOTE: Default.aspx = WCF and HTTPUpload.aspx = HTTP method described above)

UploaderSL.zip 1.2MB  (Updated 12/19/09 with minor pathing fix mentioned in comments)

To open this project, you’ll need the free Silverlight3 Tools.

In Summary

My philosophy with samples like this is to leave them completely open ended for you all to modify and play with. I left quite a few things unfinished both on server side and client side; so I encourage you to change whatever you like. And let me know what you think! Just email me at nathan at integrated web systems dot com.

Enjoy!