Как выполнить проверку JSF в actionListener или методе действий?

У меня есть Bean валидация, хорошо работающая в моем приложении. Теперь я хочу проверить, что новый пользователь не выбирает имя пользователя, которое уже было выбрано.

В actionlistener у меня есть код, который проверяет базу данных, но как заставить пользователя быть отправленным обратно на страницу, на которой они были, если они выбирают уже существующее имя пользователя?

Ответ 1

Вступление

Вы можете сделать это, но методы JSF ajax/action/listener семантически не подходят для проверки. Вы на самом деле не хотите заходить так далеко в жизненном цикле JSF, если вы неправильно указали значения в форме. Вы хотите, чтобы жизненный цикл JSF остановился после фазы проверки JSF.

Вы хотите использовать аннотацию JSR303 Bean Validation (@NotNull и друзья) и/или валидатор ограничений, или использовать вместо этого JSF Validator (required="true", <f:validateXxx> и т.д.). Он будет правильно вызываться на этапе проверки JSF. Таким образом, при сбое проверки значения модели не обновляются, и бизнес-действие не вызывается, и вы остаетесь на той же странице/представлении.

Поскольку нет стандартной аннотации Bean Validation или JSF Validator для проверки того, является ли заданное входное значение уникальным в соответствии с базой данных, вам необходимо создать собственный валидатор для этого.

В обоих случаях я покажу, как создать собственный валидатор, который проверяет уникальность имени пользователя.

Пользовательская аннотация аннотации bean-компонента JSR303

Сначала создайте пользовательскую аннотацию ограничения @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 {};
}

С этим валидатором ограничений (примечание: @EJB или @Inject внутри ConstraintValidator работает только с 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 Validator

Альтернативой является использование собственного валидатора JSF. Просто реализуйте интерфейс JSF Validator:

@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"));
        }
    }

}

Наконец, используйте его следующим образом:

<h:inputText id="username" ... validator="#{usernameValidator}" />
<h:message for="username" />

Обратите внимание, что вы обычно используете аннотацию @FacesValidator для класса Validator, но до следующей версии JSF 2.3 она не поддерживает @EJB или @Inject. Смотрите также Как сделать инъекцию в @FacesValidator с помощью @EJB, @PersistenceContext, @Inject, @Autowired.

Ответ 2

Да, вы можете. Вы можете сделать валидацию в методе слушателя действий, добавить сообщения лиц, если ваша пользовательская проверка не удалась, затем вызовите FacesContext.validationFailed() непосредственно перед возвратом.

Единственная проблема с этим решением заключается в том, что это происходит после проверки JSF и проверки bean. I.e., это после фазы валидации. Если у вас несколько слушателей действий, скажем, listener1 и listener2: если ваша пользовательская проверка в listener1 не удалась, она продолжит выполнение listener2. Но в конце концов вы получите validationFailed в ответе AJAX.

Ответ 3

Для этой цели лучше использовать метод действий вместо actionListener. Затем вы можете вернуть null (перезагружает страницу, вызвавшую действие) из этого метода, если имя пользователя существует. Вот пример:

в лице:

<h:commandButton action="#{testBean.doAction}" value="and... Action"/>

в bean:

public String doAction() {
   if (userExists) {
     return null;
   } else {
     // go on processing ...
   }
}

Ответ 4

Если вы хотите оставить отзыв конечному пользователю:

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";
}

Ответ 5

Вы можете определить случай навигации в файле faces-config.xml. Это позволит вам перенаправить пользователя на заданную страницу в зависимости от возвращаемого значения bean.

В приведенном ниже примере suer перенаправляется на одну из двух страниц в зависимости от возвращаемого значения "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>