今天遇到使用Model Binding來快速處理表單AJAX回傳FormData時,表單中需要包含多值集合的項目,一直以來沒有在Model Binding的模型下進行這樣的資料型態處理,因此進行個小實驗來驗證一下,另外也順便實驗了多重檔案上傳的可能性,將程式碼紀錄於此供給有需要的人查閱。
舉例來說,有一份考卷資料,裡面包含了成績單的名稱,另外包含了一個學生成績清冊列表的集合,另外也有可能存在著考卷的影像檔案,這些資料如何透過FormData以AJAX的方式向後端傳送,並讓後端可以順利的讀取呢?
public class Exam { public string cName { get; set; } public System.Collections.Generic.List<Student> oStudents { get; set; } } public class Student { public string cName { get; set; } public int iScore { get; set; } }
值得一提的是,在一些網路文獻裡面有提到,可以在Exam類別下加上HttpPostedFile或是HttpPostedFileBase型別的檔案屬性資料,但就我實際上的測試來說,並不會被Model Binding自動識別與讀取。原因是先前我的WebForm DataBinding支援能力是從微軟的原始碼幹過來的,而有關於這方面全部的實作微軟早就轉移陣地至System.Web.MVC之中,包含IValueProvider、ValueProviderFactories、HttpFileCollectionValueProviderFactory HttpFileCollectionValueProvider...等實作,這麼龐大的架構移植到現行的WebForm框架中根本不符成本,因此直接放棄改成由Request.Files(HttpFileCollection)來拿即可。
說白點就是有關「HTTP檔案上傳」這部分放棄由ModelBinding來完成。
<form id="myForm"> <fieldset> <div class="form-row"> <div class="form-group col-12"> <label for="cKSUID">考卷名稱</label> <div class="input-group"> <input type="text" class="form-control" id="cName" name="cName"> </div> </div> </div> <div class="form-row"> <div class="form-group col-12 col-md-6"> <label for="cKSUID">學生A姓名</label> <div class="input-group"> <input type="text" class="form-control" id="oStudents[0].cName" name="oStudents[0].cName"> </div> </div> <div class="form-group col-12 col-md-6"> <label for="iMoney">學生A分數</label> <input type="text" class="form-control" id="oStudents[0].iScore" name="oStudents[0].iScore"> </div> </div> <div class="form-row"> <div class="form-group col-12 col-md-6"> <label for="cKSUID">學生B姓名</label> <div class="input-group"> <input type="text" class="form-control" id="oStudents[1].cName" name="oStudents[1].cName"> </div> </div> <div class="form-group col-12 col-md-6"> <label for="iMoney">學生B分數</label> <input type="text" class="form-control" id="oStudents[1].iScore" name="oStudents[1].iScore"> </div> </div> <div class="form-row"> <div class="form-group col-12 col-md-6"> <label for="cKSUID">考卷影像檔1</label> <div class="input-group"> <input type="file" id="oPaperFile1" name="oPaperFile1"> </div> </div> <div class="form-group col-12 col-md-6"> <label for="cKSUID">考卷影像檔2</label> <div class="input-group"> <input type="file" id="oPaperFile2" name="oPaperFile2"> </div> </div> </div> </fieldset> </form>
$.ajax({ url: cUrl, type: "POST", data: new FormData($("#myForm")[0]), processData: false, //FormDataRequired contentType: false, //FormDataRequired dataType: "json", beforeSend: function () { //傳輸中 }, complete: function () { //傳輸完成 }, error: function (oXHR, cStatus, cError) { //傳輸錯誤 }, success: function (oData) { //傳輸成功 } });
觀察一下傳輸過去的資料:
可以藉機看到multipart/form-data的依據,全部都是表單中的name屬性而已,所以在HTML中id屬性就算拿掉也可以正常運作。而「多值集合」與「多重檔案」的傳輸秘訣,關鍵就在於name屬性以陣列[n]的方式來描述即可。
//WebForm ModelBinding var oMB = WebFormModelBinding.ModelBinding<Exam>(); //取得試卷名稱 oMB.oData.cName; //取得考生姓名與成績 oMB.oData.oStudents.Select(x => $"{x.cName}|{x.iScore}"); //取得前端AJAX回傳的檔案並儲存 for (int i = 0; i < Request.Files.Count; i++) { var oFile = Request.Files[i]; oFile.SaveAs($@"\\YourFileServer\" + oFile.FileName); }
Slashview.ModelBinding這個方法,可以在相關參考中找到之前的文章。