ExtJs中的store在sync時,是可以批量提交資料的,所謂的批量是指如果在store中同時記錄了create、update、delete的對象時,分三次提交。在提交時,多個對象將被組合成JSON格式的字串,當要提交的資料只有一條時,JSON的字串如下所示:
{"ID":"a6d671ca-5480-4b5b-bf7a-b8459c0f598b","MobilePIN":"","Email":"111@ee.com","Password":"","CreateDate":null,"LastLoginDate":null,"LastPasswordChangedDate":null,"Comment":"","UserName":"user1","MobileAlias":"","LastActivityDate":null,"DisplayName":"","RememberMe":false}
如果是多條資料時,JSON的字串是:
[{"ID":"a6d671ca-5480-4b5b-bf7a-b8459c0f598b","MobilePIN":"","Email":"111@ee.com","Password":"","CreateDate":null,"LastLoginDate":null,"LastPasswordChangedDate":null,"Comment":"","UserName":"user1","MobileAlias":"","LastActivityDate":null,"DisplayName":"","RememberMe":false}]
在Controller的Action中,如果方法的簽名是public JsonResult CreateUser(UserDataObject user),則當傳入多條資料時,只能接收到第1條;當方法簽名是private List<UserDataObject> ProcessUsers(List<UserDataObject> users)時,當多條資料時沒有問題,可只有1條資料時,users為null值。
我想當然的想到在Action中直接把兩個方法都寫上,但結果報了System.Reflection.AmbiguousMatchException,即MVC的ActionSelector已經不能區分應該調哪個方法了。
經過尋找MVC的代碼,發現在AsyncActionMethodSelector的RunSelectionFilters方法中,將會尋找在Action上的Atrribute,像HttpPost這樣的Attribute都會做過濾,以判斷是否符合當前調用的上下文。
知道了這一點以後,我試想可以通過寫一個自訂的Attribute,然後根據當前Request請求的內容來決定哪個方法可用,哪個方法不可用,實現這個的關鍵點有兩個:
1、如何?這個Attribute。
2、如何得到Request的Post的內容並進行解決。
第1個問題很好解決,因為我還沒有系統的看MVC方面的資料,所以查看一下HttpPost可以知道它繼承了ActionMethodSelectorAttribute,這個類裡有一個抽象的方法IsValidForRequest。
第2個問題可以得到Request後,然後判斷當前Post的類型是否為application/json,進行對Post的資料進行JSON的還原序列化。經過測試得知,如果是單一對象的話,還原序列化化的對象是一個Dictionay<String,Object>對象,而多個對象得到的是一個Object[]。下面給出實現的源碼:
public class JsonModelPostAttribute : ActionMethodSelectorAttribute { public enum ParameterType { Single, Multiple } private ParameterType _parameterType; public ParameterType MethodParameterType { get { return _parameterType; } set { _parameterType = value; } } public JsonModelPostAttribute() { _parameterType = ParameterType.Single; } public JsonModelPostAttribute(ParameterType type) { _parameterType = type; } public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo) { var c = GetDeserializedObject(controllerContext); if (c == null) { return true; } if (MethodParameterType == ParameterType.Single) { return c is Dictionary<string, object>; } else { return c is object[]; } } private static object GetDeserializedObject(ControllerContext controllerContext) { if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) { // not JSON request return null; } controllerContext.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin); StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream); string bodyText = reader.ReadToEnd(); controllerContext.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin); if (String.IsNullOrEmpty(bodyText)) { // no JSON data return null; } JavaScriptSerializer serializer = new JavaScriptSerializer(); object jsonData = serializer.DeserializeObject(bodyText); return jsonData; } }
需要注意的時,在讀取了InputStream後,要重新將Position置為0,否則後續的JsonValueProviderFactory將得不到反序化執行個體,這樣即使能進入到正確的Action,參數也會為空白。
下面是Controller中的測試的程式碼片段:
[AllowAnonymous] [HttpPost] [JsonModelPost(JsonModelPostAttribute.ParameterType.Multiple)] public JsonResult CreateUser(List<UserDataObject> users) { ProcessUsers(users); return this.Json(new { success = true, user = users }, JsonRequestBehavior.AllowGet); } private List<UserDataObject> ProcessUsers(List<UserDataObject> users) { int i = 1; foreach (var user in users) { user.ID = Guid.Empty.ToString(); user.UserName = "Server Name" + i++; } return users; } [AllowAnonymous] [HttpPost] [JsonModelPost(JsonModelPostAttribute.ParameterType.Single)] public JsonResult CreateUser(UserDataObject user) { var list = new List<UserDataObject>(new UserDataObject[] { user }); ProcessUsers(list); return this.Json(new { success = true, user = list[0] }, JsonRequestBehavior.AllowGet); }