Simple Silverlight Uploader with Progress Bar using HttpRequest or WCF

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!

28 Comments

  • Nathan on Jul 05, 2011

    Hi @Saieesh

    I believe it does support unlimited files sizes if you remove the size cap in the code. Each chunk is < 4MB per HTTP request, so they just build on each other to any size. It does not have a resume; although with a little creativity, you could probably devise some way to make that work. The data is validated with hashes SHA256 I think. Each individual chunk is hashed and compared to the pre-hash sent with the chunk. Then the full received file data is re-hashed on the server and compared to the hash sent in the final request. It’s not very fancy; this was kind of a concept demo when I wrote it.

    Thanks!

  • Saieesh on Jul 03, 2011

    Hi, wonderful code. few questions
    does it support unlimited file size upload
    how do i resume upload in case of interruption from the point of interruption
    how does the code validate the uploaded data

  • Nathan on Jun 03, 2011

    @Matthew Blott
    Haha awesome! Glad it worked!

  • Nathan on Jun 03, 2011

    @YongYong
    You bet!

  • YongYong on Jun 02, 2011

    this is awesome!
    Thanks for the samples!

  • Matthew Blott on May 10, 2011

    Excellent project. I can’t believe I’ve just downloaded a project from the web that’s worked without any problems – that must be a first! I’ve just deployed it to our production server and it still worked. Thanks, much appreciated :-)

  • Nathan on Apr 22, 2011

    @Dmasty
    Good catch. That’s likely a bug. I’ll peek at it when I get a chance. If you code the solution, fork the repo and submit a pull request; I’ll be happy to accept it. :) Here is the github project.

    I also built another uploader recently that uses just a regular HTTP Multi-part/form post and had something very similar to this. I’ll be posting the new uploader soon. Things have been pretty busy @ home. :)

Leave Reply