Home > Silverlight > 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!

  1. boukaka
    December 17th, 2009 at 10:01 | #1

    I’m new to Silverlight and I’ve integrated your uploader into my silverlight application but I run into issues getting the server to respond (getting Not found error on web request). Do you have step by step instructions for how to deploy a silverlight application with a file uploader in it?

  2. December 17th, 2009 at 14:51 | #2

    I just tested a few scenarios out and found an issue when deploying this site to a virtual directory vs the root of the web server. I’m guessing that’s your web application end point, a virtual directory?

    I found the culprit in FileSender.cs line 61. Change it to:
    builder.Path = builder.Path.TrimEnd(‘/’) + “/Services/FileReceiver.ashx”;

    I’m laughing a little bit because I noticed the //HACK: comment there. Apparently I knew of something when I wrote that, and I had forgotten about it. ;) I’ll post an update build with new code, but that one line fix should get you going in your integrated application.

    There isn’t anything special you need to do for deployment. The endpoint should automatically figure out where it’s at based on your request path. That function caused that not to work. I think the only real caveat is that the FileReceiver.ashx handler is hard coded there; so make sure your integrated app has the same relative path/handler filename. (Services/FileReceiver.ashx).

    Let me know how that goes. Thanks

  3. boukaka
    December 17th, 2009 at 16:27 | #3

    Thank you for that fix however that did not fix my issue. I sent you an email with an additional question however, I put breaks on all of the methods and stepped through the code, the spot where the exception is thrown is below:

    void ReadResponseStream(IAsyncResult result)
    {
    try
    {
    WebRequest req = (WebRequest)result.AsyncState;
    WebResponse resp = req.EndGetResponse(result); <– breaks

    Error: base {System.InvalidOperationException} = System.InvalidOperationException

    I have searched online and it seems to be a popular error but I have yet to find a solution.

  4. boukaka
    December 18th, 2009 at 12:33 | #4

    I found what my problem was in case anyone else runs into it. The Web service wasn’t being found because I had this error. The markup “Service=” didn’t match the naming convention in the class.

    **** class
    namespace AMS_SL3_new.Web.Services
    {
    public class FileReceiver : IFileReceiver

    ***** markup (corrected to match namespace and class name)

  5. boukaka
    December 18th, 2009 at 12:34 | #5

    woops .. ServiceHost Language=”C#” Factory=”System.Data.Services.DataServiceHostFactory, System.Data.Services, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″ Service=”AMS_SL3_new.Web.Services.FileReceiver”

  6. December 18th, 2009 at 13:30 | #6

    Ah yeah. With the WCF version, that will cause that. Nice job finding that. Sometimes just browsing your WCF endpoint will indicate whether that’s configured correctly or not too. Glad you got it working.

  7. December 28th, 2009 at 12:14 | #7

    Hey Nathan!

    Excellent post, I like the way you just go for “asynchronous” mode, quite tempting setting up a loop and block the UI thread.

    Congratulations.

  8. December 28th, 2009 at 13:40 | #8

    Thanks! @Braulio

  9. Richard
    February 1st, 2010 at 22:30 | #9

    Great article!!! That’s exactly what I was trying to do. Thank you, it works on my machine.

  10. February 2nd, 2010 at 08:51 | #10

    @Richard
    Awesome!

  11. July 19th, 2010 at 07:01 | #11

    hi,
    how to change upload destination folder?

  12. July 19th, 2010 at 08:28 | #12

    @mdda
    Checkout the web.config. There’s an app setting in there that specifies the destination folder called “StorageFolder”.

  1. January 18th, 2010 at 04:52 | #1