Introduction
I had a requirement in MVC 4 application - A multi file uploader control that has options:
- Select multiple files at a time
- Select from different folder
- List all selected files
- Remove files from the list (Filtering)
- When submitting - Save only filtered one
- Get page model also in the Action
- Save attachments after saving the model saving or some business logic
Issues I Faced
I was getting the partial needs - Uploader control, but Model received is empty - at Action in our controller.
When Model is filled, the filtered list is incorrect (deleted files also appeared).
How I Solved It
I created a JS script that will make the <input type="file" />
control get recreated and preserve the HttpPostedFileBase files + removed/filtered files in a Hidden
field - This maintains the filtration in deleting selected files also.
When user puts some files in the fileupload control, "onchange
" event, it will hide the control and create a similar control and place there and allow users to select again. The selected files are taken and maintained in a Hidden field appending its names. With these names - we can filter the posted files list (List<httppostedfilebase>) in our action. So while passing Model - keep this hidden field value.
Using the Code
Please find the source code attached here for the full demo.
I will explain how it works. See the code snippet below.
Given below is the snippet of HTML that has a fileUploader
and a button that submits to our server action.
@using (Html.BeginForm("UploadAction", "Home",
FormMethod.Post, new { enctype = "multipart/form-data", id = "frmID" }))
{
@Html.HiddenFor(i => i.FilesToBeUploaded)
<div class="labelstyle">
<label>Files</label>
</div>
<div id="uploaders">
<input type="file" id="fileToUpload"
name="fileUpload" multiple="multiple" style="float: left;" />
<br />
<span id="spnFile" style="float: left; color: #FF0000"></span>
@Html.ValidationMessage("File")
@Html.Hidden("hdnFileUpload")
</div>
<br />
<div class="col-lg-6">
<button class="btn btn-primary" id="btnAddIssue" type="submit">Submit</button>
</div>
<br />
<div class="control-section" style="padding: 0px;">
<div id="selectedFiles"></div>
</div>
}
This portion is the script that is responsible for creation of the multi file upload and hiding the previous uploader + storing the filtered attachments to a hidden field.
<script src="~/Scripts/jquery-1.7.1.js"></script>
<script>
var nowTemp = new Date();
var now = new Date(nowTemp.getFullYear(), nowTemp.getMonth(), nowTemp.getDate(), 0, 0, 0, 0);
var files;
var storedFiles = [];
var upc = 0;
$(function () {
$(":file").attr('title', ' ');
var $loading = $('#loadingDiv').hide();
$("input[id^='fileToUpload']").change(function (e) {
doReCreate(e);
});
selDiv = $("#selectedFiles");
});
function doReCreate(e) {
alert('a');
upc = upc + 1;
handleFileSelect(e);
$("input[id^='fileToUpload']").hide();
$('<input>').attr({
type: 'file',
multiple: 'multiple',
id: 'fileToUpload' + upc,
class: 'fUpload',
name: 'fileUpload',
style: 'float: left',
title: ' ',
onchange: "doReCreate(event)"
}).appendTo('#uploaders');
}
function handleFileSelect(e) {
selDiv = document.querySelector("#selectedFiles");
if (!e.target.files) return;
files = e.target.files;
for (var i = 0; i < files.length; i++) {
var f = files[i];
selDiv.innerHTML += "<div>" + f.name +
"<a onclick='removeAtt(this)'> X </a></div>";
storedFiles.push(f.name);
}
$('#@Html.IdFor(i => i.FilesToBeUploaded)').val(storedFiles);
}
function removeAtt(t) {
var serEle = $(t).parent().text().slice(0, -3);
var index = storedFiles.indexOf(serEle);
if (index !== -1) {
storedFiles.splice(index, 1);
}
$(t).parent().remove();
$('#@Html.IdFor(i => i.FilesToBeUploaded)').val(storedFiles);
}
</script>
And find the server code, where it receives the model + hidden element and the full attachments that we need.
[HttpPost]
public ActionResult UploadAction(AnyModel model, List<HttpPostedFileBase> fileUpload)
{
foreach (HttpPostedFileBase item in fileUpload)
{
if (Array.Exists(model.FilesToBeUploaded.Split(','), s => s.Equals(item.FileName)))
{
}
}
return View("Index");
}
Also, if style "X" to remove selected files, update your script code accordingly.
Points of Interest
This is a simple one and a tricky approach - Recommended for small file(s) size - But can be any number of files .