上一篇介紹完了在Mvc的框架裡面,用Json方式傳遞資料的問題之後,接下來將會看這些問題將會如何解決。
- 2016 更新程式碼:在正式機器的JsonError拿不到物件資料的問題 - 詳細可以看:[Asp .Net Mvc]http status 400 的JsonResult在正式機器無法取得回傳的Json資料
解決思路
首先,最大的問題是Json內建的Serialization是沒有辦法讓我們解決這些問題。因此,我們將會使用Json .Net作為Serialization的library。
再來,會建立一個Class繼承JsonResult,來對裡面做一些處理。不過,需要注意到,因為Mvc裡面的JsonResult寫的比較死,因此有些部分還是需要重複原始的JsonResult內容,讓整個使用起來和之前的Api一樣。
最後,會在之前建立的BaseController寫一些method,方便呼叫來建立新的JsonResult的class。
CoreJsonResult
先從客制的JsonResult開始,首先這個客制的JsonResult會記錄需要顯示的錯誤訊息,並且保留和之前JsonResult一樣的api,因此:
public class CoreJsonResult : JsonResult { public IList<string> ErrorMessages { get; set; } public CoreJsonResult() { ErrorMessages = new List<string>(); } public void AddError(string errorMessage) { ErrorMessages.Add(errorMessage); } public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("GET access is not allowed. Change the JsonRequestBehavior if you need GET access."); } var response = context.HttpContext.Response; response.ContentType = string.IsNullOrEmpty(ContentType) ? "application/json" : ContentType; if (ContentEncoding != null) { response.ContentEncoding = ContentEncoding; } SerializeData(response); } }
再來SerializeData
才是實際要序列化物件的時候要做的事情:
protected virtual void SerializeData(HttpResponseBase response) { if (ErrorMessages.Any()) { var originalData = Data; Data = new { Success = false, OriginalData = originalData, ErrorMessage = string.Join("\n", ErrorMessages), ErrorMessages = ErrorMessages.ToArray() }; response.StatusCode = 400; response.TrySkipIisCustomErrors = true; } var settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new JsonConverter[] { new StringEnumConverter(), } }; response.Write(JsonConvert.SerializeObject(Data, settings)); }
可以看到,這一邊的序列化使用的就是Json .Net。那用Json .Net序列化的時候有增加一些設定:
- 使用了CamelCase作為序列化出來的參數樣式
- 遇到Enum的時候,序列化出來的是string的樣子而不是數字的值
最後,為了方便傳遞序列化物件的形態,可以在增加一個泛型的版本:
public class CoreJsonResult<T> : CoreJsonResult { public new T Data { get { return (T)base.Data; } set { base.Data = value; } } }
在BaseController增加呼叫方法
還記得之前有介紹過使用BaseController。在之後使用這個CoreJsonResult,當然不希望手動new這個物件出來,因此可以再Controller增加方法,讓使用CoreJsonResult變的更加的簡單。
public class BaseController : Controller { protected CoreJsonResult JsonValidationError() { var result = new CoreJsonResult(); foreach (var validationError in ModelState.Values.SelectMany(v => v.Errors)) { result.AddError(validationError.ErrorMessage); } return result; } protected CoreJsonResult JsonError(string errorMessage) { var result = new CoreJsonResult(); result.AddError(errorMessage); return result; } protected CoreJsonResult<T> JsonSuccess<T>(T data) { return new CoreJsonResult<T> { Data = data }; } }
基本上有三個方便呼叫的方法:
- JsonValidationError - 這個是當ModelState驗證失敗的時候,呼叫的版本
- JsonError - 這個是當發生其他類型的錯誤的時候
- JsonSuccess - 這個是當要Json返回ViewModel的時候呼叫
呼叫的範例
再來看一下實際使用的時候的範例:
// POST: /Users/Edit/5 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(Edit viewModel) { if (!ModelState.IsValid) { return JsonValidationError(); } if (viewModel.Id == null) { return JsonError("Id不能是空"); } // 如果需要以Json格式返回viewModel的時候 return JsonSuccess(viewModel); }
這邊給了三個方法使用的時候和情況。可以注意到的是如果是手動回傳錯誤訊息(JsonValidationError或者是JsonError),返回的http status是400,因此在前端的Ajax,也是由 error方法執行。假設,今天是系統出錯了,例如丟出了exception,前端接到的還是error的方法,這樣error就是處理和錯誤有關的,不管是驗證錯誤還是不預期的錯誤。
結語
透過自訂的JsonResult,不只是在速度上面比原生的還快,還可以控制很多設定,例如使用camel case而不是用pascal case。
有了自訂的JsonResult,前端需要和後端溝通也變的更加簡單。
沒有留言 :
張貼留言