views:

712

answers:

1

Hello SO:

I am working on an upload script for an ASP.NET MVC application. I have an implementation that works with one file, but I would like to expand this to handle multiple uploads. Here is the original implementation:

Upload View (yes I will be moving this jQuery code to its own file):

<script type="text/javascript">
 var fieldCount = 0;
    function addField() {
        var name = 'file' + fieldCount;
        var row = 'row' + fieldCount;
        var str = '<p id="' + row + '"><label for="' + name + '">File to upload: <input type="file" name="' + name + '" id="' + name + '" />(100MB max size) <a onclick="removeRow(\'#' + row +     '\'); return false;">[-]</a></label></p>';
        fieldCount++;
        $("#fields").append(str);
    };
    function removeRow(id) {
        if ($("#fields").children().size() > 1) {
            $(id).remove();
        }
    };
    $(function() {
        $("#ajaxUploadForm").ajaxForm({
            iframe: true,
            dataType: "json",
            beforeSubmit: function() {
                $("#resultBox").show();
                $("#status").html('<h1><img src="/Content/busy.gif" /> Uploading file...</h1>');
            },
            success: function(result) {
                $("#ajaxUploadForm").unblock();
                $("#ajaxUploadForm").resetForm();
                var tcolor;
                var msg;
                if (!result.message) {
                    tcolor = "red";
                    msg = result.error;
                }
                else {
                    tcolor = "green";
                    msg = result.message;
                }
                $("#resultBox").show();
                $("#status").html('<span style="color:' + tcolor + ';">' + msg + '</span>').animate({ opacity: 1.0 }, 3000).fadeOut('slow', function() {
                    $("#resultBox").hide();
                });
            },
            error: function(xhr, textStatus, errorThrown) {
                $("#ajaxUploadForm").unblock();
                $("#ajaxUploadForm").resetForm();
                $("#resultBox").add("p").attr("id", "status").css("margin-top", "15px").css("padding", "20px");
                $("#status").html('<span style="color:red;">Error uploading file</span>').animate({ opacity: 1.0 }, 3000).fadeOut('slow', function() {
                    $("#resultBox").hide();
                });
            }
        });
    });
</script>
<form id="ajaxUploadForm" action="<%= Url.Action("AjaxUpload", "Upload")%>" method="post" enctype="multipart/form-data">
    <fieldset id="uploadFields">
        <legend>Upload a file</legend>
        <div id="fields"></div>
        <input id="ajaxUploadButton" type="submit" value="Submit" />            
    </fieldset>
    <a onclick="addField(); return false;" id="add">Add</a>
    <div id="resultBox">
        <p id="status" style="margin:10px;"></p>
    </div>
</form>

Action result:

public FileUploadJsonResult AjaxUpload(HttpPostedFileBase file)
{
    Upload fileToUpload;
    try
    {
        fileToUpload = new Upload
        {
            filename = file.FileName,
            filesize = file.ContentLength,
            date = DateTime.Now,
            id = Guid.NewGuid()
        };

    var savedFileName = Server.MapPath(Path.Combine(@"~/uploads", Path.GetFileName(fileToUpload.filename)));
    if (System.IO.File.Exists(savedFileName))
    {
        throw new Exception(string.Format("The file '{0}' already exists on the server.", file.FileName));
    }
    file.SaveAs(savedFileName);
    }
    catch (Exception ex)
    {
        return new FileUploadJsonResult { Data = new { error = string.Format("Upload failure: {0}", ex.Message), message = string.Empty } };
    }
    return new FileUploadJsonResult { Data = new { message = string.Format("{0} uploaded successfully. (id:{1})", Path.GetFileName(file.FileName), fileToUpload.id) } };
}

I used concepts from this blog post to get this whole thing rolling.


Now with my javascript to add/remove fields, I changed the action result slightly:

public List<FileUploadJsonResult> AjaxUpload(HttpPostedFileBase fileBase)
{
    var results = new List<FileUploadJsonResult>();
    try
    {
        if (Request.Files.Count > 0)
        {
            Upload uploadFile;
            for (var i = 0; i <= (Request.Files.Count - 1); i++)
            {
                HttpPostedFileBase file = Request.Files[i];
                uploadFile = new Upload
                {
                    filename = file.FileName,
                    filesize = file.ContentLength,
                    date = DateTime.Now,
                    id = Guid.NewGuid()
                };
                var savedFileName = Server.MapPath(Path.Combine(@"~/uploads", Path.GetFileName(uploadFile.filename)));
                if (System.IO.File.Exists(savedFileName))
                {
                    results.Add(new FileUploadJsonResult { Data = new { error = string.Format("Upload failure: {0}", string.Format("The file '{0}' already exists on the server.", file.FileName)), message = string.Empty } });
                }
                file.SaveAs(savedFileName);
                results.Add(new FileUploadJsonResult { Data = new { message = string.Format("{0} uploaded successfully", file.FileName) } });
            }
        }
    }
    catch (Exception ex)
    {
        results.Add(new FileUploadJsonResult { Data = new { error = string.Format("Upload failure: {0}", ex.Message), message = string.Empty } });
    }
    return results;
}

I am kind of stuck at this point. The upload works fine, but my javascript errors out because I am returning a list rather than a single item. I imagine if I tweak that part of the code to iterate through the result list, this can be accomplished.

Another thing that I would like to be able to do is after each file finishes uploading, to add success text to the resultBox.

Any assistance will be appreciated! Thanks!

A: 

Hiya, you must return an ActionResult here, you cannot return a list of them. Also your using the Request.Files collection so there is no need to use HttpPostedFileBase fileBase in your action.

something like this might do you

    public FileUploadJsonResult AjaxUpload() {
        try {
            foreach (string name in Request.Files) {
                var file = Request.Files[name];
                if (!string.IsNullOrEmpty(file.FileName)) {
                    file.SaveAs( Server.MapPath(Path.Combine(@"~/uploads", Path.GetFileName(file.FileName)));
                }
            }
        } catch (Exception ex) {
            return new FileUploadJsonResult {
                Data = new {
                    error = string.Format("Upload failure: {0}", ex.Message),
                    message = string.Empty
                }
            };
        }

        return new FileUploadJsonResult { 
            Data = new { 
                message = "file(s) uploaded successfully"
            }
        };
    }

Also, you might want an HttpModule to provide a progress bar the example uses the jQuery.form plugin and the jquery ui progress bar

Anthony Johnston
For the script that I have in place to work properly, I must have the HttpPostedFileBase as an argument. In the original implementation, you will see that the argument is used in the code. In the multi-file implementation, I use Request.Files instead so I can process each file.
Anders
Hi Anders, I see it is used in your original implementation, but not in your multi file implementation. You could alter your js to load each file separately and use the original implementation...?
Anthony Johnston
I am not entirely sure how to go about doing what you have suggested. As far I can tell, the javascript only handles submitting the form and dumping the form data into a hidden iframe so that the form can be submitted asynchronously. I am kind of rusty with js and I am just starting to get back into the swing of things. Any suggestions or something I can try? Thanks!
Anders