後臺引數驗證的幾種方式
前臺和後臺驗證(MVC、Struts2)的必要性經驗總結:
1.後端驗證是必需的,只有後端驗證才能保證表單資料輸入的合法性,前端驗證的主要目的是為了方便使用者,增強使用者體驗。
2.雖然不是必需的,但目前也算是一種發展趨勢,特別是面向一般使用者的網站,沒有加前端驗證可能會加大使用者註冊跑路率。
3.前端驗證方式:
1)目前主流的Web框架已經集成了前後端驗證功能,如:Asp.net mvc,PHP 的Yii 等,只要定好驗證規則,前端驗證程式碼就自動生成好,後端驗證也很方便。
2)前端驗證程式碼除非特殊情況或以學習練習為目的,就不要再自己一個個寫了,因為實際開發的時候是根本沒時間讓你慢慢自個去寫的。
3)真正工作時使用一般使用: jquery.validate.js 外掛才能體會到前端驗證的酸爽,通過外掛可以使用一些自帶的驗證方式,也可以自定義驗證規則
4.如果還不會使用jquery.validate.js可以去看我部落格的另一篇文章《jquery.validate.js的使用教程》,一定會讓你受益匪淺的
---------------------
後端校驗比前端校驗更安全,更可靠,前端校驗可以增加使用者體驗,一般來說,在前端校驗的東西在後端也必須校驗(比如登陸使用者名稱、密碼),有些東西在前端就可以校驗,比如:字串長度、郵箱格式、手機號碼等等,沒必要提交到後端,增加伺服器的壓力,正常情況下,前端校驗的東西,最好後端都在校驗一次。
放到後端校驗的,常見的與資料庫有關,比如輸入重複之類的,需要先查詢資料庫才知道,當然還有其他的一些東西。
//java檢測是否為電話號碼(手機、固定電話驗證)
String legalPhone = "";
String regExp ="^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}|[0]{1}[0-9]{2,3}-[0-9]{7,8}$";
Pattern p = Pattern.compile(regExp);
Matcher m = p.matcher(importPotentialBFOs[i].getLegalPhone());
if(m.find()){ //注意:m.find只能用一次,第二次呼叫後都為false
legalPhone = importPotentialBFOs[i].getLegalPhone();
uploadTmp.setLegalTelephone(legalPhone);
}else{
throw new BizException("聯絡電話格式錯誤!");
}
---------------------
引數驗證是一個常見的問題,無論是前端還是後臺,都需對使用者輸入進行驗證,以此來保證系統資料的正確性。對於web來說,有些人可能理所當然的想在前端驗證就行了,但這樣是非常錯誤的做法,前端程式碼對於使用者來說是透明的,稍微有點技術的人就可以繞過這個驗證,直接提交資料到後臺。無論是前端網頁提交的介面,還是提供給外部的介面,引數驗證隨處可見,也是必不可少的。總之,一切使用者的輸入都是不可信的。
引數驗證有許多種方式進行,下面以mvc為例,列舉幾種常見的驗證方式,假設有一個使用者註冊方法
[HttpPost]
public ActionResult Register(RegisterInfo info)
一、通過 if-if 判斷
1 2 3 4 5 6 7 8 |
if
(
string
.IsNullOrEmpty(info.UserName))
{
return
FailJson(
"使用者名稱不能為空"
);
}
if
(
string
.IsNullOrEmpty(info.Password))
{
return
FailJson(
"使用者密碼不能為空"
)
}
|
逐個對引數進行驗證,這種方式最粗暴,但當時在WebForm下也確實這麼用過。對於引數少的方法還好,如果引數一多,就要寫n多的if-if,相當繁瑣,更重要的是這部分判斷沒法重用,另一個方法又是這樣判斷。
二、通過 DataAnnotation
mvc提供了DataAnnotation對Action的Model進行驗證,說到底DataAnnotation就是一系列繼承了ValidationAttribute的特性,例如RangeAttribute,RequiredAttribute等等。ValidationAttribute 的虛方法IsValid 就是用來判斷被標記的物件是否符合當前規則。asp.net mvc在進行model binding的時候,會通過反射,獲取標記的ValidationAttribute,然後呼叫 IsValid 來判斷當前引數是否符合規則,如果驗證不通過,還會收集錯誤資訊,這也是為什麼我們可以在Action裡通過ModelState.IsValid判斷Model驗證是否通過,通過ModelState來獲取驗證失敗資訊的原因。例如上面的例子:
1 2 3 4 5 6 7 8 |
public
class
RegisterInfo
{
[Required(ErrorMessage=
"使用者名稱不能為空"
)]
public
string
UserName{
get
;
set
;}
[Required(ErrorMessage=
"密碼不能為空"
)]
public
string
Password {
get
;
set
; }
}
|
事實上在webform上也可以參照mvc的實現原理實現這個過程。這種方式的優點的實現起來非常優雅,而且靈活,如果有多個Action共用一個Model引數的話,只要在一個地方寫就夠了,關鍵是它讓我們的程式碼看起來非常簡潔。
不過這種方式也有缺點,通常我們的專案可能會有很多的介面,比如幾十個介面,有些介面只有兩三個引數,為每個介面定義一個類包裝引數有點奢侈,而且實際上為這個類命名也是非常頭疼的一件事。
三、DataAnnotation 也可以標記在引數上
通過驗證特性的AttributeUsage可以看到,它不只可以標記在屬性和欄位上,也可以標記在引數上。也就是說,我們也可以這樣寫:
1 |
public
ActionResult Register([Required(ErrorMessage=
"使用者名稱不能為空"
)]
string
userName, [Required(ErrorMessage=
"密碼不能為空"
)]
string
password)
|
這樣寫也是ok的,不過很明顯,這樣寫很方法引數會難看,特別是在有多個引數,或者引數有多種驗證規則的時候。
四、自定義ValidateAttribute
我們知道可以利用過濾器在mvc的Action執行前做一些處理,例如身份驗證,授權處理的。同理,這裡也可以用來對引數進行驗證。FilterAttribute是一個常見的過濾器,它允許我們在Action執行前後做一些操作,這裡我們要做的就是在Action前驗證引數,如果驗證不通過,就不再執行下去了。
定義一個BaseValidateAttribute基類如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public
class
BaseValidateAttribute : FilterAttribute
{
protected
virtual
void
HandleError(ActionExecutingContext context)
{
for
(
int
i = ValidateHandlerProviders.Handlers.Count; i > 0; i--)
{
ValidateHandlerProviders.Handlers[i - 1].Handle(context);
if
(context.Result !=
null
)
{
break
;
}
}
}
}
|
HandleError 用於在驗證失敗時處理結果,這裡ValidateHandlerProviders提過IValidateHandler用於處理結果,它可以在外部進行註冊。IValidateHandler定義如下:
1 2 3 4 |
public
interface
IValidateHandler
{
void
Handle(ActionExecutingContext context);
}
|
ValidateHandlerProviders定義如下,它有一個預設的處理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public
class
ValidateHandlerProviders
{
public
static
List<IValidateHandler> Handlers {
get
;
private
set
; }
static
ValidateHandlerProviders()
{
Handlers =
new
List<IValidateHandler>()
{
new
DefaultValidateHandler()
};
}
public
static
void
Register(IValidateHandler handler)
{
Handlers.Add(handler);
}
}
|
這樣做的目的是,由於我們可能有很多具體的ValidateAttribute,可以把這模組獨立開來,而把最終的處理過程交給外部決定,例如我們在專案中可以定義一個處理器:
1 2 3 4 5 6 7 8 9 10 |
public
class
StanderValidateHandler : IValidateHandler
{
public
void
Handle(ActionExecutingContext filterContext)
{
filterContext.Result =
new
StanderJsonResult()
{
Result = FastStatnderResult.Fail(
"引數驗證失敗"
, 555)
};
}
}
|
然後再應用程式啟動時註冊:ValidateHandlerProviders.Handlers.Add(new StanderValidateHandler());
ValidateNullttribute:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public
class
ValidateNullAttribute : BaseValidateAttribute, IActionFilter
{
public
bool
ValidateEmpty {
get
;
set
; }
public
string
Parameter {
get
;
set
; }
public
ValidateNullAttribute(
string
parameter,
bool
validateEmpty =
false
)
{
ValidateEmpty = validateEmpty;
Parameter = parameter;
}
public
void
OnActionExecuting(ActionExecutingContext filterContext)
{
string
[] validates = Parameter.Split(
','
);
foreach
(
var
p
in
validates)
{
string
value = filterContext.HttpContext.Request[p];
if
(ValidateEmpty)
{
if
(
string
.IsNullOrEmpty(value))
{
base
.HandleError(filterContext);
}
}
else
{
if
(value ==
null
)
{
base
.HandleError(filterContext);
}
}
}
}
public
void
OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
|
ValidateRegexAttribute:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public
class
ValidateRegexAttribute : BaseValidateAttribute, IActionFilter
{
private
Regex _regex;
public
string
Pattern {
get
;
set
; }
public
string
Parameter {
get
;
set
; }
public
ValidateRegexAttribute(
string
parameter,
string
pattern)
{
_regex =
new
Regex(pattern);
Parameter = parameter;
}
public
void
OnActionExecuting(ActionExecutingContext filterContext)
{
string
[] validates = Parameter.Split(
','
);
foreach
(
var
p
in
validates)
{
string
value = filterContext.HttpContext.Request[p];
if
(!_regex.IsMatch(value))
{
base
.HandleError(filterContext);
}
}
}
public
void
OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
|
更多的驗證同理實現即可。
這樣,我們上面的寫法就變成:
1 2 |
[ValidateNull(
"userName,password"
)]
public
ActionResult Register(
string
userName,
string
password)
|
綜合看起來,還是ok的,與上面的DataAnnotation可以權衡選擇使用,這裡我們可以擴充套件更多有用的資訊,如錯誤描述等等。
總結
當然每種方式都有有缺點,這個是視具體情況選擇了。一般引數太多建議就用一個物件包裝了。