How to Process Multiple File Uploads From a Single Input Field in C#

When Your Project Doesn't Have JS Declarative Bindings And You Can't Dynamically Create Multiple Input Fields Easily

How And Why We Ended Up Here

The most user friendly way to allow for multiple file uploads is through multiple input elements whereby a user can press a button that says "Add Another" and another <input> element is generated on the fly via a JS framework that makes it trivial via declarative bindings. If your project doesn't use such a framework, dynamically generating and removing DOM elements on the fly is a little more challenging and you also run the risk of introducing more bugs.

A Few Front End Particulars Before Going Server Side

We're going to use a single input field to support multiple file uploads instead of dynamically generating new elements. Many guides exist on how to do this, but most (all)? miss this specific case. Let’s say you have this field in your form:


<form method="POST" enctype="multipart/form-data">
	<input type="file" id="detail-docs" name=“detail_documents[]” accept=".doc,.docx" multiple />
</form>
	

As you can see above we’re specifying the input name with an array identifier ([]) appended to the end which tells our server that there are multiple files stored under one field name. We’re also using HTML5 front end validation so that only .doc and .docx file extensions will be visible from the file explorer window. The field also has the multiple attribute.

This style of file input allows for multiple attachments in one field. It's not the most user friendly approach because of the keyboard combinations required to select multiple files. Other unfriendliness includes poor display of which files have been uploaded... and possibly the most sinister is what happens when a user has already attached files and then presses the browse button again and then hits the cancel button.

Here's some (slightly untested) JavaScript to do some front end validation on file size:


$(document).on('change', '#detail-docs', function(e){
	var maxSize = 9001;
	
	if (e.target && $(e.target).attr("type") === "file" && e.target.files && e.target.files.length) {
		var totalSize = 0;

		for (var i = 0; i < e.target.files.length; i++) {
			totalSize += (e.target.files[i].size || e.target.files[i].fileSize);
		}

		return totalSize <= maxSize;
	} else {
		return true;
	}
});
	

Handling Input on the Server

Now let’s sort through the files on the server side.


if (Request.Files.Count > 0)
{
	var totalFileSize = 0; // In case you want to check file size
	
	/* 
     * Use a "for" loop as opposed to a "foreach" loop to iterate through the files
     * because if all of the files have the same input name (which is the key), each iteration of Request.Files 
     * returns the field name of each file, and in this case they would all be the same. 
    */
	for (var i = 0; i < Request.Files.Count; i++)
	{
		// Request.Files[i] gets the ACTUAL file as opposed to just the field name of the file
		// with Request.File
		var file = Request.Files[i];
		
		if (file != null && file.FileName != "")
		{
			var trueFileName = file.FileName;
			
			totalFileSize += file.ContentLength; // ContentLength is represented in bytes
			
			// From here the possibilities are endless... 
		}
	}
}
	

Good luck out there,

Marcel

Fish