项目须要,作一个图片上传的功能,原本是很简单,可是须要同时上传多个文件,并分条带一些额外的信息,听上去很复杂,经过下面图就能够一目了然:ajax
网上找过一些方法,但多为不支持图片与其余信息关联,或者分两次上传(文件一次,返回一个indicator,而后经过json传其余信息),不理想。偶然发现一个MVC的方法,思路很是简单,就是MVC,独特之处是把图片做为一个HttpPostedFileBase类型的属性存到ViewModel里。直接看代码:json
<form action="" method="post" enctype="multipart/form-data"> @for (int i = 0; i < Model.Count; i++) { <div class="col-xs-11 row space-top"> <div class="col-xs-2"> @Html.EditorFor(m => m[i].Title) </div> <div class="col-xs-2"> @Html.EditorFor(m => m[i].AltText) </div> <div class="col-xs-2"> @Html.EditorFor(m => m[i].Caption) </div> <div class="col-xs-3"> @Html.TextBoxFor(m => m[i].ImageUpload, new { type = "file" }) </div> </div> } <div class="container space-top" > <button class="btn col-xs-2 space-top" type="submit">Create</button> </div> </form>
// GET: Image public ActionResult Create() { var models = new List<ImageViewModel>(); models.Add(new ImageViewModel()); models.Add(new ImageViewModel()); return View(models); }
public class ImageViewModel { [Required] public string Title { get; set; } public string AltText { get; set; } [DataType(DataType.Html)] public string Caption { get; set; } [DataType(DataType.Upload)] public HttpPostedFileBase ImageUpload { get; set; } }
[HttpPost] public ActionResult Create(List<ImageViewModel> models) { var model = models[0]; var validImageTypes = new string[] { "image/gif", "image/jpeg", "image/pjpeg", "image/png" }; if (model.ImageUpload == null || model.ImageUpload.ContentLength == 0) { ModelState.AddModelError("ImageUpload", "This field is required"); } else if (!validImageTypes.Contains(model.ImageUpload.ContentType)) { ModelState.AddModelError("ImageUpload", "Please choose either a GIF, JPG or PNG image."); } if (ModelState.IsValid) { var image = new Image { Title = model.Title, AltText = model.AltText, Caption = model.Caption }; if (model.ImageUpload != null && model.ImageUpload.ContentLength > 0) { //var uploadDir = "~/uploads"; //var imagePath = Path.Combine(Server.MapPath(uploadDir), model.ImageUpload.FileName); //var imageUrl = Path.Combine(uploadDir, model.ImageUpload.FileName); //model.ImageUpload.SaveAs(imagePath); //image.ImageUrl = imageUrl; } //db.Create(image); //db.SaveChanges(); // return RedirectToAction("Index"); } return View(models); }
原文连接mvc
发现原来还能够这样,可是因为我使用的knockout,因此提交数据须要用JS完成,须要将其转换,但当我构建完对象,将其转换为JSON的时候,发现文件不是随便就能序列化的。观察上面例子提交的请求:app
跟普通的Form相比,并没有特殊之处,说明只要content-type为mutipart/form-data,Form的name按照序号加名称的格式填写,Action就能得到到指定的数据,我将代码改成这种形式:ide
<form action="Create" encType="multipart/form-data" method="post"> <div> <label for="">Title</label> <input name="[0].Title" type="text" value="a"> </div> <div> <label for="">AltText</label> <input name="[0].AltText" type="text" value="a"> </div> <div> <label for="">Caption</label> <input name="[0].Caption" type="text" value="a"> </div> <div> <label for="">ImageUpload</label> <input name="[0].ImageUpload" type="file"> </div> <div> <label for="">Title</label> <input name="[1].Title" type="text" value="b"> </div> <div> <label for="">AltText</label> <input ame="[1].AltText" type="text" value="b"> </div> <div> <label for="">Caption</label> <input name="[1].Caption" type="text" value="b"> </div> <div> <label for="">ImageUpload</label> <input name="[1].ImageUpload" type="file"> </div> <button type="submit">Create</button> </form>
依然work,说明推论合理。而后作进一步修改:post
function mySubmit() { var formData = new FormData(); formData.append("[0].Title", $("[name='[0].Title'").val()); formData.append("[0].AltText", $("[name='[0].AltText'").val()); formData.append("[0].Caption", $("[name='[0].Caption'").val()); formData.append("[0].ImageUpload", $("[name='[0].ImageUpload'").get(0).files[0]); formData.append("[1].Title", $("[name='[1].Title'").val()); formData.append("[1].AltText", $("[name='[1].AltText'").val()); formData.append("[1].Caption", $("[name='[1].Caption'").val()); formData.append("[1].ImageUpload", $("[name='[1].ImageUpload'").get(0).files[0]); console.log(formData); $.ajax({ contentType: false, url: "/Image/Create", type: "POST", processData: false, dataType: 'json', data: formData, success: function (result) { }, error: function (err) { alert(err.statusText); } }); }
注意把contentType和processData都设为false,防止AJAX本身修改数据格式。ui
到这里原本问题就解决了,可是,可是,IE9及如下不支持FormData,因而作了进一步修改,以来表单的提交功能,JS里构建须要的<input type="hidden" />,设置好name属性,而后提交表单$("#Form").submit(),思路就是这样,代码就不写了。url
其实,这里的原理我仍是不大清楚,为何action能将这样的一个name list还原为对象,我猜跟negotiation有关系,还须要进一步研究。spa