ASP.NET File Upload with *Real-Time* Progress Bar

I had a number of people email me asking how the ProgressBar Toolkit control I blogged about last month could be used to provide progress for file uploads.  So I thought I would spend some time and see how this could be done.  Thankfully, there are a ton of resources available on the internet that discuss this in pretty good detail.  But I didn't see any good examples of how to include the real-time count of the number of bytes that have been transferred.  So I thought I would try to tackle that problem while building my example.  

Update 7/27/2008: This post is bogus - I have over simplified the problem.  My goal was to display the number of bytes that have been transferred to the web server, not the number that have been saved to disk.  As atashbahar pointed out, this will require a little bit more work that what I have shown.  I followed atashbahar's suggestion and downloaded the NeatUpload control (its free) and started taking a look at its HttpModule.  I am going to take a look at how the NeatUpload handles uploading large files and will update this post or create a part II that outlines what I have found.

 

Also, a couple of quick notes on the demo.  The first is that the demo looks like crap in FF, sorry but I gave up styling the input element.  I will come back to it later.  Second, I have a limited amount of bandwidth that I can use each month, so I don't accept files larger than 250 bytes.  I recommend creating a real small text file and using that to upload to the demo site.  I have setup the demo to upload only a handful of bytes per second, so you will be able to get a feel for how the progress bar works even with real small files.   

Live Demo | Download

image

image

image

Basic Approach

A few minutes of googleing and you will learn that creating the file upload widget above includes the following pieces ...

  • A input element for collecting the file.  I am using the ASP.NET FileUpload control to render this.
  • 2 ASP.NET pages.  One contains the FileUpload control and has codebehind logic that reads the file from the response and writes it some place.  And a second page that contains an IFRAME with the src attribute that points to the upload page.
  • A UI widget for displaying the upload's progress.  I have used the progress bar control I have blogged about previously

The general idea is that when the Upload button is clicked, a bit of JavaScript runs that causes the form that contains the FileUpload control to submit.  Because just the nested form is submitting the main content page remains unchanged (i.e. it isn't refreshed).

Here is the markup for the Default page

   1: <form id="form1" runat="server">
   2: <div>
   3:     <div class="upload">
   4:         <h3>File Upload</h3>
   5:         <div>
   6:             <iframe id="uploadFrame" frameborder="0" scrolling="no" src="Upload.aspx"></iframe>
   7:             <mb:ProgressControl ID="progress" runat="server" CssClass="vista" style="display:none" Value="0" Mode="Manual" Speed=".4" Width="100%" />
   8:             <div>
   9:                 <div id="status" class="info">Please select a file to upload</div>
  10:                 <div class="commands">
  11:                     <input id="upload" type="button" value="Upload" /> 
  12:                 </div>
  13:             </div>
  14:         </div>
  15:     </div> 
  16: </div>
  17: </form>

 

And here is the Upload page

   1: <form id="form" runat="server" enctype="multipart/form-data">
   2: <div>
   3:     <asp:FileUpload ID="fileUpload" runat="server" Width="100%" />
   4: </div>
   5: </form>

 

Posting the File - Building the Upload.aspx Page

This is the complicated part of this implementation.  When the Upload.aspx page is submitted, I want to take the uploaded file off of the response and save it to disk.  But, because I want to display the real-time progress, I need a way to get at how much of the uploaded file has been transferred.  In my first crack at solving this, I tried just calling SaveAs on the HttpPostedFile object and then using a regular WebMethod that would return the size of the file on disk.  Something like this ...

   1: protected void SaveUploadedFile(string filePath)
   2: {
   3:     //  save the file
   4:     this.fileUpload.PostedFile.SaveAs(filePath);
   5: }
   6:  
   7: public int GetFileSize(string filePath)
   8: {
   9:     //  get the file size on disk
  10:     return new FileInfo(filePath).Length;
  11: }

 

But, that wasn't working too well because the file was being buffered as it was being written to disk so my progress bar would jump from 0 to 67 to 100.  Which wasn't what I wanted.  So to get around this, I decided that I would try handling saving the file myself.  To do this I manually read X bytes off the response stream and write it to a FileStream (where X is the size of the buffer).  For the demo I have set X to 1, so each byte is read/written individually, but for non-demo purposes I believe I will be updating the size of the buffer based on the size of the file being uploaded.  The smaller the buffer size, the longer the file takes to upload, but the more accurate our progress bar is.  And of course, a large buffer will upload faster, but our progress bar won't be as accurate. 

   1: //  DEMOWARE: set the buffer size to something larger.
   2: //  the smaller the buffer the longer it will take to
   3: //  download, but the more precise your progress bar will be.
   4: //  to large of a value and the progress bar will make real large jumps
   5: int bufferSize = 1;
   6: byte[] buffer = new byte[bufferSize];
   7:  
   8: //  get the status object from session
   9: UploadInfo uploadInfo = this.Session["UploadInfo"] as UploadInfo;
  10:  
  11: //  write the byte to disk
  12: using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.Create))
  13: {
  14:     //  aslong was we haven't written everything ...
  15:     while (uploadInfo.UploadedLength < uploadInfo.ContentLength)
  16:     {
  17:         //  fill the buffer from the input stream
  18:         int bytes = this.fileUpload.PostedFile.InputStream.Read(buffer, 0, bufferSize);
  19:         //  write the bytes to the file stream
  20:         fs.Write(buffer, 0, bytes);
  21:         //  update the number the webservice is polling on
  22:         uploadInfo.UploadedLength += bytes;
  23:     }
  24: }

 

Checking the Progress

Back on the main page (Default.aspx), I have setup a Page Method that retrieves the number of bytes that have been written to the FileStream as well as a friendly status message that reports the total number of bytes that have been transferred so far. 

   1: [System.Web.Services.WebMethod]
   2: [System.Web.Script.Services.ScriptMethod]
   3: public static object GetUploadStatus()
   4: {
   5:     //  get the length of the file on disk and divide that
   6:     //  by the length of the stream ...
   7:     UploadInfo info = HttpContext.Current.Session["UploadInfo"] as UploadInfo;
   8:  
   9:     if (info != null && info.IsReady)
  10:     {
  11:         int soFar = info.UploadedLength;
  12:         int total = info.ContentLength;
  13:  
  14:         int percentComplete = (int)Math.Ceiling((double)soFar / (double)total * 100);
  15:         string message = string.Format("Uploading {0} ... {1} of {2} Bytes", info.FileName, soFar, total);
  16:         
  17:         //  return the percentage
  18:         return new { percentComplete = percentComplete, message = message };
  19:     }
  20:     
  21:     //  not ready yet ...
  22:     return null;
  23: }

 

And back on the client, I have setup a bit of JavaScript that calls the GetUploadStatus PageMethod every second or so.  When the status changes, the script updates the progress bar to the new value and updates the status text.

   1: //  start polling to check on the progress ...
   2: var intervalID = window.setInterval(function(){
   3:     //  call the GetUploadStatus Page Method
   4:     PageMethods.GetUploadStatus(function(result){
   5:         if(result){
   6:             //  update the progressbar to the new value
   7:             progressBar.set_percentage(result.percentComplete);
   8:             //  upadte the message
   9:             updateMessage('info', result.message);
  10:             
  11:             if(result == 100){
  12:                 //  clear the interval so we stop polling
  13:                 window.clearInterval(intervalID);                        
  14:             }
  15:         }
  16:     });
  17: }, 500); 

 

Conclusion

I am not using this in production (yet!).  So if you find some issues with it, just leave a comment or drop me an email.  And there is still a bunch that can be approved (like supporting multiple files), so if you make any mods to this, let me know that too so I don't have to redo any of your hard work ;)

 

That's it.  Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/140

Comments


So good. I've been run your demo, and it run smoothly. Thanks for your great sample.

Posted by: atashbahar on July 27, 2008 12:16 AM

I think there is a problem with this. ASP.NET engine first caches the whole request (that includes the uploaded files) and then processes them. This means you won't reach the code that processes the upload until upload is finished (this is more clear when you upload bigger files).
One way to have real time progress is to write a HTTP Module that handles the request directly and parses it for files. It is not a very easy thing to do though.
There are tons of commercial upload controls out there but personally I like NeatUpload (http://www.brettle.com/neatupload) because it is open source and free. NeatUpload has a great module for handling uploads.
Another option is to use flash for uploads. There is a great component named SWFUpload (http://swfupload.org/) for this matter (this is free too).

Posted by: zj on July 27, 2008 03:28 AM

You strong, and support you.

@atashbahar -

Arghh ... My solution doesn't work to well then. I am going to take a peak at NetUpload and see how that control works.

Thanks for pointing this out.

Matt.

By the same token, you are not really limiting the size of the file they can upload. You are limiting the size of file they can save to the server disk. The request size restriction is too low-level for ASP.NET to handle programmatically, but there's a web.config setting under system.web (httpRuntime maxRequestLength) to set the maximum, in kilobytes. When they go over this specified limit, they get cutoff by IIS, and end up seeing a "Page cannot be displayed".

Sorry for trying to upload a 700MB file, by the way :)

Regards...

I've looked at a few different controls:
http://remy.supertext.ch/2008/01/file-upload-with-aspnet/
and NeatUpload came out as the winner, because it was one of the few controls that really worked in every browser. We use it in Production since about 3 months now and had no issues with it so far.

It does have a few drawbacks though:
- It does make a page refresh at the beginning and at the end, so it's not as sexy as it could be
- Only classic mode on IIS 7

And it's not using ASP.NET Ajax, not a big deal, but it does put a lot of javascript into the page.

@Josh -
Thanks Josh. I updated the web config. Boy, ever had a post that you wish you hadn't written? This is one of them for me ;) I severly misunderstood this problem!

I did look into the NeatUpload though and I am intrigued about all of the stuff going on. I think I am going to spend sometime and see if I can integrate with the HttpModule it is using or create a new module based on this one.

@Remy -
Thats good info to know, cuz I am going to have do something like this in production as well. After I spend some time with the NeatUpload module, hopefully I can address some of these issues.

One big problem with all Upload tools is that they only work in classic Mode in IIS 7. So far the only workaround seems to be to hack into this stuff with Reflection. There is a big discussion on:

http://www.brettle.com/ForumThreadView.aspx?thread=441&pageindex=7

Good luck :-)

There is a nice File Upload HttpModule available (source available) on
http://darrenjohnstone.net/2007/06/02/writing-an-aspnet-file-upload-module/

The module works in conjunction with a special HttpHandler to report on the progress of the upload.

On top of that, the post is clear, very well written, and includes ample technical information on the details of the implementation.

Mix both together and you've got one powerfull uploader.
BTW: your upload webcontrol creates an iframe that references upload.aspx; why not implement upload.aspx as a Custom HttpHandler as well, that way you don't have to bother with the page and everything can be included in one dll.

Posted by: Cristiano on August 1, 2008 11:55 AM

How execute in VS 2005 ?
Dont work

Posted by: krunal on August 2, 2008 04:38 AM

Demo is working when i run thru visual studio (vs development web server).

But, when i publish it in local - Vista 64 SP1 (IIS 7)
it's not working....

its just says ... initlizing... and upload button got disabled and nothing happens afterwords.

thanks,
krunal

Posted by: Nameless Samurai on August 12, 2008 08:35 PM

Thank you your demo is awesome!

Keep up that creativity and good work!

Posted by: Albert on August 13, 2008 11:14 AM

Your work is great!
I just have one question. If I move the Upload.aspx and Default.aspx files away from the root folder... I get this error:

The type or namespace name 'upload_aspx' could not be found (are you missing a using directive or an assembly reference?)

Is it possible to do this or do all the files and folders have to be in the same root directory as the Bin folder?

Thanks,
Albert

Posted by: Sly on August 21, 2008 06:09 AM

Hi, Matt!
Did you know how to find the way to catch exception on FileUpload ctrl, then the name of the file is like 'dasfasdgf'?
I have a page with tab 'Upload' and 'Main'.
Then i wrote some name on keyboard, like 'dasfasdgf' and after this choose tab 'Main' i have exception:

"htmlfile: Access is denied"

Any ideas?

Posted by: nirav pandya on September 12, 2008 12:38 AM

is there any demo for multiple file upload in asp.net

Posted by: sandeep on September 16, 2008 03:50 AM

How can use dll of materberseth ajax control how to embed controls of this ajaxtool kit, How to download ajax tool kit

Posted by: arjun on September 27, 2008 03:52 AM

Buffered file download using asp.net Buffered File Download from Asp.Net Web Server.

Posted by: Mohammad AL Hoss on October 26, 2008 03:32 AM

HI

I am having this error "Error 1 Child nodes not allowed. (c:\inetpub\wwwroot\webroot\web.config line 79)"

when trying to run the code can u help please i am using .Net 2.0 thanks.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Mohammad AL Hoss wrote: HI I am having this error "Error 1 Child nodes not allowed. (c:\inetpub\wwwroot\webroot\web.config ...
  • arjun wrote: Buffered file download using asp.net <a href='http://developmentzone.blogspot.com/2008/09/buffered-f...
  • sandeep wrote: How can use dll of materberseth ajax control how to embed controls of this ajaxtool kit, How to dow...
  • nirav pandya wrote: is there any demo for multiple file upload in asp.net...
  • Sly wrote: Hi, Matt! Did you know how to find the way to catch exception on FileUpload ctrl, then the name of t...
  • Albert wrote: Your work is great! I just have one question. If I move the Upload.aspx and Default.aspx files away ...
  • Nameless Samurai wrote: Thank you your demo is awesome! Keep up that creativity and good work!...
  • krunal wrote: Demo is working when i run thru visual studio (vs development web server). But, when i publish it i...