Enter The AsyncFileUpload Control

The new build of the Ajax Control Toolkit released at the end of September included its first crack at an AJAX-enabled file upload control. So let’s have a look and see what we do and don’t get in this version of the AsyncFileUpload (AFU) control.

The Barest Minimum

Up first then, let’s have a look at the control in its barest form. Add the new AjaxControlToolkit.dll to a project, @Register it on the page and then add the control.

<%@ Page Language="C#" AutoEventWireup="true" 
   CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="AjaxControlToolkit" 
   Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>FileUploader - Basics</title>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ScriptManager runat="server" ID="sm1" />
  <cc1:AsyncFileUpload ID="AsyncFileUpload1" runat="server" />
  </form>
</body>
</html>

If you look at the resultant HTML, you’ll see that aside from the standard Microsoft AJAX libraries being pulled in via ScriptResource.axd, four other script files are downloaded in support of the AFU. Meanwhile the default values for some of the control’s properties are added to the call to the standard AJAX library Sys.Application.add_init call. The control itself renders as a file input element (with two id attributes!!) wrapped inside a div with some text added dynamically saying ‘No file chosen’. The wrapper div is itself wrapped in a span tag with a hidden field that will be used to keep score of uploads and the like.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head><title>FileUploader - Basics</title></head> 
<body> 
  <form ...> 

  <script src="/FileUploader/WebResource.axd?d=.."></script>
  <script src="/FileUploader/ScriptResource.axd?d=.."></script>
  <script src="/FileUploader/ScriptResource.axd?d=.."></script>
  <script src="/FileUploader/ScriptResource.axd?d=.."></script>
  <script src="/FileUploader/ScriptResource.axd?d=.."></script>
  <script src="/FileUploader/ScriptResource.axd?d=.."></script>

  <span id="AsyncFileUpload1"> 
    <input type="hidden" name="AsyncFileUpload1$ctl00" id="AsyncFileUpload1_ctl00" />
    <div id="AsyncFileUpload1_ctl01" name="AsyncFileUpload1_ctl01">
      <input name="AsyncFileUpload1$ctl02" type="file" id="AsyncFileUpload1_ctl02" 
        id="AsyncFileUpload1_ctl02" onkeydown="return false;" onkeypress="return false;" 
        onmousedown="return false;" style="width:355px;" />
    </div>
  </span> 
  
  <script type="text/javascript"> 
  //<![CDATA[
  Sys.Application.initialize();
  Sys.Application.add_init(function() {
    $create(AjaxControlToolkit.AsyncFileUpload, 
      {"clientStateField":$get("AsyncFileUpload1_ClientState"),
       "completeBackColor":"Lime","errorBackColor":"Red","formName":"form1",
       "hiddenField":$get("AsyncFileUpload1_ctl00"),
       "inputFile":$get("AsyncFileUpload1_ctl02"),
       "postBackUrl":"Default.aspx","uploadingBackColor":"White"}, 
      null, null, $get("AsyncFileUpload1"));
  });
//]]>
  </script> 
  </form> 
</body> 
</html> 

With a spot of profiling, we can see that the other four scripts pulled into the page are:

  • AjaxControlToolkit.Common.Common.js
  • AjaxControlToolkit.ExtenderBase.BaseScripts.js
  • AjaxControlToolkit.AsyncFileUpload.AsyncFileUpload.js
  • Some additional code for handling asynchronous callbacks pulled in by WebResource.axd

Styling and Appearance

Besides the standard style properties (CssClass, BorderColor, etc) inherited from System.Web.UI.WebControls.WebControl, the AsyncFileUpload control defines three more. Each defines the background color of the control at a certain point in time.

  • UploadingBackColor : Used as the file is being uploaded.
  • CompleteBackColor : Used if the file is uploaded successfully
  • ErrorBackColor : Used if an error occurs as the file is uploaded.

If you do use the WebControl style CssClass property, bear in mind that it will add your nominated CSS class to the outer span of the HTML rendered for the control. You’ll need to define separate styles for the child elements if you do this. The AFU control meanwhile applies the other WebControl style properties to different elements of the HTML it renders (although mostly the outer span). For example, adding the following properties directly to the AFU

<cc1:AsyncFileUpload ID="AsyncFileUpload1" runat="server" 
  BackColor="Blue" BorderStyle="Dotted" 
  BorderWidth="10px" BorderColor="Brown" />

is equivalent to setting its CssClass property to ‘afu’ and defining the following styles.

<style type="text/css">
  .afu {
    display:inline-block;
    border: Dotted 10px Brown;
  }
    
  .afu input[type=file]  {
    background-color:Blue;
  }
</style>

In particular note the display:inline-block style which isn’t rendered correctly in IE 6/7/8 and produces quite different results in IE from Chrome or Firefox. For example

Styling the AFU across various browsers

The AFU also includes a property called UploaderStyle with two possible values: Traditional (the default) and Modern. Setting it to the latter adds an additional div and textbox to the HTML rendered by the control as well as some more advanced inline styles to these elements. Again the results are not very consistent across all browsers.

<span id="AsyncFileUpload1">
  <input type="hidden" ... />
  <div id="AsyncFileUpload1_ctl01" name="AsyncFileUpload1_ctl01" 
    style="background: url(/FileUploader/WebResource.axd?d=...) no-repeat 100% 1px;
    height: 24px; margin: 0px; width: 355px;">
    <input name="AsyncFileUpload1$ctl02" type="file" id="AsyncFileUpload1_ctl02" ...
      style="opacity: 0.0; -moz-opacity: 0.0; filter: alpha(opacity=00); font-size: 14px;" />
    <div name="AsyncFileUpload1_ctl01" style="margin-top: -23px;" type="text">
      <input name="AsyncFileUpload1$ctl04" type="text" 
        readonly="readonly" id="AsyncFileUpload1_ctl04"
        style="height: 17px; font-size: 12px; font-family: Tahoma; width: 248px;" />
    </div>
  </div>
</span>

Events and Other Properties

As the name of the control implies, the AsyncFileUpload control starts uploading a file to the server as soon as a user has selected one. Thus there is a lack of control here that other FileUpload controls offer by way of a separate Upload button which a user must click to start the upload. It is also not possible to queue multiple files for upload as other uploader controls can do. Hopefully, this will be added in future iterations.

When a file is chosen for upload, the control itself may fire up to five events while the upload occurs and ends. In order of occurrence, they are:

  • OnClientUploadStarted is fired on the client-side as upload begins
  • OnUploadedComplete is fired on the server-side as upload to a temp file on the server completes. Use this event to then save the file in the actual location you want it.
  • OnClientUploadComplete is fired on the client-side after the upload has completed.

There are also events fired if an error occurs somewhere in the process of upload. For example, the file could be of zero length, the connection could fail, or the site might not have permission to save to the specified location.

  • OnClientUploadError is fired on the client-side
  • OnUploadedFileError is fired on the server-side.

All client-side event handlers take two parameters. For example

function uploadError(sender, args) {
  alert(args.get_fileName() + ' could not be uploaded. ' + args.get_errorMessage());
}

The first parameter identifies the object that fired the event, while the second provides information on the file being uploaded. In fact, it contains five useful properties accessed using the get_abc() syntax demonstrated above.

  • get_fileName() and get_path() both return the name of the file being uploaded
  • get_length() returns the size of the file in bytes once uploaded. Returns null prior to upload
  • get_contentType() returns the mime type of the file once it is uploaded. Returns null prior to upload
  • get_errorMessage() returns an error message should one occur. Returns null otherwise

Similarly, the server-side event handlers take the same two parameters. For example

protected void afu_UploadedComplete(
   object sender, AsyncFileUploadEventArgs args)
{
  string savePath = MapPath(Path.Combine("~/", args.filename));
  AsyncFileUpload1.SaveAs(savePath);
}

The AsyncFileUploadEventArgs object exposes four properties.

  • filename returns the name of the file
  • filesize returns the size of the file in bytes
  • state returns one of the three AsyncFileUploadState enum values – Failed, Success and Unknown – indicating how the upload went. Unknown implies that something was wrong with the file before upload occurred. For example the file name was wrong, it had zero length etc.
  • statusMessage returns either an ‘Upload Successful’ message or clarifies the Unknown state with one of the constants in AsyncFileUpload.Constants.Errors (EmptyContentLength, FileNull, InputStreamNull, NoFileName or NoFiles)

Besides the AsyncFileUploadEventArgs object, you can of course access any of the properties of the AsyncFileUpload control itself. In addition to those already mentioned, you also have read-only access to the following.

  • ContentType returns the mime type of the file that was uploaded. Returns String.Empty prior to upload.
  • FileBytes returns the uploaded file as a byte array and null prior to upload.
  • FileContent returns a Stream object pointed at the uploaded file. Referencing this property prior to upload throws a NullReferenceException at the moment.
  • FileName returns the name of the uploaded file. Returns String.Empty prior to upload.
  • HasFile returns true if the file is uploaded to the server and exists in the nominated temporary persistance store. Returns false prior to upload.
  • IsUploading returns true if the file is still uploading (determined by whether or not the AsyncFileUploadID querystring parameter is still on the URL – this is updated by the AFU during asynchronous postback) and false otherwise.
  • PostedFile returns an HttpPostedFile object describing the uploaded file. Returns null prior to upload.

Finally, the AFU has three other read-write properties at your disposal.

  • FailedValidation sets a boolean value (false by default) indicating whether or not the nominated file failed validation. How you define ‘failed validation’ is up to you. Note that this property is ‘broken’. It only has an effect on the AFU (displays the ErrorBackColor rather than the CompleteBackColor if its UploaderStyle property is set to ‘Modern’ and if a full postback takes place. The latter point seems to counter the whole point of control working asynchronously).
  • PersistedStoreType sets the type of store the file is uploaded initially to before you use the SaveAs() method to push the file wherever you want it. Currently there’s only one choice: Session.
  • ThrobberID sets the ID of the control that will appear whilst the file is being uploaded. Typically a ‘Please Wait’ animation of some kind.

And of course, the AsyncFileUpload control has its SaveAs() method.

Saving A File And (Not) Connecting Client To Server To Client

As implied earlier, you use the SaveAs() method to move the uploaded file from the temporary store to its final destination. SaveAs() requires a complete physical path and filename to work, but does not check whether the named location to save to already exists. It will overwrite the existing file without checks. Adding a check in here then and failing validation will be a good idea once the FailedValidation property is fixed (see earlier note).

protected void afu_UploadedComplete(object sender, AsyncFileUploadEventArgs args)
{
  string savePath = MapPath(Path.Combine("~/", Path.GetFileName(args.filename)));
  if (File.Exists(savePath))
  {
    afu.FailedValidation = true;
  }
  else
  {
    afu.SaveAs(savePath);
  }
}

One workaround would be to throw an Exception if the target file already exists. The AFU does then display the ErrorBackColor correctly as an Error has occurred, but the full Exception message is also displayed in a popup window which isn’t great.

One more issue that hasn’t yet been addressed is a way for the server-side event code handling OnUploadedComplete to talk to the client-side code handling OnClientUploadComplete. This becomes particularly important if the AFU is part of a larger form and details an upload generates must be incorporated into the form. Let’s take a scenario where we’re using a form to write a message to a forum with the AFU allowing the upload of attachments. As the attachment is uploaded, a record for that attachment is saved to a database, and an ID for the new row is passed back and must be stored for inclusion in the form data for the new message.

There should be two possible ways to do this.

  • Save the attachment id in, say, a HiddenField control within the UploadedComplete server-side handler for use when the form is submitted.
  • Pass the attachment id through to the client-side handler in its args parameter and deal with it using some client-side script.

Unfortunately neither are possible. While the former is possible, even if the hidden field is wrapped inside an UpdatePanel triggered when a file is uploaded, the attachmentId is still not available until the second time the form is posted back the form’s submit button is pressed thanks to the page’s event lifecycle. Which would mean that manipulating the content of the hidden field directly with client-side script should (and indeed does) work except that there is no mechanism to pass that attachment id through to the client-side handler from the server side.

Conclusion

This first go at providing an Asynchronous File Upload control in the AJAX control toolkit is functional enough but still buggy enough and lacking in features that I would stay away from it until v2 appears or unless you haven’t an alternative.

Personally, I would love to see

  • Steps towards a uniform appearance across all modern browsers.
  • A way to trigger the upload manually rather than have it occur as soon as a file is picked
  • An option to offer multiple file uploads within the same control.
  • A default ‘throbber’ graphic
  • The FailedValidation property fixed and working in both Modern and Traditional styles
  • The AFU working within UpdatePanels
  • Some way to pass additional information from the server-side UploadedComplete handler to the client-side.

There are other issues I’ve not mentioned here, all logged at the toolkit’s codeplex site. Visit there and give your feedback and bug reports.

Tags:

Comments

6 responses to “Enter The AsyncFileUpload Control”

  1. Nacho says:

    In localhost I can use this control without problems.
    But, when I upload the web to the server, The fileupload show me the error: ‘Unknown server error’…
     
    What can i do? 

  2. kapil says:

    Hi Dan
    "ID for the new row is passed back and must be stored for inclusion in the form data for the new message."
    I am struck ina similar situation and need to pass certain parameters from the server side to my client sidee script after the file is uploaded..Have you though of any solution yet…
     
    Thanks and Regards
    Kapil
     
     

  3. Anonymous says:

    Kapil, pls use session variables to transfer variables to the Ajaxfileupload page.

  4. Shrikrishna Padalkar says:

                          I am trying to implement "dropbox" web site in ASP.NET C#. For this purpose I wanted to synchronize files on my website with my local computer.Secondly I have to manage the events similar to "www.dropbox.com" websit.
               Please provide me with the source code and detailed explanation for the above 2 functionalities. 

  5. Tom Leong says:

    I add a file size checking for an AFU and it shows error message if the uploaded file is bigger than the max size. But the back color of AFU is still green. I can not make it to red color.
    Please give me some advisement how to make it.
    Thank a lot.
     

  6. Hello,

    I am using Async File upload ,i want to set the uploading percentage of how much file size is uploaded in Percentage , i know that there is no provision in async file upload in built.
    Is there any alternative to achieve this without moving from Async File Upload Control(Kind of Custom Logic)

Leave a Reply

Your email address will not be published. Required fields are marked *