前台和后台驗證(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可以權衡選擇使用,這里我們可以擴展更多有用的信息,如錯誤描述等等。
總結
當然每種方式都有有缺點,這個是視具體情況選擇了。一般參數太多建議就用一個對象包裝了。