Print This Post

ASP.Net File Upload/Download Module Version 2 (stable)

After 3 beta releases and many comments and emails from users it’s time to release the first stable version of the ASP.Net file upload module. This release contains a number of bug fixes and feature enhancements based on requests I have received. Thanks to everyone who has taken the time to implement the module and test it- I don’t think getting to this point would have been possible without you all.

As usual the Visual Studio 2008 solution is available in the downloads section of this site. I’ve also updated the demo site to allow browser testing.

I’ll go through the changes one by one.

Browser testing extended - Opera support

I have now tested the module on the following browsers:

  • Internet Explorer 6/7
  • Safari 3.1.2 on OSX
  • Firefox 3.0.1 on OSX
  • Firefox 3.0.1 on Windows
  • Opera 9.5.1 on Windows
  • Opera 9.5.1 on OSX

The main change here is that I’ve included Opera support. This turned out to be a great deal more difficult than I had expected. Opera 9+ is now supported fully with the exception of the progress bar. It seems that Opera blocks the UI during a file upload meaning that the modal box based AJAX progress bar never updates. For this reason, on Opera the non-script progress bar will automatically be used (see below). This means that Opera users at least get a progress bar- if not as nice.

I have tested all of the above browsers with and without javascript enabled and not found any issues.

File input styling now optional

The technique of styling file input controls that I have used may not be suitable for all purposes. For that reason I have made it optional. To turn file input styling on set the ApplyStyles property of the DJFileInput control to true. The default is now false.

Styling turned off Styling turned on

If javascript is not enabled on the browser then styling will be turned off automatically.

Better accessibility

This release of the module will work entirely without javascript enabled and degrade gracefully on all browsers I have tested on. This is done by importing a secondary style sheet using javascript which expands all of the file inputs in a control and hides the remove and add buttons.

The accessibility styling changes are accompanied by a non-script iframe based progress bar which kicks in automatically if script is not enabled. This results in a display something similar to the following which is from Opera 9 with script turned off.

As a reminder, the default AJAX based progress bar looks like this:

The basic accessibility features are enabled by default and you don’t have to do anything to make them work. If you want to use the non-script progress bar then you need to place one instance of the DJAccessibleProgressBar control somewhere on the page.

<cc1:DJAccessibleProgressBar ID="DJAccessibleProgrssBar1" runat="server" />

The non-script progress bar needs access to the UploadProgress.aspx page which is included in the project. You can set the URL of this page using the ProgressURL property of the DJAccessibleProgressBar if you need to move it for any reason.

Note that if script is enabled on the browser then the non-script progress bar will automatically remove itself and be replaced with the default modal box version. If you need to use the non-script version even if script is enabled then set the ShowProgressBar property of the DJUploadController to false.

Access to form fields from IFileProcessors

I’ve had a few questions around accessing form fields on the same page as file upload controls. All fields on the form are parsed by the upload module (file uploads or otherwise) but non-file upload fields are not available until the module has finished processing.

The requests I’ve had centered around a need to capture information from the user on the same page as the file upload control which would then affect the way the IFileProcessor deals with the file. Up to now the advice I’ve been giving has been to capture these details on a preceeding page and pass them into the upload page via request variables- this is still valid but doesn’t allow for the controls to all be on the same page.

I’ve now added logic into the form processor which stores the names and values of all form fields as it goes. These are then passed into the IFileProcessor which can use them as it sees fit. The only restriction is that the IFileProcessor will only have access to fields which preceeded the corresponding file input on the form.

To accomplish this the IFileProcessor interface has been changed very slightly. The StartNewFile method now has a new parameter PreviousFields which is a dictionary of all fields that have been parsed so far.

        /// <summary>
        /// Starts a new file.
        /// </summary>
        /// <param name="fileName">File name.</param>
        /// <param name="contentType">The content type of the file.</param>
        /// <param name="headerItems">A dictionary of items pulled from the header of the field.</param>
        /// <param name="previousFields">A dictionary of previous fields.</param>
        /// <returns>An optional object used to identify the item in the storage container.</returns>
        object StartNewFile(string fileName, string contentType, Dictionary<string, string> headerItems, Dictionary<string, string> previousFields);

To take a trivial example, imagine that there were to be a drop down list of prefixes which must be selected for an upload control. When the file was created, the prefix would be applied to the file name. This is an example of information which is not available until the form is processed.

To accomplish this a drop down list called lstFilePrefix is placed on the form before the related file upload control. Then in the IFileProcessor the prefix can be applied with the following code by checking the PreviousFields dictionary for the control ID of the list.

/// <summary>
/// Starts a new file.
/// </summary>
/// <param name="fileName">File name.</param>
/// <param name="contentType">The content type of the file.</param>
/// <param name="headerItems">A dictionary of items pulled from the header of the field.</param>
/// <param name="previousFields">A dictionary of previous fields.</param>
/// <returns>An optional object used to identify the item in the storage container.</returns>
public object StartNewFile(string fileName, string contentType, Dictionary<string, string> headerItems, Dictionary<string, string> previousFields)
{
    string prefix = String.Empty;

    _errorState = false;
    _headerItems = headerItems;

    // Get the prefix from the drop down list
    if (previousFields.ContainsKey("lstFilePrefix"))
    {
        prefix = previousFields["lstFilePrefix"];
    }

    try
    {
        _fileName = fileName;
        _fullFileName = _outputPath + prefix + Path.GetFileName(fileName);
        _fs = new FileStream(_fullFileName, FileMode.Create);
    }
    catch (Exception ex)
    {
        _errorState = true;
        throw ex;
    }

    return null;
}

I’ve provided an example processor (FieldTestProcessor.cs) in the library.

Immediate termination of requests that are too large

One problem discovered in previous betas was that if a file were uploaded which exceeded the maxRequestLength setting then the progress bar would freeze. Actually, I found this to be an IIS 6 specific issue. Although the module correctly terminated the request it would not end until the entire file had been read in- even though the file would never be processed. This meant the progress bar was left hanging looking like the application had frozen.

To get round this I have added some javascript to the progress bar which cancels the request when the upload progress service informs it that a request was recieved which was too large. This prevents the progress bar from looking like it is frozen.

Note that the progress bar would eventually disappear once IIS 6 correctly ended the request, so the script is merely a user convenience.

File extension validation moved

In previous versions of the upload control file extension validation was handled via script in the styled input controls. The user would be informed via a message box if they picked a file with an invalid extension. This has now been moved to a custom validator on the form since the styling can be turned off. This of course means that the user will not be informed of invalid extensions until they attempt to submit the form.

Note that file extension validation is only carried out on the client. If you need to implement server validation then check in the StartNewFile method of your IFileProcessor and throw an exception if an invalid file extension is found. This will result in the erronous file being placed in the ErrorFiles collection of the UploadStatus.

Documentation

Documentation for the module is available on this page. The basic documentation is a collection of the most important parts from the previous posts as the module was developed.

Setup kits

Setup kits for the module and example application have been placed in the downloads section.

Looking forward

I think I may have said in a previous post that I wasn’t going to put any more features into version 2 of the module. Obviously a few have crept into this release (ahem ;-), but I think version 2 is now feature complete.

I’ll fix any bugs or issues which anyone encounters (just post a comment or send me an email) and make service releases as appropriate.

There Are 17 Responses So Far. »

  1. Hi Darren (and Mark).

    I’ve read in your post something that could be related to Mark’s issue: “… imagine that there were to be a drop down list of prefixes which must be selected for an upload control ….. information which is not available until the form is processed …”

    Although we’ve used the technique I’m going to mention to ensure the “::DJ_UPLOAD_ID::” form element is sent before any file upload (we needed this because we customized the FileUpload module), I think you can do the same thing to ensure that the “input type=’file’” elements are the last items sent in the “submit request”.

    It’s very simple, just tricky, because you need some Javascript to put it in the “up_BeginUpload()” function that’s inside “upload_scripts/fileupload.js”.

    1) First, you need to locate the last element in your form (function not implemented):

    var lastElement = up_findLastElement();

    2) For each “input type=file” element move it after the last element.

    for (var i = 0; i < theForm.elements.length; i++ )
    {
    el = theForm.elements[i];
    if (el.type == ‘file’))
    {
    el.parentNode.removeChild(el);
    lastElement = lastElement.insertAdjacentElement(’afterEnd’, el);
    }
    }

    THIS CODE IS NOT TESTED, SO IT CAN CONTAIN ERRORS, but the technique of altering the order in which the form items are sent to the server by the browser does work, at least in Firefox 3 and IE 6/7 for Windows (don’t know about Opera nor OSX).

    3) Because I didn’t test the code, it may have some other issues, such as a bad rendered form while uploading, so when you move the file upload controls you might try to hide them (display:none or similar) and leave antoher control in it’s place. Also, if you use the Cancel feature, you would need to restore the file upload controls to it’s original position.

    I hope it will help you.

    Juan Daniel Cebrian
    http://www.solnatec.com

  2. Hi Juan,

    Thanks for this. It’s a useful technique for anyone who wants to ensure that the PreviousFields dictionary contains all of the form fields.

    Because this is a javascript method it may not be applicable to everyone and as Juan says it will need cross browser testing. You’d also need to be aware that in non-javascript environments the upload would still work but the PreviousFields collection may be different than if submitted with javascript.

    Still, very useful code for anyone who wants to incorporate it. I’ll do some testing and possibly factor the code in as an optional feature in a service release.

    Cheers again Juan.

  3. I’ve realized the “insertAdjacentElement()” only works for IE, so it would be wiser to use “lastElement.parentNode.appendChild(…)”

  4. Howdy, Darren

    Thank you for the update. I’ve incorporated it into my DNN project, but I’m still having some odd behavior from DNN. Now whenever I navigate to the page that has the control in it and either wait a few seconds or upload a file, DNN logs me out if I click on any of my menu items.

    I thought that maybe having the control in a .ascx file might be causing some problems so I moved it to a popup .aspx file that I open by injecting javascript in to the Response when a button on my form is clicked. Oddly enough, I’ still see the same behavior in that scenario. I’m pretty sure this is a DNN interation issue, but I’m not certain what is causing it.

    Still plugging away…
    Mark

  5. Darren,

    Just an update…
    I’m growing more and more convinced this is a DNN issue. If I log in as the DNN site’s superuser (a.k.a. “host”), I don’t have the aforementioned problem. If I use any non-superuser account, I get logged out after a file upload, or if I simply leave the upload form open for a few seconds. The logout occurs when I try to navigate to any other page on the DNN portal.

    I’ll continue to research…

    Thanks again,
    Mark

  6. Darren,

    It’s definitely a DNN (or my [mis]usage of DNN) issue, not your upload control that is causing the spontaneous logouts.

    If I install my module in the main DNN portal (not a child portal), the login problem goes away. There may be something else I need to do to my module to make it function in a child portal, but at this time I don’t have any time to chase it down (this portal was supposed to be completed last week). :-(

    I’ll check back in later when I get this thing launched.

    Thanks so much for your valuable time.

    Mark

  7. Hi Mark,

    Send me the code for your module and I will set time aside tomorrow to help you (don’t worry your code will stay between us).

    In the meantime I’ve set up DNN and am writing my own file upload module. Tell me though- are you using SQL or file system uploading?

    I agree, the problems you are having are unlikely to be related to the file upload module itself, but I want to help you sort this out anyway.

    Cheers,
    Darren

  8. Darren,

    I’ll try to get my files to you before tomorrow. BTW I’m using DNN Framework v4.8.4.

    I’m using neither the filesystem upload nor the SQL upoading. I’ve created a new web service processor that implements the IFileProcessor interface. The upload seems quite reliable now. I still need to firm up the error handling.

    You may want to experiment with your own DNN instance to see if you get the same behavior, but one thing I’ve noticed is that your file upload module and the DNN file import do not appear to get along…specifically with the skin upload which imports (uploads) .ZIP files for each skin package. I had to comment out the upload module entries in web.config to allow me to upload skins through the DNN skins facility. With those lines in web.config, the DNN skin import throws the following exception:

    DotNetNuke.Services.Exceptions.ModuleLoadException: EOF in header —> ICSharpCode.SharpZipLib.ZipException: EOF in header at ICSharpCode.SharpZipLib.Zip.ZipInputStream.ReadLeByte() at ICSharpCode.SharpZipLib.Zip.ZipInputStream.GetNextEntry() at DotNetNuke.UI.Skins.SkinController.UploadSkin(String RootPath, String SkinRoot, String SkinName, Stream objInputStream) at DotNetNuke.Modules.Admin.FileSystem.WebUpload.cmdAdd_Click(Object sender, EventArgs e) in C:\Inetpub\wwwroot\DNN484\Admin\Files\WebUpload.ascx.vb:line 343

    I’m displaying my httpHandler ignorance here, but I suspect the file upload module is intercepting the DNN skin upload before DNN gets it and it is passing a null stream to it. Would that be correct? If so, is there a way to “tag” uploads to use a particular upload handler other than by file extension?

    Thanks again,

    Mark

  9. Hi Mark,

    No, you are quite right. The module will intercept all requests which are file uploads- this is likely what is happening with DNN. In this case the asp.net FileUpload controls will have null file contents. The files would have instead have been streamed to the IFileProcessor implementation(s), details of which would be available via the DJUploadController control. I will send you an email (and post in due course) tomorrow about how to bypass this.

    I await your code to help you with your other problems.

    Cheers
    Darren

  10. I’m interested in setting up this module with DNN also, are there plans for possibly creating a DNN module wrapper around your code? Or maybe some instructions on how to get your upload control to work in a DNN environment?

    Thanks much, your control is very impressive!!

    -Rob-

  11. Hi Rob,

    As it happens I’m working on a DNN module just now. I’ll release it soon.

    Cheers,
    Darren

  12. Kindly, help to create upload control as field type in WSS 3.0

    Thanks, impressive work

    Balucci

  13. Hi, I was wondering if can I use this file upload in framework 2.0.

    Regards,

    Dick Valdivieso

  14. Hi Dick,

    Yes. The component is designed for version 2.0 of the framework. Although the solution is in VS 2008 it is targetted for the 2.0 framework and upwards.

    Cheers,
    Darren

  15. Hi Darren,

    Just wondering whether you are considering making this available in ASP.NET MVC ?

    Cheers,

    Andy

  16. Hi Darren,

    Are chance of releasing this to work in ASP.NET MVC ? Searched everywhere for a decent upload tool in ASP.NET MVC and none are available.

    Im pretty sure the entire MVC community would love you if its possible?

    Cheers,

    Andy

  17. this is great!

Post a Response