Geeks With Blogs
The Quandary Phase This code was generated by a tool.

Yes, I know, the multiple file uploading in ASP.NET issue has been exhaustively covered already elsewhere. There are examples of multiple file uploading using ASP.NET user controls, there are examples using jQuery, there are examples using flash to provide progress feedback, and even examples in Silverlight.

However, what I wanted was a simple solution which allows the user to select a variable number of files, didn’t rely on postbacks in order to increase or decrease the number of files selected, and also allows the user to enter a description for each uploaded file. The description was to be saved to the database, and the file saved to the server’s file system (with the path stored in the database so the file can be subsequently retrieved).

The way the page should work was also straightforward: the user should initially be presented with a single file upload control and file description textbox, but they should have the option to add and remove additional rows. Here’s a quick screengrab to illustrate what we’re aiming for:

It is very possible to create an upload page of this type using a ListView or Repeater control, using similar code to that I outlined in an earlier article, however this would require additional postbacks for the user to add or remove files, which is not what I wanted.

The alternative is, of course, to use javascript to create the additional upload controls and textboxes. Not in itself a hugely tricky task, but there is one usability issue which needs to be resolved. As any ASP.NET developer already knows, any changes made to the page client-side are not persisted by the ASP.NET viewstate, so will disappear after the page posts back.

This is not particularly desirable behaviour in this case. Let’s say after the user adds several files and clicks the upload button, and I then want to validate the file extensions server side, in order to display a message to the user if they have uploaded a file with an invalid extension (I may want to exclude all .exe files, for example). If all the file input fields have been created using javascript, after the postback they will have disappeared, so the user will have to input the whole lot again if the validation fails. This would be a hugely irritating thing to force users to do.

The solution is to manually re-create the controls on each page load. This involves keeping track of the number of files which have been created each time the page posts back, and ensuring that the appropriate javascript is invoked to rebuild the table.

So, enough with the rambling: let’s get to the code.

I started off with some page markup and a little CSS:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Upload Multiple Files</title>
    
    <style type="text/css">
        
        #tblFilesContainer
        {
            width: 80%;
            border: solid 1px black;
        }
    
        #tblFilesContainer tr td
        {
            border: dashed 1px black;
        }
        
        #tblFilesContainer tr.noborder td
        {
            border: none;
        }
        
        #tblFilesContainer tr td input
        {
            width: 99%;
        }
    
    </style>
</head>
<body>
    <form id="form1" runat="server" enctype="multipart/form-data" method="post">
           
        <h3>Upload Files</h3>
        <br/>
       
        <table id="tblFilesContainer" style="width:80%; border: solid 1px black">
      
            <tr class="noborder">
                <td style="width: 10%"></td>
                <td style="width: 30%"></td>
                <td style="width: 10%"></td>
                <td style="width: 30%"></td>
                <td style="width: 20%"><a href="#" onclick="return addFile();" >Add File</a></td>
            </tr>  
       
        </table>
        
        <br />
        
        <asp:Button ID="btnUpload" runat="server" Text="Upload" OnClick="btnUpload_Click" />
    </form>
</body>
</html>

In the markup above, I have created a table which will act as the container for the file upload controls and description textboxes, a link which will act as the trigger for adding an additional file input control row to the table, and some very basic styles to add some borders to the table. Note that the link is executing a javascript function named addFiles() onclick (and also passing itself in as a parameter value): we’ll return to this shortly.

Finally, there is a button which will be used to submit the page and kick off the uploading- this is hooked up to a server-side event handler.

One thing to watch out for: you must remember to add the enctype="multipart/form-data" attribute to the form tag on the page. ASP.NET only adds this when one or more FileUpload controls exist on a given page. As all the file input fields are being created by javascript in this case, it won’t get added unless you do so manually.

It was then time to start with the javascript. The table which contains the upload controls has five cells on each row, so we’re going to need a bit of code which can create a table row with five cells, and insert it into the existing table, but leaving the last table row in the same position (as it contains the ‘Add File’ link). I started by placing the HTML markup for each cell into a javascript array in a script block:

 

<script type = "text/javascript">
 
    var cellTemplates = new Array();
    cellTemplates[0] = 'Description:';
    cellTemplates[1] = '<input id="txtDescription{counter}" name="txtDescription{counter}" type="text" value="{value}" />';
    cellTemplates[2] = 'Select File:';
    cellTemplates[3] = '<input id="file{counter}" name="file{counter}" type="file" />';
    cellTemplates[4] = '<a href="#" id="lnkRemoveFile{counter}" onclick="return removeFile(this);">Remove</a>';
 
    var counter = 0;
 
ript>

This was to prevent having to do a whole bunch of messy string manipulation in the function which will add new rows to the table.

There is also a variable named ‘counter’ which is used to track the current file input row count.

Note the above HTML markup contains a couple of placeholders: {counter} and {value}. These placeholders are replaced at runtime with variable values. The {counter} placeholder is replaced with the value of the counter variable. This is to ensure all the controls are named sequentially to make it easier to retrieve their values server-side. The {value} placeholder is replaced with the file description- and this only occurs when the page is reloaded after a postback.

Now for the functions to add and remove file input rows. Here’s the full code:

 

//adds a file input row
function addFile(description) {
 
    //increment counter
    counter++;
 
    var tbl = document.getElementById("tblFilesContainer");
    var rowCount = tbl.rows.length;
    var row = tbl.insertRow(rowCount - 1);
    var cell;
    var cellText;
 
    for (var i = 0; i < cellTemplates.length; i++) {
        cell = row.insertCell();
 
        //format the cell template text
        cellText = cellTemplates[i].replace(/\{counter\}/g, counter).replace(/\{value\}/g,
        (description == null) ? '' : description);
        cell.innerHTML = cellText;
    }
}
 
//removes a file input row
function removeFile(ctrl) {
    var tbl = document.getElementById("tblFilesContainer");
    if (tbl.rows.length > 2)
        tbl.deleteRow(ctrl.parentNode.parentNode.rowIndex);
}

The addFile() function starts by retrieving the current file input row count and incrementing it- it then adds the additional row to the table, by retrieving the template HTML markup from the cellTemplates array and replacing the placeholders with the appropriate values, as outlined above. It makes use of javascript’s regular expression find-and-replace functionality to inject the counter and description values. And don’t forget the ‘g’ modifier at the end of the regular expression; this results in a global find-and-replace (i.e. all matching instances of the search pattern are replaced- the default behaviour is just to replace the first instance found).

As mentioned above, when a ‘Remove File’ link is clicked, the link passes itself as the parameter value to the removeFile method. The method uses this to select the link’s parent node (i.e. the table cell it is in), and then the parent of that node (the table row) and removes it,

I added one additional javascript function to reinitialise all the textboxes after a page reload: here it is:

//creates all file inputs on page load
function createFiles(descriptions) {
    for (var i = 0; i < descriptions.length; i++) {
        addFile(descriptions[i]);
    }
}

 

The method simply accepts in an array of file descriptions, and uses this to recreate each of the file input rows.

Then to the server-side code. Firstly, the handler for the upload button’s click event: this needed to extract the files and descriptions from the HttpRequest and pass all this information through to a business logic class for validation and saving. In my code, I am collecting all the files and descriptions first, and passing these through to the business logic layer in a single hit, but to keep the example concise, I have removed all this extraneous code to concentrate on getting the file and description. Here is the button click event handler in full:

protected void btnUpload_Click(object sender, EventArgs e)
{
    int counter;
    string description;
    HttpPostedFile file;
 
    foreach (string key in Request.Files.Keys)
    {
        //get a reference to the file
        file = Request.Files[key];
 
        if (file != null && file.ContentLength > 0)
        {
            //get the value of the description textbox
            counter = int.Parse(key.Replace("file", string.Empty));
            description = Request.Form["txtDescription" + counter.ToString()];
 
            //save file and description here...
        }
    }
}

Because the file upload controls are description textboxes were named with matching integer values appended to their names, it is possible to iterate over the file input IDs and use each ID to find the relevant description in the Request.Form collection.

So everything is now in place to allow users to upload multiple files, but we still haven’t solved the original problem of maintaining state between postbacks.

The final code example fills in the last missing piece of functionality:

 

protected void Page_Load(object sender, EventArgs e)
{
    //register the client scripts
    this.RegisterFileInputScripts();
}
 
/// <summary>
/// Registers the startup scripts to create the file input controls.
/// </summary>
private void RegisterFileInputScripts()
{
    if (!Page.IsPostBack)
    {
        //JS: create the initial file input control
        Page.ClientScript.RegisterStartupScript(this.GetType(), 
            "CreateFileInput", "addFile();", true);
    }
    else
    {
        StringBuilder text = new StringBuilder();
 
        int counter;
        SortedList<int, string> descriptions = new SortedList<int,string>();
        foreach (string key in Request.Files.Keys)
        {
            counter = int.Parse(key.Replace("file", string.Empty));
            descriptions.Add(counter, (Request.Form["txtDescription" + counter.ToString()]));
        }
 
        //JS: add the file descriptions to a javascript array
        text.Append("var descriptions = new Array(");
        int i = 0;
        foreach (KeyValuePair<int, string> item in descriptions)
        {
            text.AppendFormat("'{0}'", item.Value ?? string.Empty);
            if (++i < descriptions.Keys.Count)
                text.Append(",");
        }
 
        //JS: close the array declaration &invoke the createFiles method
        text.Append("); createFiles(descriptions);");
 
        //register the javascript
        Page.ClientScript.RegisterStartupScript(this.GetType(), "RecreateFileInputs",
            text.ToString(), true);
    }
}

The code snippet above ensures that the addFile() method is called once when the page first loads, to create the initial file input control. And on each postback it iterates over the posted files collection, building up an array of file descriptions corresponding to each posted file, and finally registers a call to the createFiles javascript method, passing in an array containing all the descriptions.

And that's a wrap: a basic multiple file upload system, driven by client-side javascript rather than server-side code. As ever, you can download the source code using the link at the top of the article.

Posted on Friday, October 2, 2009 4:17 AM | Back to top


Comments on this post: ASP.NET: Multiple File Uploading

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
Adam,

Thanks for the info. Learned a bit about the code behind client script topic. I like being able to rebuild the controls on postback. However, the file input controls are all blank after postback. Is the user supposed to reselect all the files if an error occurs? I'm assuming the whole point to rebuilding the controls was to prevent the user from having to reselect files. I believe the security features imposed on file input controls prevents resetting the value or file path of the control after postback. I may have to stuff the posted files into a session variable on postback if an error occurs and just pull them out of session during the next postback if necessary.

Anyhow thanks for the info.
Left by oja on Oct 29, 2009 6:48 PM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
Excellent example for multi file upload with descriptions.
Left by krishna velidi on Feb 05, 2010 2:36 AM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
Without a demo it's al blah blah blah.
Left by Thorton on Feb 17, 2010 8:29 AM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
@thornton: agreed, which is why there is a source code link at the top. I have updated the article text to make that clearer.
Left by adampooler on Feb 17, 2010 8:30 PM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
ASP.NET is more easier than other development technologies and languages. While working on asp.net platform, you may access to system windows event log. Using System namespace you retrieve messages. ASP.NET is more easier than other development technologies and languages.
Left by jeu de Kéno Vidéo on Mar 13, 2010 12:39 AM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
very good
I'm luck to you
tnx
Left by Hamid Reza Mansouri on Nov 24, 2010 8:53 PM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
Your blog article is very intersting and fantastic,at the same time the blog theme is unique and perfect,great job.To your success.One of the more impressive blogs Ive seen. Thanks so much for keeping the internet classy for a change.I ran across search engines and find this blog that fulfill my needs. There is a thing I do not agree but It doesn't matter since I think it does not hurt the whole content.
The added inshight increases my insights about:Indonesian Furniture | Indonesian Teak Furniture.
Left by John Agung Vanderbilt on Dec 07, 2010 5:01 AM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
FileUltimate is an ASP.NET file upload control which you can add directly to your existing ASP.NET (.aspx) pages.The control renders a user interface similar to "Windows Explorer" within the page which displays the contents of the target folder and accepts multiple file uploads from users. Actions can be limited by permissions and quota limits on folders. During file uploading, detailed information such as transfer speed and estimated time of completion are displayed along with the progress bar. This ASP.NET upload control supports browser upload, ajax upload and flash upload modes.
Left by upload.net on Jan 31, 2011 11:19 AM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
Very brilliant. It's so meaningful. Thank you so much.
Left by myvietnamvisa on Feb 17, 2011 7:53 PM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
great!By the way,may be this will be better <a href="http://www.cheapnfljerseyszone.com" </ a> <br>come in and take yourfavorite! cheap jerseys </ a> <br>
Left by Jams Huang on Mar 13, 2011 8:02 PM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
Nice info. I'll try to implement it.
Left by mahogany furniture on Mar 20, 2011 4:36 AM

# re: ASP.NET: Multiple File Uploading
Requesting Gravatar...
Very helpful article.
Left by Faisal on Mar 25, 2011 3:12 PM

Comments have been closed on this topic.
Copyright © Adam Pooler | Powered by: GeeksWithBlogs.net