I am currently working on implementing a feature that involves uploading attachments for an email message to be sent from my site. I've set up an input tag with the multiple keyword and am using ajax to send the files along with other model properties back to the controller.
However, I have noticed that all model properties are bound successfully once the controller is reached, except for the property that should contain the uploaded files.
Upon inspecting the request with Fiddler, I can see that the uploaded file(s) are present and also accessible in the Request.Files collection on the controller.
My question is whether the files should be automatically bound to the model using this method, or if I need to create a custom binder for this purpose?
[Non relevant code has been removed]
The following is the structure of the object meant to hold the files
public class Attachment
{
public IEnumerable<HttpPostedFileBase> Files { get; set; }
public Attachment()
{
Files = new List<HttpPostedFileBase>();
}
}
This is the object containing the attachments along with other model properties
public class QuoteRequest
{
public Attachment Attachment { get; set; }
public QuoteRequest()
{
Attachment = new Attachment();
}
}
The form used by the user to upload is structured as follows
@using (Html.BeginForm("Index", "Quote", FormMethod.Post, new { required = "true", id = "frm-quote", enctype = "multipart/form-data" })) {
@Html.AntiForgeryToken()
@Html.EditorFor(model => model.Contact, "ContactForm")
@Html.EditorFor(model => model.Enquiry, "EnquiryForm")
@Html.EditorFor(model => model.Attachment, "AttachmentForm")
<div class="row">
<div class="col l10 offset-l2 m6 s12 add-pad-bot center">
<button class="col s12 btn waves-effect orange" type="reset" data-test-clear>CLEAR</button>
</div>
<div class="col l10 offset-l2 m6 s12 center">
<button class="col s12 btn waves-effect waves-green green" type="button" data-test-submit>SUBMIT</button>
</div>
</div> }
This partial view handles the file input tag functionality
@using Domain.Models
@model Attachment
<section id="attachment" class="row no-mar-bot">
<div class="file-field input-field col s12">
<div class="btn primary">
<span id="icon-attachment">
<i class="medium material-icons">note_add</i>
</span>
@Html.TextBoxFor(m => m.Files, new {type = "file", multiple =
"multiple"})
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text" placeholder="Upload one or more files">
</div>
</div>
</section>
The Javascript function responsible for extracting form inputs to post to the controller
function getFormData() {
var formData;
formData = new FormData();
var fileUpload = $("#Attachment_Files").get(0);
var files = fileUpload.files;
if (files) {
// Looping over all files and adding them to FormData object
for (var i = 0; i < files.length; i++) {
console.log("files[i].name:" + files[i].name);
formData.append(files[i].name, files[i]);
}
}
// You can update the jquery selector to use a css class if you desire
$("input:not([type='file']), textarea").each(function (x, y) {
formData.append($(y).attr("name"), $(y).val());
});
return formData; }
The code for triggering the ajax post request
$.ajax({
url: "/quote",
method: "POST",
cache: false,
contentType: false,
processData: false,
data: getFormData()
headers: {
'__RequestVerificationToken':
$("input[name='__RequestVerificationToken']").val()
}
})
.fail(function(jqXHR, textStatus) { })
.done(function(data) { });
This is the controller action method designed to handle the posted data
[HttpPost]
[ValidateAntiForgeryHeader]
[Route("")]
public ActionResult Index(QuoteRequest model) { }