上一篇介紹完了在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,前端需要和後端溝通也變的更加簡單。