在web開發中必不可少的會遇到表單驗證的問題,為避免數據在寫入到數據庫時出現異常,一般比較安全的做法是前端會先做一次驗證,通過后把數據提交到后端再驗證一次,因為僅僅靠前端驗證是不安全的,有太多的http請求工具可以輕松繞過你的前端驗證把危險數據提交到后端,所以,之前不做后端參數驗證的同學趕快檢查一下你的代碼~別中招了
那么,故事就是有關於后端驗證。
這里舉一個項目中真實的注冊場景,賬號注冊主要包含2個信息:手機號和驗證碼,因為我這里是用webapi的post方式從前端拿數據,所以封裝成了一個MemberRegister對象。以最基礎的非空驗證為例,通常要寫如下代碼:
如果還要加上手機號格式驗證,還得再來一個if。一旦要驗證的信息多的話代碼行就會很多,看着很冗余。想着既然做的都是同一件事,那能不能封裝一下減少代碼行?架構師allen說可以試一下鏈式編程,也就是類似Jquery的xxxx.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里面有沒有錯誤信息,如果有的話就返回錯誤信息,沒有就做后面的操作。但實際上碰到一個問題,當model為null的時候,第一步驗證沒有問題,但第二步的時候就報錯了,未將對象引用到實例,原因是model已經是null了再取model.Phone不出錯才怪。問題找到了,那就想着如果model為null就不執行后面的驗證了,想法不錯但想了很久就是沒找到辦法實現。不知所措的時候,斷點跟了一下出錯的代碼,發現報錯的地方是在執行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#鏈式編程的問題,有支持的也有反對的,反對的人說代碼可讀性不太好、簡單的問題復雜化等等。經過實際實踐,我覺得這個問題偏向於個人喜好,談不上好壞,怎樣用着爽、開發效率高就行。不喜歡的還請輕點拍磚。
當然,關於這個問題有更好解決方案的希望能交流一下。