Simple (?) AJAX Upload For ASP.NET
As I was working on an AJAX upload web part for SharePoint, I looked around to see if there was anything out there that would be suitable before I rolled my own after discovering that the ASP.NET UpdatePanel doesn’t play nicely with file inputs. Since I’m using jQuery, I figured I’d start there and see if there were any plugins which would meet the need.
To my dismay, it seems as if kids these days are all using flash (flash!) to implement asynchronous upload. This was wholly unacceptable to me for some reason (not to mention it might cause compatibility issues for downstream clients) so I ended up tackling it myself š
The basics of getting this to work are simple enough; the solution is made of three main components:
- The main page that is displaying the upload control. Since I was using this in a SharePoint site, I wanted to write my uploader as a web part (the control) that could be placed on any page. The main page is simply the container for your control. It does not post back.
- The control that basically just renders the <iframe/>. The control is relatively simple. It provides a container for the <iframe/> and also the scripts which are executed when the frame is loaded and unloaded. The control also holds the progress layer since we want this to be shown when the user starts the upload. This also does not post back.
- The upload page that receives the actual file. This page is the target for the <iframe/> and contains the logic that validates the posted file. This is the only page that posts back.
Before we go into the details, let’s look at the screens of this in action (pay particular attention to the times):
The first screen shows the general layout of the page in the default state. In case you’re wondering, I found a handy guide for hacking the file input control and used that to customize the appearance. Again, note the time.
Once you click “Upload”, a progress layer is shown over the control. You can also see that we’ve got an unloaded time now as well.
And finally, you can see that the loaded time changed once the form upload completes.
The control ASCX file contains two very simple scripts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<span style="font-family: Lucida Console;"> <span style="color: #0000ff;">function </span><span style="color: #800000;">handleFrameLoaded() {</span> <span style="color: #008000;">// Do animation here.</span> <span style="color: #800000;">$(</span><span style="color: #ff00ff;">"#progress"</span><span style="color: #800000;">).hide();</span></span> <span style="font-family: Lucida Console;"> <span style="color: #800000;">$(</span><span style="color: #ff00ff;">"#load"</span><span style="color: #800000;">).html(</span><span style="color: #ff00ff;">"<b>Loaded!</b> " </span><span style="color: #800000;">+ </span> <span style="color: #800000;">(</span><span style="color: #0000ff;">new </span><span style="color: #ff0000;">Date</span><span style="color: #800000;">()).toTimeString());</span> <span style="color: #800000;">}</span></span> <span style="font-family: Lucida Console;"> <span style="color: #0000ff;">function </span><span style="color: #800000;">handleFrameUnloaded() {</span> <span style="color: #008000;">// Do animation here.</span> <span style="color: #800000;">$(</span><span style="color: #ff00ff;">"#progress"</span><span style="color: #800000;">).show().fadeTo(</span><span style="color: #ff00ff;">"fast"</span><span style="color: #800000;">, .</span><span style="color: #800080;">90</span><span style="color: #800000;">);</span></span> <span style="font-family: Lucida Console;"> <span style="color: #800000;">$(</span><span style="color: #ff00ff;">"#unload"</span><span style="color: #800000;">).html(</span><span style="color: #ff00ff;">"<b>Unloaded!</b> " </span><span style="color: #800000;">+ </span> <span style="color: #800000;">(</span><span style="color: #0000ff;">new </span><span style="color: #ff0000;">Date</span><span style="color: #800000;">()).toTimeString());</span> <span style="color: #800000;">}</span></span> |
The first function is called when the frame is loaded and the second function is invoked when the file upload is submitted. Note that both functions are called from the upload page in the <iframe/>. In this case, I’ve just added simple animation calls to show and hide a progress panel. You can hook up whatever custom code you want here.
On the upload page itself, we need to wire the events to the functions above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<span style="font-family: Lucida Console;"> <span style="color: #800000;">$(</span><span style="color: #ff0000;">window</span><span style="color: #800000;">).load(</span><span style="color: #0000ff;">function</span><span style="color: #800000;">() {</span> <span style="color: #800000;"><strong>parent.handleFrameLoaded();</strong></span> <span style="color: #800000;">});</span></span> <span style="font-family: Lucida Console;"> <span style="color: #800000;">$(</span><span style="color: #ff0000;">document</span><span style="color: #800000;">).ready(</span><span style="color: #0000ff;">function</span><span style="color: #800000;">() {</span> <span style="color: #800000;">$(</span><span style="color: #ff00ff;">".upload-button"</span><span style="color: #800000;">).click(</span><span style="color: #0000ff;">function</span><span style="color: #800000;">() {</span> <span style="color: #800000;"><strong>parent.handleFrameUnloaded();</strong></span> <span style="color: #800000;">});</span></span> <span style="font-family: Lucida Console;"> <span style="color: #008000;">/* simulate hover */</span> <span style="color: #800000;">$(</span><span style="color: #ff00ff;">"#fake-container"</span><span style="color: #800000;">).hover(</span> <span style="color: #0000ff;">function</span><span style="color: #800000;">() {</span> <span style="color: #800000;">$(</span><span style="color: #0000ff;">this</span><span style="color: #800000;">).addClass(</span><span style="color: #ff00ff;">"hover"</span><span style="color: #800000;">);</span> <span style="color: #800000;">},</span> <span style="color: #0000ff;">function</span><span style="color: #800000;">() {</span> <span style="color: #800000;">$(</span><span style="color: #0000ff;">this</span><span style="color: #800000;">).removeClass(</span><span style="color: #ff00ff;">"hover"</span><span style="color: #800000;">);</span> <span style="color: #800000;">});</span></span> <span style="font-family: Lucida Console;"> <span style="color: #008000;">/* simulate populating the file value since </span> <span style="color: #008000;"> we can't see the file input */</span> <span style="color: #800000;">$(</span><span style="color: #ff00ff;">"input.file"</span><span style="color: #800000;">).change(</span><span style="color: #0000ff;">function</span><span style="color: #800000;">() {</span> <span style="color: #800000;">$(</span><span style="color: #ff00ff;">"#fake input"</span><span style="color: #800000;">).val($(</span><span style="color: #0000ff;">this</span><span style="color: #800000;">).val());</span> <span style="color: #800000;">});</span> <span style="color: #800000;">});</span></span> |
It’s important to note that the upload page gets its own set of window events since it’s loaded inside of the frame. The upload page makes calls to functions in the control. I’ve highlighted the points of interest; you’ll note that I only bind the load event of the window (I don’t bind the unload). It’s also possible to do this using the onbeforeunload event, but I found that this would fire the progress layer even if I was browsing away from the page (which may confuse your users). So it made more sense to just do it simply from the upload button click.
The upload page itself is remarkably simple:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<span style="font-family: Lucida Console;"><span style="color: #0000ff;"><body></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><form </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"_form" </span><span style="color: #ff0000;">runat</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"server"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><div></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><div </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"fake-container"</span><span style="color: #0000ff;">></span><span style="color: #000000;"> </span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><input </span><span style="color: #ff0000;">type</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"file" </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"_file" </span><span style="color: #ff0000;">runat</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"server" </span><span style="color: #ff0000;">class</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"file"</span><span style="color: #0000ff;">/></span><span style="color: #000000;"> </span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><div </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"fake"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><input </span><span style="color: #ff0000;">type</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"text" </span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></div></span><span style="color: #000000;"> </span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></div></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><asp:Button </span><span style="color: #ff0000;">runat</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"server" </span><span style="color: #ff0000;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"_upload" </span> <span style="color: #ff0000;">Text</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Upload" </span><span style="color: #ff0000;">OnClick</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"HandleUploadClick" </span> <span style="color: #800080;">CssClass</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"upload-button"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></div></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></form></span> <span style="color: #0000ff;"></body></span></span> |
The control isn’t much more complex either:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<span style="font-family: Lucida Console;"><span style="color: #0000ff;"><div </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"frame"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> This is an asynchronous upload control. </span> <span style="color: #000000;"> The control load time is </span><span style="color: #0000ff;"><asp:Label </span><span style="color: #ff0000;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"_time" </span><span style="color: #ff0000;">runat</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"server"</span><span style="color: #0000ff;">/></span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> </span><span style="color: #0000ff;"><iframe </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"upload-frame" </span><span style="color: #ff0000;">src</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Upload.aspx" </span> <span style="color: #ff0000;">frameborder</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"0" </span><span style="color: #ff0000;">scrolling</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"no" </span><span style="color: #ff0000;">height</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"100px"</span><span style="color: #0000ff;">></span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> </span><span style="color: #0000ff;"></iframe></span><span style="color: #000000;"> </span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><div </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"progress" </span><span style="color: #ff0000;">style</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"display:none;"</span><span style="color: #0000ff;">></div></span> <span style="color: #0000ff;"></div></span> <span style="color: #0000ff;"><div></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><div </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"load"</span><span style="color: #0000ff;">></div></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><div </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"unload"</span><span style="color: #0000ff;">></div></span> <span style="color: #0000ff;"></div></span></span> |
There’s no codebehind for the control to speak of. The only place where you need to implement custom code is in the codebehind of the upload page to receive the posted file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<span style="font-family: Lucida Console;"><span style="color: #0000ff;">using </span><span style="color: #008080;">System</span><span style="color: #000000;">;</span> <span style="color: #0000ff;">using </span><span style="color: #008080;">System</span><span style="color: #000000;">.</span><span style="color: #008080;">Threading</span><span style="color: #000000;">;</span> <span style="color: #0000ff;">using </span><span style="color: #008080;">System</span><span style="color: #000000;">.</span><span style="color: #008080;">Web</span><span style="color: #000000;">.</span><span style="color: #008080;">UI</span><span style="color: #000000;">;</span></span> <span style="font-family: Lucida Console;"><span style="color: #0000ff;">namespace </span><span style="color: #000000;">AsyncUploadControlTest</span> <span style="color: #000000;">{</span> <span style="color: #008000;">/// <summary></span> <span style="color: #008000;">/// This is the actual page that handles the upload.</span> <span style="color: #008000;">/// </summary></span> <span style="color: #0000ff;">public </span><span style="color: #000000;">partial </span><span style="color: #0000ff;">class </span><span style="color: #000000;">Upload : </span><span style="color: #808000;">Page</span> <span style="color: #000000;">{</span> <span style="color: #008000;">/// <summary></span> <span style="color: #008000;">/// Handles the Load event of the Page control.</span> <span style="color: #008000;">/// </summary></span> <span style="color: #008000;">/// <param name="sender">The source of the event.</param></span> <span style="color: #008000;">/// <param name="e">The <see cref="System.EventArgs"/> </span> <span style="color: #008000;">/// instance containing the event data.</param></span> <span style="color: #0000ff;">protected void </span><span style="color: #000000;">Page_Load(</span><span style="color: #0000ff;">object </span><span style="color: #000000;">sender, </span><span style="color: #808000;">EventArgs </span><span style="color: #000000;">e)</span> <span style="color: #000000;">{</span></span> <span style="font-family: Lucida Console;"> <span style="color: #000000;">}</span></span> <span style="font-family: Lucida Console;"> <span style="color: #008000;">/// <summary></span> <span style="color: #008000;">/// Handles the upload click.</span> <span style="color: #008000;">/// </summary></span> <span style="color: #008000;">/// <param name="sender">The sender.</param></span> <span style="color: #008000;">/// <param name="e">The <see cref="System.EventArgs"/> </span> <span style="color: #008000;">/// instance containing the event data.</param></span> <span style="color: #0000ff;">protected void </span><span style="color: #000000;">HandleUploadClick(</span><span style="color: #0000ff;">object </span><span style="color: #000000;">sender, </span><span style="color: #808000;">EventArgs </span><span style="color: #000000;">e)</span> <span style="color: #000000;">{</span> <span style="color: #008000;">// Just fake a long running upload for dramatic effect</span> <span style="color: #808000;">Thread</span><span style="color: #000000;">.Sleep(</span><span style="color: #800080;">2500</span><span style="color: #000000;">);</span></span> <span style="font-family: Lucida Console;"> <span style="color: #008000;">// Add your logic here</span> <span style="color: #000000;">}</span> <span style="color: #000000;">}</span> <span style="color: #000000;">}</span></span> |
There you have it. Works in SharePoint 2007 just fine. Works in IE7 and Firefox 3.5 as well. Because the receiving upload page is just an ASPX page, you can simply output your errors or success messages to the page itself; no special hackery required.
Download the source code here: AsyncUploadControlTest.7z (17.54 KB)
the AsyncUploadControlTest.7z download link is not working
Marc,
Thanks for notifying me; I’ve remapped the MIME type mappings for .7z files on the server.
Much appreciated. Let me know if you have any questions on the sample.