Upload Multiple Files With Progress Using Uploadify

I previously posted on how to use Uploadify to upload files. The previous post covered really well how to upload single files to an ASP.NET MVC controller using Uploadify. This post will expand on that a bit. First we will revisit how to upload multiple files using a single HTML input element. Then the case of uploading multiple files using multiple HTML input elements. Finally, how to upload multiple files while posting form data.

image

Here are links to the demo pages for the Single and Multiple file uploads.

Multiple  Single

Getting Started

If you are not familiar with Uploadify here is a description from their web site:

Uploadify is a jQuery plugin that integrates a fully-customizable multiple file upload utility on your website. It uses a mixture of JavaScript, ActionScript, and any server-side language to dynamically create an instance over any DOM element on a page.

If will first need to download the Uploadify code. I simply unzipped it into my ‘scripts’ folder as shown below:

image

Any page that uses Uploadify will need to include the following lines in the ‘head’ section:

<link href="<%= Url.Content("~/Content/Scripts/uploadify/uploadify.css") %>" rel="stylesheet" type="text/css" />

<script type="text/javascript" src="http://cdn.bobcravens.com/wp-content/uploads/2011/03/jquery-1.4.1.js"></script>
<script type="text/javascript" src="<%= Url.Content("~/Content/Scripts/uploadify/swfobject.js") %>"></script>
<script type="text/javascript" src="<%= Url.Content("~/Content/Scripts/uploadify/jquery.uploadify.v2.1.4.min.js") %>"></script>

The first line links in the CSS used by Uploadify. The last three lines add the required JavaScript.

Multiple Files With A Single HTML Input

Uploadify directly supports uploading multiple files by configuring the Uploadify object. Add the following HTML input elements to your page:

<p><input type="file" id="multipleFiles" /></p>
<p><button id="btn1">Upload Files</button></p>

The ‘input’ element will be used as the Uploadify object. The ‘button’ element serves as a trigger to start the uploads. Here is the JavaScript that configures this scenario:

// Multiple files - single input
$("#multipleFiles").uploadify({
    'uploader': '<%= Url.Content("~/Content/Scripts/uploadify/uploadify.swf") %>',
    'script': '<%= Url.Action("Upload") %>',
    'fileDataName': 'file',
    'buttonText': 'File Input...',
    'multi': true,
    'sizeLimit': 1048576,
    'simUploadLimit': 2,
    'cancelImg': '<%= Url.Content("~/Content/Scripts/uploadify/cancel.png") %>',
    'auto': false,
    'onError': function (a, b, c, d) {
        if (d.status == 404)
            alert("Could not find upload script. Use a path relative to: " + "<?= getcwd() ?>");
        else if (d.type === "HTTP")
            alert("error " + d.type + ": " + d.status);
        else if (d.type === "File Size")
            alert(c.name + " " + d.type + " Limit: " + Math.round(d.info / (1024 * 1024)) + "MB");
        else
            alert("error " + d.type + ": " + d.text);
    },
    'onComplete': function (event, queueId, fileObj, response, data) {
        alert(response);
    }
});
$("#btn1").click(function () {
    $('#multipleFiles').uploadifyUpload();
});

The Uploadify object supports a number of properties, events, and methods. Visit their page for in-depth information. The configuration that allows multiple files to be selected is the ‘multi’ option. Setting this to ‘true’ enables multiple file selection as shown below.

image

That results in the following:

image

The ‘Upload Files’ button is wired up to call the ‘uploadifyUpload’ method on the Uploadify object. This begins the upload process. The file uploads happen asynchronously and not in a guaranteed to arrive in a specified order. All selected files are handled by a single ASP.NET MVC action method:

public string Upload(HttpPostedFileBase file)
{
    // Process the file here.
    //
    return "Upload processed. filename=" + file.FileName;
}

This works great if the same processing can be applied to all files. If you require the files to be dispatched out to different processors based upon their type then this probably will not work. In this case, you will need to use multiple HTML input elements.

Multiple Files With Multiple HTML Input Elements

Now we have multiple HTML ‘input’ elements on the page.

<p><input type="file" id="file1" name="file1" class="uploadify1" /></p>
<p><input type="file" id="file2" name="file2" class="uploadify1" /></p>
<p><input type="file" id="file3" name="file3" class="uploadify1" /></p>
<p><button id="btn2">Upload Files</button></p>

The ‘button’ element is again used to start the upload processing. Each ‘input’ element has a unique ‘name’ attribute. If this were posted back in a normal HTML ‘form’ then this ‘name’ attribute would follow the file to the server where the file processing could be based upon the value of the ‘name’ attribute. Here is the JavaScript that configures this scenario:

// Multiple files - multiple inputs
var packageId = 'multiple1' + new Date().getTime() + Math.round(Math.random() * 1000);
$(".uploadify1").each(function () {
    $(this).uploadify({
        'uploader': '<%= Url.Content("~/Content/Scripts/uploadify/uploadify.swf") %>',
        'script': '<%= Url.Action("Upload2") %>',
        'fileDataName': 'file',
        'buttonText': 'File Input...',
        'multi': false,
        'scriptData': { 'packageId': packageId, 'type': $(this).attr('name') },
        'sizeLimit': 1048576,
        'simUploadLimit': 1,
        'cancelImg': '<%= Url.Content("~/Content/Scripts/uploadify/cancel.png") %>',
        'auto': false,
        'onError': function (a, b, c, d) {
            if (d.status == 404)
                alert("Could not find upload script. Use a path relative to: " + "<?= getcwd() ?>");
            else if (d.type === "HTTP")
                alert("error " + d.type + ": " + d.status);
            else if (d.type === "File Size")
                alert(c.name + " " + d.type + " Limit: " + Math.round(d.info / (1024 * 1024)) + "MB");
            else
                alert("error " + d.type + ": " + d.text);
        },
        'onComplete': function (event, queueId, fileObj, response, data) {
            alert(response);
        }
    });
});
$("#btn2").click(function () {
    $(".uploadify1").uploadifyUpload();
});

In this case, each input element is configured to only allow a single file to be selected (‘multi’:false). The key configuration in this case is the ‘scriptData’ option. This options allows JSON data to be sent back to the server along with the file. In this case we send a ‘packageId’ (created on the first line) and a ‘type’. The ‘packageId’ is used to associate the files into groups on the server. The ‘type’ variable is the ‘input’ element’s ‘name’ attribute and allows the server side code to differentiate the files. The ASP.NET MVC controller now looks like the following:

public string Upload2(HttpPostedFileBase file, string packageId, string type)
{
    // Add the file to the package.
    //
    Package package = PackageManager.GetPackage(packageId);
    package.AddFile(file, type);
    return "Upload received.nFilename: " + file.FileName + "nPackage Id: " + packageId + "nType: " + type;
}

This method not only receives the file, but also the JSON data sent by Uploadify. The JSON data is then used to add the incoming file to a particular package. The specific implementation of the packaging system is not important (and not shown), but the key is that the server has the information necessary to implement the packaging feature. The above code could just as easily dispatched the file to various processors based upon the ‘type’ parameter.

The above works great for processing files based upon their types. The next challenge is processing files and form data where the form data provides important context for processing.

Multiple Files With Multiple HTML Input Plus Form Data

Assume we have an HTML ‘form’ that accepts multiple files and the first/last name of the user. The files must be processed based upon who uploaded the files. Therefore the first/last name data is important and required before processing can occur. Here is the HTML defining the form:

<form id='form1' method='post' action='<%= Url.Action("ProcessForm") %>'>
    <p><em>Provide your name:</em></p>
    <p>First Name <input type="text" name="firstName" id="fname" /></p>
    <p>Last Name <input type="text" name="lastName" id="lname" /></p>
    <p><em>Using the buttons below, navigate and select up to three files (1MB size limit) then click the 'Submit' button:</em></p>
    <p><input type="file" id="file4" name="file1" class="uploadify2" /></p>
    <p><input type="file" id="file5" name="file2" class="uploadify2" /></p>
    <p><input type="file" id="file6" name="file3" class="uploadify2" /></p>
    <p><input type="submit" id="btn3" /></p>
</form>

Traditionally (without Uploadify involved) this form would POST all the data to a single ASP.NET MVC action method and no upload progress would be provided. Hooking up Uploadify provides the desired progress indicators, but now the files are uploaded ‘out of band’. Some special handling is necessary to keep the files and the form data associated. Here is the JavaScript that configures the form:

// Multiple files - multiple inputs in a form
var packageId2 = 'multiple2' + new Date().getTime() + Math.round(Math.random() * 1000);
var elem = $("<input type='hidden' name='packageId' value='" + packageId2 + "' />");
$("#form1").prepend(elem);
$(".uploadify2").each(function () {
    $(this).uploadify({
        'uploader': '<%= Url.Content("~/Content/Scripts/uploadify/uploadify.swf") %>',
        'script': '<%= Url.Action("Upload2") %>',
        'fileDataName': 'file',
        'buttonText': 'File Input...',
        'multi': false,
        'scriptData': { 'packageId': packageId2, 'type': $(this).attr('name') },
        'sizeLimit': 1048576,
        'simUploadLimit': 1,
        'cancelImg': '<%= Url.Content("~/Content/Scripts/uploadify/cancel.png") %>',
        'auto': false,
        'onError': function (a, b, c, d) {
            if (d.status == 404)
                alert("Could not find upload script. Use a path relative to: " + "<?= getcwd() ?>");
            else if (d.type === "HTTP")
                alert("error " + d.type + ": " + d.status);
            else if (d.type === "File Size")
                alert(c.name + " " + d.type + " Limit: " + Math.round(d.info / (1024 * 1024)) + "MB");
            else
                alert("error " + d.type + ": " + d.text);
        },
        'onComplete': function (event, queueId, fileObj, response, data) {
            incrementUploadCount();
        }
    });
});
$("#btn3").click(function (evt) {
    evt.preventDefault();

    submit();
});

The Uploadify elements are configured nearly the same as before using the ‘scriptData’ option to send back the ‘packageId’ and the ‘type’ information as JSON. However, in this case the ‘packageId’ is prepended to the form as an HTML hidden ‘input’ element (first three lines). In addition the Uploadify ‘onComplete’ callback now calls the ‘incrementUploadCount’ function (covered below) and the HTML submit button is wired up to call the ‘submit’ function.

var numFilesSelected = 0;
var numFilesUploaded = 0;
function submit() {
    // validate the form
    if ($('#fname').val() === '') {
        alert('Provide a first name.');
        return;
    }
    if ($('#lname').val() === '') {
        alert('Provide a last name.');
        return;
    }

    // determine the number of files that need to be
    //  uploaded and reset the uploaded count
    numFilesSelected = $(".uploadifyQueueItem").length;
    numFilesUploaded = 0;
    // upload the files
    $('.uploadify2').uploadifyUpload();
}

function incrementUploadCount() {
    numFilesUploaded++;
    if (numFilesUploaded === numFilesSelected) {
        alert('About to submit the form data.');
        postFormData();
    }
}

function postFormData() {
    $("#form1").submit();
}

The above code defines the client side pipeline that eventually upload the files and submit the form. The submit button calls the ‘submit’ function. In the ‘submit’ function, the form is first validated. Then the number of files being uploaded is determined and a counter is set to zero. Finally, the Uploadify elements are triggered.

Each Uploadify element calls the ‘incrementUploadCount’ upon successfully uploading the file to the server. Once all the files have been uploaded, the form is submitted. Here is the ASP.NET MVC action methods that are needed:

public string Upload2(HttpPostedFileBase file, string packageId, string type)
{
    // Add the file to the package.
    //
    Package package = PackageManager.GetPackage(packageId);
    package.AddFile(file, type);
    return "Upload received.nFilename: " + file.FileName + "nPackage Id: " + packageId + "nType: " + type;
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ProcessForm(string firstName, string lastName, string packageId)
{
    Package package = PackageManager.GetPackage(packageId);
    package.FirstName = firstName;
    package.LastName = lastName;
    PackageManager.RemovePackage(package.Id);
    return View("UploadReceipt", package);
}

The ‘Upload2’ action method is the same as before. It collects the uploaded files into a package. The ‘ProcessForm’ action method receives the rest of the form data along with the ‘packageId’. The ‘packageId’ is used to request the ‘Package’ that contains the associated files. The form data is then added to the ‘Package’. Once complete the ‘Package’ can be sent off for processing as a whole. In this case, it is simply sent to a ‘View’ to render a receipt page.

Summary

Uploading multiple files with progress using Uploadify is an option. Depending on your requirements there can be additional complexity. In this post, we have covered a way of accomplishing multiple file uploads under various requirements. As always, if you have feedback post a comment.

[ Update ]

Here is the package manager that I put together. I did not put a lot of thought or testing into this implementation.

public class UploadedFile
{
    public HttpPostedFileBase Stream { get; set; }
    public string Type { get; set; }
}

public class Package
{
    public string Id { get; private set; }
    public List<UploadedFile> Files { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Package(string id)
    {
        Id = id;
        Files = new List<UploadedFile>();
    }

    public void AddFile(HttpPostedFileBase file, string type)
    {
        UploadedFile fileInfo = new UploadedFile { Stream = file, Type = type };
        Files.Add(fileInfo);
    }
}

public static class PackageManager
{
    private static readonly List<Package> _packages = new List<Package>();

    public static Package GetPackage(string packageId)
    {
        Package package = _packages.Where(p => p.Id == packageId).SingleOrDefault();
        if (package == null)
        {
            package = new Package(packageId);
            _packages.Add(package);
        }
        return package;
    }

    public static void RemovePackage(string packageId)
    {
        Package package = _packages.Where(p => p.Id == packageId).SingleOrDefault();
        if (package != null)
        {
            _packages.Remove(package);
        }
    }
}

Comments
  1. Michael O'Donnell
    • rcravens
  2. boban
    • rcravens
  3. boban
  4. Derek Golding
    • rcravens
  5. Compusam
  6. Pankaj Pandey
  7. Inuant
  8. Murtaza
    • rcravens
  9. Amy
    • rcravens
  10. dl5
  11. CADAVID
  12. CADAVID
  13. Aleksey

Leave a Reply

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

*