由表單驗證說起,關於在C#中嘗試鏈式編程的實踐


在web開發中必不可少的會遇到表單驗證的問題,為避免數據在寫入到數據庫時出現異常,一般比較安全的做法是前端會先做一次驗證,通過后把數據提交到后端再驗證一次,因為僅僅靠前端驗證是不安全的,有太多的http請求工具可以輕松繞過你的前端驗證把危險數據提交到后端,所以,之前不做后端參數驗證的同學趕快檢查一下你的代碼~別中招了

 

那么,故事就是有關於后端驗證。

這里舉一個項目中真實的注冊場景,賬號注冊主要包含2個信息:手機號和驗證碼,因為我這里是用webapipost方式從前端拿數據,所以封裝成了一個MemberRegister對象。以最基礎的非空驗證為例,通常要寫如下代碼:

如果還要加上手機號格式驗證,還得再來一個if。一旦要驗證的信息多的話代碼行就會很多,看着很冗余。想着既然做的都是同一件事,那能不能封裝一下減少代碼行?架構師allen說可以試一下鏈式編程,也就是類似Jqueryxxxx.attr().css().html().show()這樣,看起來還不錯的樣子,那就干吧。

其實C#里也有類似的用法,比如Linq里面的xxxx.Where().OrderBy().Select()這種,但是這種實際上每次返回的都是不同的對象,然后執行對象里的方法,這並不適合我的需求,因為我執行的驗證方法肯定都是同一個,比如validate().validate().validate()這種,於是決定用擴展方法來實現。先定義一個被擴展的對象:

public class ValidateResult<T>
    {
        public List<string> Errors { get; set; }
        public T Entity { get; private set; }
 
        public ValidateResult(T entity)
        {
            Errors = new List<string>();
            Entity = entity;
        }
}

定義擴展方法:

public static ValidateResult<T> Validate<T>(this ValidateResult<T> target, Predicate<T> predicate, string errorMessage)
        {
            if (!predicate(target.Entity))
            {
                target.Errors.Add(errorMessage) ;
            }
            return target;
        }

使用辦法:

var error = new ValidateResult<MemberRegister>(model)
                .Validate(m => m != null, ResponseTip.ParamError)
                .Validate(m => !string.IsNullOrEmpty(m.Phone), ResponseTip.PhoneRequired)
                .Validate(m => !string.IsNullOrEmpty(m.CodeValue), ResponseTip.ValidateCodeRequired)
                .Errors;

理想中的情況是,可以判斷error里面有沒有錯誤信息,如果有的話就返回錯誤信息,沒有就做后面的操作。但實際上碰到一個問題,當modelnull的時候,第一步驗證沒有問題,但第二步的時候就報錯了,未將對象引用到實例,原因是model已經是null了再取model.Phone不出錯才怪。問題找到了,那就想着如果modelnull就不執行后面的驗證了,想法不錯但想了很久就是沒找到辦法實現。不知所措的時候,斷點跟了一下出錯的代碼,發現報錯的地方是在執行if (!predicate(target.Entity))的時候,於是換了一個思路,改進一下代碼:

    public class ValidateResult<T>
    {
        public string Error { get; set; }
        public T Entity { get; private set; }
 
        public ValidateResult(T entity)
        {
            Entity = entity;
        }
    }

擴展方法:

        public static ValidateResult<T> Validate<T>(this ValidateResult<T> target, Predicate<T> predicate, string errorMessage)
        {
            if (string.IsNullOrEmpty(target.Error))
            {
                if (!predicate(target.Entity))
                {
                    target.Error = errorMessage;
                }
            }
            return target;
        }

改進后的代碼把ValidateResult里的Errors取消了換成了string類型的Error(要那么多錯誤提示也沒什么用,一個就夠了),然后驗證失敗后就更新這個屬性,驗證的時候如果這個屬性string.IsNullOrEmpty(target.Error)就表示前面的驗證都通過了本次可以繼續驗證,如果! string.IsNullOrEmpty(target.Error)就表示前面的驗證已經失敗了本次不用驗證,要驗證的對象原封不動的返回。這樣子就不會報錯了,然后調用結果判斷Error是否NullOrEmpty再做相應操作。測試一下,沒有問題。代碼演變為:

 

優點

可讀性個人覺得並不比直接if差,分行顯示的話還是能很清晰看出具體的驗證項。

省去了每次判斷的if語句和return,支持自定義驗證規則和錯誤提示。

減少了代碼的行數。

 

缺點

某次驗證失敗不能中斷后面的驗證,多執行了不必要的代碼,這點用if可以避免。

 

總結

完了以后去網上找了一些C#鏈式編程的問題,有支持的也有反對的,反對的人說代碼可讀性不太好、簡單的問題復雜化等等。經過實際實踐,我覺得這個問題偏向於個人喜好,談不上好壞,怎樣用着爽、開發效率高就行。不喜歡的還請輕點拍磚。

當然,關於這個問題有更好解決方案的希望能交流一下。

 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM