Play 框架手册(5) – 验证http数据


 

验证确保了某些指定的需求能够获得正确的值。一般用于在存入数据库前对数据进行验证或表单验证。

5.1. 在play里验证如何进行的?

每个请求都有他自己的Validation(验证)对象和相应的错误集合。有以下三种方式来定义验证。

  1. 在一个控制器方法,可以直接调用控制器的validation属性的方法。也可使用play.data.validation.Validation类的静态方法来访问API子集。
  2. 在控制器的方法参数上使用注释声明来进行验证。
  3. 为一个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

可通过以下三种方式定制验证消息:

  1. 重写应用程序的messages文件,对其中的消息进行重新定义。
  2. 提供一个定制消息作为附加的验证参数。
  3. 为局部消息提供一个消息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

 


前一篇:
后一篇:

发表评论