2018年5月14日 星期一

部落格正式搬家到 https://toyo0103.github.io


原本寫部落格只是想記錄工作碰到的問題與解法,並希望藉由回顧部落格來檢視自己是否有在前進與成長,不知不覺就這樣寫了5年多。

最近無意間看到Hexo後,覺得用Markdown的寫作方式相當吸引我(以前常常為了調整CSS覺得很阿雜),而且Hexo有許多第三方的整合與支援,讓部落格只要稍微設定一下就有一定水準。

因為上述原因,決定花了一些時間把部落格搬家到 https://toyo0103.github.io/ ,之後這邊文章還是會保留,只是就不會再進行維護跟新增了,也感謝這段時間大家的回饋跟指教!!

2018年5月7日 星期一

【MVC教學】驗證參數- 透過FluentValidation


Demo範例 : Git位置

上一篇簡介了Controller如何安排程式邏輯流程、驗證參數,卻也發現了驗證參數導致程式寫得冗長不易維護,這一篇要介紹【FluentValidation】這個套件來解決這個問題。


首先先透過Nuget來安裝套件,因為我們用的版本是MVC5,所以安裝這個版本



安裝完後應該會在參考中看到多出兩個FluentValidation的組件


設定


把之前的程式碼稍作整理,將Account與Password新建一個類型(Class)來封裝起來,原本Controller裡面的程式整理一下






將驗證的邏輯透過FluentValidation來做

建立一個Class命名為UserSignUpParameterValidation



將UserSignUpParameter掛上Validator Attribute,意思是UserSignUpParameter請幫我用UserSignUpParameterValidation裡面的邏輯來驗證



將原本的驗證邏輯慢慢的移入UserSignUpParameterValidation之中

1. 帳號密碼不能為空值


    /// <summary>
    /// Validation UserSignUpParameter
    /// </summary>
    /// <seealso cref="FluentValidation.AbstractValidator{HelloDotNetMVC.Parameters.UserSignUpParameter}" />
    public class UserSignUpParameterValidation:AbstractValidator<UserSignUpParameter>
    {
        public UserSignUpParameterValidation()
        {
            //AbstractValidator為FluentValidation提供的抽象類別
            //目的是讓使用者可以透過繼承這個抽象類別後,實作自己的驗證邏輯
            //而泛型T, AbstractValidator<T> ,則帶入你想驗證的類別
            //所以這邊帶入UserSignUpParameter,這個我們剛剛製作的Class

            //Account
            RuleFor(x => x.Account)
            .NotEmpty()
            .WithMessage("帳號不能為Empty")
            .NotNull()
            .WithMessage("帳號不能為Null");

            //Password
            RuleFor(x=>x.Password)
            .NotEmpty()
            .WithMessage("密碼不能為Empty")
            .NotNull()
            .WithMessage("密碼不能為Null");
        }
    }

RuleFor是FluentValidation提供的方法,意思是我為Account這個屬性建立驗證的Rule,而Rule分別是,NotEmpty(不能為空值)、NotNull(不能為Null),而緊接在每個驗證邏輯下面的WithMessage則是表示,當發生它上面的錯誤時,錯誤訊息請回傳這行。


接著我們可以將Controller的這塊驗證邏輯拿掉,交給UserSignUpParameterValidation專責處理就好。




2. 帳號必須包含@字元,Password必須大於6個字

因為判斷字元@並不像判斷空值與Null一樣,官方有實做好的方法,必須自己透過Must來定義,這邊提供兩種寫法給讀者參考




上面是匿名方法的寫法,但如果你對於匿名委派還不是很熟悉,也可以用比較簡單的方式寫


先寫好驗證的方法,然後在Must中帶入方法。

延伸閱讀 : 
MSDN : Func<T,TResult> 委派 
MSDN : 匿名方法

    /// <summary>
    /// Validation UserSignUpParameter
    /// </summary>
    /// <seealso cref="FluentValidation.AbstractValidator{HelloDotNetMVC.Parameters.UserSignUpParameter}" />
    public class UserSignUpParameterValidation:AbstractValidator<UserSignUpParameter>
    {
        public UserSignUpParameterValidation()
        {
            //AbstractValidator為FluentValidation提供的抽象類別
            //目的是讓使用者可以透過繼承這個抽象類別後,實作自己的驗證邏輯
            //而泛型T, AbstractValidator<T> ,則帶入你想驗證的類別
            //所以這邊帶入UserSignUpParameter,這個我們剛剛製作的Class

            //Account
            RuleFor(x => x.Account)
            .NotEmpty()
            .WithMessage("帳號不能為Empty")
            .NotNull()
            .WithMessage("帳號不能為Null")
            .Must(IncludeAccountKeyword)
            .WithMessage("帳號不符合格式");

            //Password
            RuleFor(x=>x.Password)
            .NotEmpty()
            .WithMessage("密碼不能為Empty")
            .NotNull()
            .WithMessage("密碼不能為Null")
            .MinimumLength(7)
            .WithMessage("密碼必須大於6個字元");
        }

        /// <summary>
        /// Accounts 必須包含@.
        /// </summary>
        /// <param name="account">The account.</param>
        /// <returns></returns>
        private bool IncludeAccountKeyword(string account)
        {
            if (!string.IsNullOrWhiteSpace(account))
            {
                return account.Contains("@");
            }

            return false;
        }
    }

整理後的Controller



透過ModelState來得知參數是否符合規則

ModelState是本來MVC就有提供的參數驗證方式,而使用方法這邊提供幾篇文章給有興趣的讀者參考
What is the ModelState? - ASP.NET MVC Demystified
保哥 : ASP.NET MVC 開發心得分享 (28):深入了解 ModelState 內部細節

而因為開發習慣的關係,我習慣用FluentValidation的方式來取代原本ModelState驗證方法與設定方式。


接著我們在Global.asax中呼叫FluentValidationModelValidatorProvider.Configure()


        [HttpPost]
        public ActionResult SignUp(UserSignUpParameter parameter)
        {
            if (!ModelState.IsValid)
            {
                //IsValida為False時,表示驗證參數不過
                //取出第一筆錯誤訊息回傳
                var Error = ModelState.Values.SelectMany(x => x.Errors).First();
                TempData["Message"] = Error.ErrorMessage;
                return RedirectToAction("signup", "user");
            }

            //這邊沒有搬到FluentValidation去驗證的原因是
            //使用者存在與否比較屬於服務層的事情,通常需要讀取資料庫才能判斷
            //開發上習慣盡量讓驗證參數越乾淨簡單越好,而不是在裡面呼叫很多外部服務(例如資料庫)後做驗證
            //這會讓職責過於複雜
            if (parameter.Account != "steven@mymail.com")
            {
                TempData["Message"] = "註冊成功!!";
                //註冊成功,導到首頁 
                return RedirectToAction("index", "home");

            }
            else
            {
                TempData["Message"] = "帳號已經存在";
                return RedirectToAction("signup", "user");
            }
        }

接著執行程式,驗證看看是否之前的驗證邏輯都還是正常運作。






比較一下最一開始與套用FluentValidation後的程式碼,應該可以明顯感受到差異,這不僅僅只是讓程式變乾淨,更重要的是驗證參數的職責被分離到UserSignUpParameterValidation這個類別中,以後要改參數驗證的邏輯只要到這調整即可,不用再擔心Controller會被不小心改壞。




延伸閱讀