看到很多ASP.NET MVC項目還在從request.querystring或者formContext里面獲取數據,這實在是非常落后的做法。也有的項目建了大量的自定義的modelbinder,以為很牛,實際上也落后的很。
ASP.NET MVC提供了IModelBinder的默認實現,這個實現的類就叫DefaultModelBinder。我們在寫代碼的時候,幾乎感覺不到這個類的存在,因為這個類自動將request信息解析成action參數。本文將向大家展示這個類是多么強大,可以拯救大量的代碼。
先看個例子。假如有如下表單,用於編輯用戶信息以及該用戶的時間表。在這個例子中,我要利用DefaultModelBinder自動將整個表單數據解析成復雜實體類的實例。一行手工解析的C#代碼都不用寫。
對應的controller的代碼如下,很簡單:
1 public class DemoController : PublicControllerBase 2 { 3 public ActionResult UserEditor() 4 { 5 return View(); 6 } 7 8 [HttpPost] 9 public string SaveUser(DemoUser user) 10 { 11 var result = string.Empty; 12 if (user != null) 13 { 14 result = Serializer.ToJson(user); 15 } 16 return result; 17 } 18 }
相關的實體類的定義,也很簡單:
public class DemoUser { public string Username { get; set; } public string Email { get; set; } public string Language { get; set; } public Gender Gender { get; set; } public int[] RoleIds { get; set; } public List<ScheduledJob> Jobs { get; set; } } public class ScheduledJob { public string Job { get; set; } public string From { get; set; } public string To { get; set; } } public enum Gender { Unknown = 0, Male = 1, Female = 2 }
請注意SaveUser這個action的參數,一個比較復雜的實體類的對象。DefaultModelBinder會自動將這個復雜的表單解析出來。這個保存的action將參數user直接序列化JSON字符串返回到瀏覽器。
下面看看HTML和JS。
HTML:
1 <form id="formUserEditor" action="/demo/saveuser" method="POST"> 2 <table class="form"> 3 <colgroup> 4 <col width="100"/> 5 <col width="auto"/> 6 </colgroup> 7 <tbody> 8 <tr> 9 <td>用戶名:</td> 10 <td> 11 <input id="txtUsername" type="text" name="username" /> 12 </td> 13 </tr> 14 <tr> 15 <td>Email:</td> 16 <td> 17 <input id="txtEmail" type="text" name="email" /> 18 </td> 19 </tr> 20 <tr> 21 <td>語言:</td> 22 <td> 23 <select id="ddlLanguages" name="language"> 24 <option value="zh-cn">中文</option> 25 <option value="en-us">英文</option> 26 </select> 27 </td> 28 </tr> 29 <tr> 30 <td>性別:</td> 31 <td id="genders"> 32 <input type="radio" name="gender" value="@(Taoad.Web.Publics.Controllers.Gender.Unknown)" id="rdUnknown" /> 33 <label for="rdUnknown">未知</label> 34 35 <input type="radio" name="gender" value="@(Taoad.Web.Publics.Controllers.Gender.Male)" id="rdMale" /> 36 <label for="rdMale">男</label> 37 38 <input type="radio" name="gender" value="@(Taoad.Web.Publics.Controllers.Gender.Female)" id="rdFemale" /> 39 <label for="rdFemale">女</label> 40 </td> 41 </tr> 42 <tr> 43 <td>角色:</td> 44 <td id="roles"> 45 <input type="checkbox" name="roleids" value="1" id="cb1" /> 46 <label for="cb1">管理員</label> 47 48 <input type="checkbox" name="roleids" value="2" id="cb2" /> 49 <label for="cb2">部門經理</label> 50 51 <input type="checkbox" name="roleids" value="3" id="cb3" /> 52 <label for="cb3">客戶</label> 53 </td> 54 </tr> 55 <tr> 56 <td>時間:</td> 57 <td> 58 <ul id="jobs"> 59 60 </ul> 61 <input type="button" value="添加" id="btnAddJob"/> 62 </td> 63 </tr> 64 <tr> 65 <td></td> 66 <td> 67 <input type="button" value="保存" id="btnSave"/> 68 <input type="button" value="取消" id="btnCancel" /> 69 </td> 70 </tr> 71 </tbody> 72 </table> 73 </form> 74 <hr/> 75 <div id="json"> 76 77 </div>
JS:
1 <script language="javascript" type="text/javascript"> 2 $(document).ready(function () { 3 $("#btnAddJob").click(function() { 4 var $newLi = $(html); 5 $("#jobs").append($newLi); 6 bindLi($newLi); 7 }); 8 9 $("#btnSave").click(function() { 10 var data = $("#formUserEditor").serialize(); 11 $("#jobs li").each(function(i) { 12 var prefix = "&jobs[" + i + "]"; 13 data += prefix + ".job=" + $(this).find(".job-id").val(); 14 data += prefix + ".from=" + $(this).find(".job-from").val(); 15 data += prefix + ".to=" + $(this).find(".job-to").val(); 16 }); 17 demo.ajax.post("/demo/saveuser", data, function(json) { 18 $("#json").html(json); 19 }); 20 }); 21 }); 22 23 function bindLi(li) { 24 $(li).find(".btn-add").click(function () { 25 var $li = $(this).closest("li"); 26 var $newLi = $(html); 27 $li.after($newLi); 28 bindLi($newLi); 29 }); 30 $(li).find(".btn-delete").click(function () { 31 $(this).closest("li").remove(); 32 }); 33 } 34 35 var html = '<li>\ 36 <select class="job-id">\ 37 <option value="job1">工作1</option>\ 38 <option value="job2">工作2</option>\ 39 </select>\ 40 <input type="text" placeholder="開始時間" class="job-from"/>\ 41 ————\ 42 <input type="text" placeholder="結束時間" class="job-to"/>\ 43 <a href="javascript:void(0);" class="btn-add">添加</a> |\ 44 <a href="javascript:void(0);" class="btn-delete">刪除</a>\ 45 </li>'; 46 </script>
如果是如下的表單數據:
點擊保存之后,返回的JSON數據為:
可以看到所有的表單數據都保存成功了。
再看看request信息:
請注意content-type的值。
實際上,POST到服務器的表單數據只是一個字符串,如下:
復制出來就是下面這樣的字符串:
ajax=true&username=leo&email=leo%40gmail.com&language=zh-cn&gender=Unknown&roleids=1&roleids=3&jobs[0].job=job1&jobs[0].from=9:00&jobs[0].to=10:00&jobs[1].job=job2&jobs[1].from=10:00&jobs[1].to=11:00
由此可知,可用JS來拼接字符串,將整個表單通過鍵值對的形式序列化成一個字符串,再將該字符串傳到服務器,這時DefaultModelBinder就可以自動解析實體類了。
關鍵點在於,對於List或者數組類型的數據,要加上數組下標。這樣,任意復雜的數據結構,DefaultModelBinder都可以自動解析了。
思考一:
如果表單數據的鍵帶有”demouser”的前綴,如下所示,那么這個action的參數還能自動解析嗎?
Demouser.username=leo&demouser.email=leo@gmail.com&demouser.jobs[0].job=job1&.....
Action如下:
1 [HttpPost] 2 public string SaveUser(DemoUser user) 3 { 4 var result = string.Empty; 5 if (user != null) 6 { 7 result = Serializer.ToJson(user); 8 } 9 return result; 10 }
如果action參數名稱又改成demouser呢?
思考二:
如果將action的參數名稱改成如下代碼所示,那么是否可以自動解析?(表單數據不帶”demouser”前綴)
ajax=true&username=leo&email=leo%40gmail.com&language=zh-cn&gender=Unknown&roleids=1&roleids=3&jobs[0].job=job1&jobs[0].from=9:00&jobs[0].to=10:00&jobs[1].job=job2&jobs[1].from=10:00&jobs[1].to=11:00
Action如下:
1 [HttpPost] 2 public string SaveUser(DemoUser demoUser) 3 { 4 var result = string.Empty; 5 if (demoUser != null) 6 { 7 result = Serializer.ToJson(demoUser); 8 } 9 return result; 10 }
思考三:
如果action的參數是用另一個類包起來了,如下代碼所示,那么表單數據應該是怎么樣的字符串才能使DefaultModelBinder可以自動解析?
1 public class UserEditorViewModel 2 { 3 public DemoUser DemoUser { get; set; } 4 } 5 6 [HttpPost] 7 public string SaveUser(UserEditorViewModel model) 8 { 9 var result = string.Empty; 10 if (model != null) 11 { 12 result = Serializer.ToJson(model); 13 } 14 return result; 15 }
思考四:
如果action是這樣定義的,那么表單數據的字符串又該怎么拼?
1 [HttpPost] 2 public string SaveUser(DemoUser demoUser, List<ScheduledJob> scheduledJobs) 3 { 4 var result = string.Empty; 5 if (demoUser != null) 6 { 7 demoUser.Jobs = scheduledJobs; 8 result = Serializer.ToJson(demoUser); 9 } 10 return result; 11 }
歡迎討論。