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”.

  13. November 7th, 2010 at 21:36 | #13

    Great Post! I have been playing with the uploader and found something interesting. Does the WCF version use GET? I bumped the chunk size up to 30000 and it fails right away. If I put it back down to 10000 it works fine. Your HTTP version work fine with different sizes but the WCF version really does not like a higher chunk size. Searched around but could not figure out the exact cause for the error. Thoughts?

    Jeff

  14. November 8th, 2010 at 10:11 | #14

    Just figured out my issue. Looks like the server is limiting the post size from WCF. I used this article and it solved my issue. Basically you are bumping up the size of the WCF post allowed. Just gotta follow all their directions and it should work.

    http://smehrozalam.wordpress.com/2009/01/29/retrieving-huge-amount-of-data-from-wcf-service-in-silverlight-application/

  15. November 8th, 2010 at 10:30 | #15

    @Jeff Cieslik

    Thanks! Yeah, you beat me to it.

    With the vanilla HTTP post, the only limitation is the max request size in asp.net. With WCF, there are a few more configuration changes like you pointed out: max buffer size and max message size. I think the asp.net max request size also plays a role as well. For this test project, the idea was to keep the chunks smaller than the default limitations that way it would work with a file of any size.

    Thanks!

  16. Bartek
    November 18th, 2010 at 18:26 | #16

    @Jeff Cieslik
    I have been working and fighting with the same issue. When chunk is 10k – everything works OK. But when I change it for bigger than 15k – Application stucks :-(

    I have applied everything with the link you gave – but still no luck… What and how did you change in Web.config?

    Regards!

  17. November 18th, 2010 at 19:50 | #17

    @Bartek

    I’ll just pushed the code out to github. I’ll try and look at it sometime over the weekend. Let me know if you figure it out first.

  18. dfcc1970
    December 16th, 2010 at 12:30 | #18

    I upgrade the code to Visual Studio 10, I did fix ListItem.xaml.cs the wrong line is:

    read = fs.Read(buffer, 0, Utility.chunkSize);

    adn should be:

    read = fs.Read(buffer, 0, buffer.Length);

    I did delete the Solucion so only needed 2 projects,1 for Silverlight and 1 for Web application with WFC services, i also modify the web.config and ServiceReferences.ClientConfig so now you can upload large files (i did test with 260Mb file)
    also i did modify utility.cs the line public const int chunkSize = 500000;

    So I want to post the final solution but i dunno how. email me dfcc1970@yahoo.com. Thanks

  19. dfcc1970
    December 16th, 2010 at 12:33 | #19

    I also modify several cs files, so the application will upload with the same original source filename, and if the file exist it overwrite it at the server.

  20. Dmasty
    April 16th, 2011 at 05:09 | #20

    i find an message or error on Default.aspx where is called the Uploader.Client. if the user upload the file less that 1Kb then will get “Offset and length were out of bounds for the array or count is greater….” message .. it is error or only a message ?? can the silverlight upload a file less that 1 Kb ?

  21. April 22nd, 2011 at 09:57 | #21

    @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. :)

  22. May 10th, 2011 at 03:06 | #22

    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 :-)

  23. YongYong
    June 2nd, 2011 at 22:05 | #23

    this is awesome!
    Thanks for the samples!

  24. June 3rd, 2011 at 09:26 | #24

    @YongYong
    You bet!

  25. June 3rd, 2011 at 09:27 | #25

    @Matthew Blott
    Haha awesome! Glad it worked!

  26. Saieesh
    July 3rd, 2011 at 23:29 | #26

    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

  27. July 5th, 2011 at 08:03 | #27

    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!

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