【发布时间】:2015-10-30 13:12:31
【问题描述】:
我的应用程序中的 Bean 验证运行良好。现在我想检查一个新用户没有选择已经选择的用户名。
在 actionlistener 中,我有检查数据库的代码,但是如果用户选择了一个已经存在的用户名,我该如何强制将用户发送回他们所在的页面?
【问题讨论】:
-
我认为你不应该首先重定向到另一个页面,除非用户名是有效的。
标签: validation jsf bean-validation
我的应用程序中的 Bean 验证运行良好。现在我想检查一个新用户没有选择已经选择的用户名。
在 actionlistener 中,我有检查数据库的代码,但是如果用户选择了一个已经存在的用户名,我该如何强制将用户发送回他们所在的页面?
【问题讨论】:
标签: validation jsf bean-validation
您可以做到这一点,但是 JSF ajax/action/listener 方法在语义上是错误进行验证的地方。如果您在表单中输入错误的值,您实际上并不想在 JSF 生命周期中走得那么远。您希望 JSF 生命周期在 JSF 验证阶段之后停止。
您想使用 JSR303 Bean 验证注释(@NotNull 和朋友)和/或约束验证器,或使用 JSF Validator(required="true"、<f:validateXxx> 等)代替。它将在 JSF 验证阶段正确调用。这样,当验证失败时,模型值不会更新,也不会调用业务操作,而您会停留在同一页面/视图中。
由于没有标准的 Bean Validation 注释或 JSF Validator 来检查给定的输入值是否根据数据库是唯一的,因此您需要为此自行开发一个自定义验证器。
我将通过两种方式展示如何创建一个自定义验证器来检查用户名的唯一性。
首先创建一个自定义的@Username约束注解:
@Constraint(validatedBy = UsernameValidator.class)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Username {
String message() default "Username already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
使用此约束验证器(注意:ConstraintValidator 内的 @Inject 或 @Inject 仅从 CDI 1.1 开始有效;因此,如果您仍在使用 CDI 1.0,则需要从 JNDI 手动获取它):
public class UsernameValidator implements ConstraintValidator<Username, String> {
@EJB
private UserService service;
@Override
public void initialize(Username constraintAnnotation) {
// If not on CDI 1.1 yet, then you need to manually grab EJB from JNDI here.
}
Override
public boolean isValid(String username, ConstraintValidatorContext context) {
return !service.exist(username);
}
}
最后在模型中使用如下:
@Username
private String username;
另一种方法是使用自定义 JSF 验证器。只需实现 JSFValidator 接口即可:
@ManagedBean
@RequestScoped
public class UsernameValidator implements Validator {
@EJB
private UserService userService;
@Override
public void validate(FacesContext context, UIComponent component, Object submittedAndConvertedValue) throws ValidatorException {
String username = (String) submittedAndConvertedValue;
if (username == null || username.isEmpty()) {
return; // Let required="true" or @NotNull handle it.
}
if (userService.exist(username)) {
throw new ValidatorException(new FacesMessage("Username already in use, choose another"));
}
}
}
最后在view中使用如下:
<h:inputText id="username" ... validator="#{usernameValidator}" />
<h:message for="username" />
请注意,您通常会在 Validator 类上使用 @FacesValidator 注释,但在即将到来的 JSF 2.3 之前,它不支持 @EJB 或 @Inject。另见How to inject in @FacesValidator with @EJB, @PersistenceContext, @Inject, @Autowired。
【讨论】:
null 或为空,则不会执行required 以外的JSF 验证。所以如果你有一个@NotNull 没有required="true",那么它将首先被执行。但如果它不为空并且您有例如@Pattern,则将首先调用 JSF 验证器。
是的,你可以。您可以在动作侦听器方法中进行验证,如果您的自定义验证失败,则添加面孔消息,然后在返回之前调用FacesContext.validationFailed()。
这个解决方案的唯一问题是,它发生在 JSF 验证和 bean 验证之后。即,它在验证阶段之后。如果您有多个动作侦听器,例如 listener1 和 listener2:如果您在 listener1 中的自定义验证失败,它将继续执行 listener2。但毕竟,你会在 AJAX 响应中得到 validationFailed。
【讨论】:
facesContext.isValidationFailed() 继续。
为此目的,最好使用 action 方法而不是 actionListener。然后,如果用户名存在,您可以从此方法返回null(重新加载触发该操作的页面)。这是一个例子:
在facelet中:
<h:commandButton action="#{testBean.doAction}" value="and... Action"/>
在 bean 中:
public String doAction() {
if (userExists) {
return null;
} else {
// go on processing ...
}
}
【讨论】:
如果您想向最终用户提供反馈:
xhtml:
<p:commandButton value="Go" process="@this" action="#{myBean.checkEntity()}" oncomplete="if(args.validationFailed){PF('widgetOldInfoNotice').show();}"/>
<p:confirmDialog id="dialogOldInfoNotice" header="NOTICE" severity="alert" widgetVar="widgetOldInfoNotice">
-- feedback message--
<p:button value="Ok" onclick="PF('widgetOldInfoNotice').hide();"/>
</p:confirmDialog>
豆子:
public String checkEntity() {
if (!dao.whateverActionToValidateEntity(selectedEntity)) {
FacesContext context = FacesContext.getCurrentInstance();
context.validationFailed();
return "";
}
return "myPage.xhtml";
}
【讨论】:
您可以在 faces-config.xml 文件中定义导航案例。这将允许您根据 bean 的返回值将用户重定向到给定页面。
在下面的示例中,根据“myMethod()”的返回值将用户重定向到两个页面之一。
<navigation-rule>
<from-view-id>/index.xhtml</from-view-id>
<navigation-case>
<from-action>#{myBean.myMethod()}</from-action>
<from-outcome>true</from-outcome>
<to-view-id>/correct.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-action>#{myBean.myMethod()}</from-action>
<from-outcome>false</from-outcome>
<to-view-id>/error.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
【讨论】: