5.1. 在play里验证如何进行的?
每个请求都有他自己的Validation(验证)对象和相应的错误集合。有以下三种方式来定义验证。
- 在一个控制器方法,可以直接调用控制器的validation属性的方法。也可使用play.data.validation.Validation类的静态方法来访问API子集。
- 在控制器的方法参数上使用注释声明来进行验证。
- 为一个action方法的POJO参数添加@Valid注释来验证POJO属性。
验证对象负责维护play.data.validation.Error对象集合。每个error都有两个属性:- key:用于确定是哪个元素导致的错误。当play发生错误时,key值可以任意设置,它遵循java变量默认命名约定。
- message:其内容包含了错误的文本描述。message可以是纯文本消息,也可以是key的消息绑定(特别是国际化支持)。
下面我们使用第一种方式来验证一个简单的http参数:
public static void hello(String name) {
validation.required(name);
...
}
此代码用于检测name变量是否正确设置。如果不正确,相应的错误消息将会增加到当前错误集合里。
如果需要,可以为每一个需要验证的变更重复这个操作:
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
...
}
5.2. 验证的错误消息
最后我们可以检索出错误信息并显示出来:
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
if(validation.hasErrors()) {
for(Error error : validation.errors()) {
System.out.println(error.message());
}
}
}
假如name和age都为null,这时将显示:
Required
Required
这是因为在$PLAY_HOME/resources/messages里设置的默认消息如下:
validation.required=Required
可通过以下三种方式定制验证消息:
- 重写应用程序的messages文件,对其中的消息进行重新定义。
- 提供一个定制消息作为附加的验证参数。
- 为局部消息提供一个消息key作为附加的验证参数。
Localised validation messages 局部验证消息
最简单的方式就是重写messages文件内容:
validation.required = Please enter a value
也可提供其他语言的区位,详见Internationalization。
验证消息参数
在消息里为错误key使用占位符:
validation.required=%s is required
输出为:
name is required
age is required
缺点:当超过一个必须字体验证时(使用validation.requied(age)),play会发生不能确定当前的参数名称语法错误。在这种情况下,就必须直接指定域名称,比如:
validation.required("age", age);
错误key默认就是参数名称。比如在hello action里的name参数可以限定如下:
name = Customer name
输出为:
Customer name is required
age is required
也可使用error.message(String key)方法重载错误key:
Error error = validation.required(name).error;
if(error != null) {
System.out.println(error.message("Customer name"));
}
许多内建的验证定义附加消息参数都适用于验证参数。比如,match验证为指定的正则表达式定义了第2个字符串参数,这与%s
占位符不同:
validation.match=Must match %2$s
与此相似,range验证定义了两个附加的数字参数2和3:
validation.range=Not in the range %2$d through %3$d
查看一下$PLAY_HOME/resources/messages
文件也了解更多的验证参数。
定制局部验证消息
$PLAY_HOME/resources/messages
使用默认的key为每个play内建的验证定义了验证信息。你可以指定不同的消息key,比如:
validation.required.em = You must enter the %s!
为消息使用新的消息key,就是手工在action方法里进行验证:
validation.required(manualKey).message("validation.required.em");
另外一种方式就是在注释的message参数里使用key:
public static void hello(@Required(message="validation.required.em") String name) {
...
}
使用同样的技术可以用于验证JavaBean的属性:
public static void hello(@Valid Person person) {
...
}
public class Person extends Model {
@Required(message = "validation.required.emphasis")
public String name;
...
}
定制teral(非局部)验证消息
如果没有为key定义错误消息,那么play只返回消息的key名称,也就是说只需使用文字消息代替消息key。上面的示例可修改为:
validation.required(manualKey).message("Give us a name!");
action方法参数注释:
public static void save(@Required(message = "Give us a name!") String name) {
...
}
JavaBean属性注释:
public static void save(@Valid Person person) {
...
}
public class Person extends Model {
@Required(message = "Give us a name!")
public String name;
...
}
5.3. 在模板里显示验证错误消息
很多情况下都需要在视图模板里显示错误消息。在模板里使用errors对象可以访问这些错误消息,一些标签专用于显示错误消息:
示例:
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
render(name, age);
}
模板代码:
#{ifErrors}
<h1>Oops...</h1>
#{errors}
<li>${error}</li>
#{/errors}
#{/ifErrors}
#{else}
Hello ${name}, you are ${age}.
#{/else}
在真实的应用程序里可能需要显示真实的窗体,因此就需要使用两个action:一个用于显示窗体,另一个用于处理POST。
当然,第二个action用于验证操作,当错误发生时就必须直接转向第一个Action。在这种情况下,在跳转期间就需要一个特定的技巧来保存错误消息。使用validation.keep()方法将为下一个action保存错误集合。
示例:
public class Application extends Controller {
public static void index() {
render();
}
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
if(validation.hasErrors()) {
params.flash(); //把http参数添加到flash作用域
validation.keep(); // 为下一个请求保存错误消息
index();
}
render(name, age);
}
}
view/Application/index.html模板代码:
#{ifErrors}
<h1>Oops„</h1>
#{errors}
<li>${error}</li>
#{/errors}
#{/ifErrors}
#{form @Application.hello()}
<div>
Name: <input type="text" name="name" value="${flash.name}" />
</div>
<div>
Age: <input type="text" name="age" value="${flash.age}" />
</div>
<div>
<input type="submit" value="Say hello" />
</div>
#{/form}
当错误发生时,为每个域显示错误消息以获得更好的用户体验:
#{ifErrors}
<h1>Oops„</h1>
#{/ifErrors}
#{form @Application.hello()}
<div>
Name: <input type="text" name="name" value="${flash.name}" />
<span class="error">#{error 'name' /}</span>
</div>
<div>
Age: <input type="text" name="age" value="${flash.age}" />
<span class="error">#{error 'age' /}</span>
</div>
<div>
<input type="submit" value="Say hello" />
</div>
#{/form}
5.4. 验证注释
在play.data.validation包里提供的注释语句来给每个Validation对象的方法进行注释进行强制验证,更为简洁。使用验证注释,只需要为控制器方法的参数进行注释即可:
public static void hello(@Required String name, @Required @Min(0) Integer age) {
if(validation.hasErrors()) {
params.flash(); //把http参数添加到flash作用域
validation.keep(); //为下一个请求保存错误消息
index();
}
render(name, age);
}
5.5. 验证复杂对象
使用验证注释,可以应用于模型对象的属性,之后在控制器里所有的属性都必须是有效的。让我们重写一下User类。
对User类的属性使用验证注释:
package models;
public class User {
@Required
public String name;
@Required
@Min(0)
public Integer age;
}
修改hello方法,使用 @Valid注释来指定所有的User对象都必须是有效的:
public static void hello(@Valid User user) {
if(validation.hasErrors()) {
params.flash(); //把http参数添加到flash作用域
validation.keep(); //为下一个请求保存错误消息
index();
}
render(name, age);
}
修改后的窗体代码:
#{ifErrors}
<h1>Oops„</h1>
#{/ifErrors}
#{form @Application.hello()}
<div>
Name: <input type="text" name="user.name" value="${flash['user.name']}" />
<span class="error">#{error 'user.name' /}</span>
</div>
<div>
Age: <input type="text" name="user.age" value="${flash['user.age']}" />
<span class="error">#{error 'user.age' /}</span>
</div>
<div>
<input type="submit" value="Say hello" />
</div>
#{/form}
5.6. 内建验证
play.data.validation 包包含了许多内建的验证 built-in validations,即可以使用Validation对象,也可使用注释。
5.7. 使用@CheckWith定制验证
使用@CheckWith注释可以用来绑定自己Check验证实现。
示例:
public class User {
@Required
@CheckWith(MyPasswordCheck.class)
public String password;
static class MyPasswordCheck extends Check {
public boolean isSatisfied(Object user, Object password) {
return notMatchPreviousPasswords(password);
}
}
}
默认的验证错误消息key是validation.invalid,要使用不同的key,需要调用Check.setMessage方法(带一个消息key和一个消息参数)。
static class MyPasswordCheck extends Check {
public boolean isSatisfied(Object user, Object password) {
final Date lastUsed = dateLastUsed(password);
setMessage("validation.used", JavaExtensions.format(lastUsed));
return lastUsed == null;
}
}
消息总是查找第一个参数的名称作为field名称,把随后的消息参数作为子参数,因此,上面的示例可以定义如下:
validation.used = &{%1$s} already used on date %2$s
user.password = Password
当&{%1$s}
使用第1个位置(field名称)作为消息key来进行消息查找时,%2$s
作为该key的值(格式化后的日期)。
消息语法%s
, %s2$s
和 &{...}
详见 Retrieve localized messages 。
5.8. 定制注释
也可定制自己的验证注释,虽然要复杂一点,但可以使模型代码更简洁,而且可以引入验证参数。
比如,假定我们打算对URL进行验证(使用@URL),通过带参确定指定的URL允许通过。
首先,需要重写默认的消息,定制一个带参的验证注释:
import net.sf.oval.configuration.annotation.Constraint;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Constraint(checkWith = URICheck.class)
public @interface URI {
String message() default URICheck.message;
}
这个注释参考net.sf.oval.configuration.annotation.AbstractAnnotationCheck实现。
public class URICheck extends AbstractAnnotationCheck<URI> {
/** Error message key. */
public final static String message = "validation.uri";
/** URI schemes allowed by validation. */
private List<String> schemes;
@Override
public void configure(URI uri) {
setMessage(uri.message());
this.schemes = Arrays.asList(uri.schemes());
}
/**
* Add the URI schemes to the message variables so they can be included
* in the error message.
*/
@Override
public Map<String, String> createMessageVariables() {
final Map<String, String> variables = new TreeMap<String, String>();
variables.put("2", JavaExtensions.join(schemes, ", "));
return variables;
}
@Override
public boolean isSatisfied(Object validatedObject, Object value,
OValContext context, Validator validator) throws OValException {
requireMessageVariablesRecreation();
try {
final java.net.URI uri = new java.net.URI(value.toString());
final boolean schemeValid = schemes.contains(uri.getScheme());
return schemes.size() == 0 || schemeValid;
} catch (URISyntaxException e) {
return false;
}
}
}
在渲染消息之前,静态方法调用 requireMessageVariablesRecreation() 来指示 OVal 去调用 createMessageVariables() 。返回一个整齐的变量map来传递消息格式。map keys 并不被使用;在这个例子中,”2”表示该消息参数索引。像前面一样,第一个参数是字段名称。
在模型里使用:
public class User {
@URI(message = "validation.uri.schemes", schemes = {"http", "https"})
public String profile;
}
消息定义如下:
validation.uri = Not a valid URI
validation.uri.schemes = &{%1$s} is not a valid URI - allowed schemes are %2$s