再說表單驗證,在Web Api中使用ModelState進行接口參數驗證

寫在前面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都寫了。

 

寫在最後

沒有上一篇的分享,就不會收到你們的建議,也許就不會有此次的實踐,因此,分享就意味着收穫!

相關文章
相關標籤/搜索