寫在前面html
上篇文章中說到了表單驗證的問題,而後嘗試了一下用擴展方法實現鏈式編程,評論區你們討論的很是激烈也推薦了一些很強大的驗證插件。其中一位園友提到了說可使用MVC的ModelState,由於以前一般都在Web項目中用沒在Api項目用過,想一想Api方法接收的多參數都封裝成了一個實體類,獨立於數據Model層,這樣其實很方便用ModelState作驗證,因而嘗試了一下。前端
認識ModelState jquery
咱們都知道在MVC中使用ModelState實現表單驗證很是簡單,藉助jquery.validate.unobtrusive這個插件就能輕鬆的在頁面上輸出錯誤信息,詳細的介紹能夠參考這篇文章《[Asp.net MVC]Asp.net MVC5系列--在模型中添加驗證規則》。可是在WebApi中沒有視圖頁讓咱們來展現錯誤信息,那要怎麼捕獲到驗證失敗的信息並做爲請求結果返回給請求端呢?之前學MVC的時候也沒有深究ModelState是什麼機制實現驗證,爲何用Html.ValidationMessageFor就能輸出錯誤信息?此次就係統的瞭解一下,那就先看看ModelState究竟是什麼鬼。轉到它的定義發現它就是一個Dictionary: 編程
爲了看個究竟,打開Reflector找到ModelStateDictionary,發現它有這些屬性: json
// Properties public int Count { get; } public bool IsReadOnly { get; } public bool IsValid { get; } public ModelState this[string key] { get; set; } public ICollection<string> Keys { get; } public ICollection<ModelState> Values { get; }
那這裏的Keys裝的就是被驗證的Model的屬性啦,Values就是對應key的值(ModelState類型)了。再看看ModelState類型是個什麼鬼:app
[Serializable] public class ModelState { // Fields private ModelErrorCollection _errors; // Methods public ModelState(); // Properties public ModelErrorCollection Errors { get; } public ValueProviderResult Value { get; set; } }
看它有兩個屬性Errors和Values,從它們的類型名稱就能看出究竟是幹嗎的了。Errors裝的就是驗證失敗的錯誤信息(具體就是一個ModelError),繼續看到底包含寫什麼東西:ide
[Serializable] public class ModelError { // Methods public ModelError(Exception exception); public ModelError(string errorMessage); public ModelError(Exception exception, string errorMessage); // Properties public string ErrorMessage { get; private set; } public Exception Exception { get; private set; } }
啊~看到ErrorMessage瞬間以爲哈皮啊,這就是咱們須要返回去的鬼東西!工具
但是爲何是Collection呢?那確定啊,由於一個字段能夠有多個驗證規則,好比有Required還有MaxLength等等。Value裝的就這個字段的值,具體就是一個ValueProviderResult,具體裏面是什麼就不貼代碼了,由於有什麼和本文沒太大關係,本身回去偷偷看就行了。關於模型是怎麼驗證的錯誤信息是怎麼綁上去的,看以看看Artech的Model驗證系統運行機制是如何實現的?,超詳細的解說。好了,前因後果都摸清楚了,那就開始碼代碼,主要就是手動把錯誤信息抓出來。ui
代碼實現this
以登陸場景爲例,爲登陸接口封裝了一個登陸模型,並加上驗證規則:
public class MemberLogin { /// <summary> /// 登陸手機號 /// </summary> [Required(ErrorMessage = "請輸入手機號碼")] [RegularExpression(@"^1[3|4|5|7|8][0-9]\d{8}$", ErrorMessage = "手機號格式錯誤")] public string Phone { get; set; } /// <summary> /// 驗證碼key /// </summary> [Required(ErrorMessage = "驗證碼無效")] public string CodeKey { get; set; } /// <summary> /// 驗證碼值 /// </summary> [Required(ErrorMessage = "請輸入短信驗證碼")] public string CodeValue { get; set; } }
而後在接口裏第一行加上:
if (!ModelState.IsValid) { string error = string.Empty; foreach (var key in ModelState.Keys) { var state = ModelState[key]; if (state.Errors.Any()) { error = state.Errors.First().ErrorMessage; break; } } return ApiResponse(new ReturnMessage() { Status = ResultStatus.Failed, Message = error }); }
主要思路就是:驗證失敗後遍歷ModelState的Key,若是這個被驗證的字段至少有一項驗證失敗(ModelError),那麼就拿到第一個ErrorMessage,而後就結束遍歷,由於取到全部的也沒什麼用,也方便前端對結果進行處理。
用swagger的接口調式工具發起請求,獲得響應以下:
CodeValue也是空的可是沒有返回錯誤信息,是由於在取錯誤信息的時候取到第一條後就break了。
到這裏貌似大功告成了,但仔細一想,每一個接口裏都要寫這麼大一坨重複代碼,真是很難受,那怎麼搞?沒錯,MVC裏有個神奇的東西-Filter,WebApi完整地沿用了這一優秀的特性,用比較高端的說法就是面向切面編程(AOP)中的分離橫切點的思想,從而實現代碼複用。那就建立一個Attribute類並繼承System.Web.Http.Filters .ActionFilterAttribute,而後重寫OnActionExecuting方法,具體內容就是剛纔那一大坨稍微調整一下,完整代碼爲:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method , Inherited = true)] public class ModelValidationAttribute : ActionFilterAttribute { public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) { var modelState = actionContext.ModelState; if (!modelState.IsValid) { string error = string.Empty; foreach (var key in modelState.Keys) { var state = modelState[key]; if (state.Errors.Any()) { error = state.Errors.First().ErrorMessage; break; } } ReturnMessage response = new ReturnMessage() { Status = ResultStatus.Failed, Message = error }; actionContext.Response = new HttpResponseMessage(HttpStatusCode.Accepted) { Content = new StringContent(JsonConvert.SerializeObject(response), System.Text.Encoding.GetEncoding("UTF-8"), "application/json") }; } } }
而後在接口上打上[ModelValidationAttribute]這麼個標籤就ok了。固然了,這個Attribute我指定了使用範圍包含Class,直接打在Controller上面也是闊以滴~這樣就不用每一個Action都寫了。
寫在最後
沒有上一篇的分享,就不會收到你們的建議,也許就不會有此次的實踐,因此,分享就意味着收穫!