验证错误后jsf viewparam丢失

时间:2011-07-30 20:46:41

标签: jsf jsf-2 cdi jboss-weld

我遇到了以下问题:在一个页面中,我列出了我的应用程序的所有用户,并为每个用户设置了一个“编辑”按钮,这是与?id=<userid>的“GET”链接。

编辑页面的元数据中包含<f:viewParam name="id" value="#{editUserBean.id}"/>
如果我输入了一些输入错误并提交(我使用CDI Weld Bean验证),页面会再次显示,但我丢失了URL中的?id=...,因此丢失了我正在编辑的用户的用户ID

我看过JSF validation error, lost value中描述的类似问题,但是输入隐藏的解决方案(或更糟糕的是,使用tomahawk,看起来有点过分)需要大量的uggly代码。

我尝试在CDI中添加“对话”,但它确实有效,但看起来对我来说太过分了。

在JSF中是否存在一个简单的解决方案,以便在出现验证错误时保留视图参数?

[我的环境:Tomcat7 + MyFaces 2.1.0 + Hibernate Validator 4.2.0 + CDI(Weld)1.1.2]

5 个答案:

答案 0 :(得分:7)

有趣的案例。对于每个人来说,以下最小代码重现了这一点:

的facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
>

    <f:metadata>
        <f:viewParam id="id" name="id" value="#{viewParamBean.id}"/>
    </f:metadata>

    <h:body>

        <h:messages />

        #{viewParamBean.id} <br/>

        <h:form>
            <h:inputText value="#{viewParamBean.text}" >
                <f:validateLength minimum="2"/>
            </h:inputText>

            <h:commandButton value="test" action="#{viewParamBean.actionMethod}"/>
        </h:form>

    </h:body>
</html>

豆:

@ManagedBean
@RequestScoped
public class ViewParamBean {

    private long id;    
    private String text;

    public void actionMethod() {

    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }    
}

如果您使用viewparam.xhtml?id=12调用Facelet,它将在屏幕上显示12。如果您输入有效的内容,例如aaaaa,id将从URL中消失,但会一直显示在屏幕上(拥有ui组件的有状态性质)。

但是......正如OP所提到的,只要发生任何验证器错误(例如输入a),ID就会永久丢失。之后输入有效输入不会带回来。这几乎看起来像一个bug,但我尝试了Mojarra 2.1和Myfaces 2.1,两者都有相同的行为。

<强>更新

经过一番检查,问题似乎出现在这个'UIViewParameter'(Mojarra)的方法中:

public void encodeAll(FacesContext context) throws IOException {
    if (context == null) {
        throw new NullPointerException();
    }

    // if there is a value expression, update view parameter w/ latest value after render
    // QUESTION is it okay that a null string value may be suppressing the view parameter value?
    // ANSWER: I'm not sure.
    setSubmittedValue(getStringValue(context));
}

然后更具体地说这个方法:

public String getStringValue(FacesContext context) {
    String result = null;
    if (hasValueExpression()) {
        result = getStringValueFromModel(context);
    } else {
        result = (null != rawValue) ? rawValue : (String) getValue();
    }
    return result;
}

因为hasValueExpression()为真,它将尝试从模型(支持bean)获取值。但由于这个bean是请求作用域,因此该请求没有任何值,因为验证刚刚失败,因此没有设置任何值。实际上,UIViewParameter的有状态值会被支持bean作为默认值返回的任何内容覆盖(通常为null,但它当然取决于您的bean)。

一个解决方法是制作你的bean @ViewScoped,这通常是一个更好的范围(我假设你使用参数从服务中获取用户,并且可能没有必要一遍又一遍地执行此操作每一次回发)。

另一种方法是创建自己的UIViewParameter版本,如果验证失败(基本上所有其他UIInput组件都没有),则不会尝试从模型中获取值。

答案 1 :(得分:1)

实际上你没有松开view参数。 f:viewParam是有状态的,所以即使它不在URL中,它仍然存在。只需在setter中放置一个断点或system.out来查看param。

(如果您在viewParam无状态状态上google,您会找到更多信息)

答案 2 :(得分:0)

我的申请表中也是如此。我切换到@ViewAccessScoped,它允许更优雅的实现。

答案 3 :(得分:0)

 <f:metadata>
        <f:viewParam id="id" name="id" value="#{baen.id}"/>
    </f:metadata>

或者当您第一次从url获取参数时,将其保存在会话映射中并继续使用该映射,并在保存/或更新表单干净映射后。

答案 4 :(得分:0)

这很棘手,但您可以尝试使用History API恢复视图参数:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <f:metadata >
        <f:viewParam name="param1" value="#{backingBean.viewParam1}" />
        <f:viewParam name="param2" value="#{backingBean.viewParam2}" />
        <f:viewAction action="#{view.viewMap.put('queryString', request.queryString)}" />
    </f:metadata>
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <ui:fragment rendered="#{facesContext.postback}" >  
            <script type="text/javascript">
                var url = '?#{view.viewMap.get('queryString')}';
                history.replaceState({}, document.title, url);
            </script>
        </ui:fragment>
        <h:form>
            <h:inputText id="name" value="#{backingBean.name}" />
            <h:message for="name" style="color: red" />
            <br />
            <h:commandButton value="go" action="#{backingBean.go}" />
        </h:form>
        <h:messages globalOnly="true" />
    </h:body>
</html>