jsf viewparam 验证错误后丢失

发布于 2024-11-27 01:09:32 字数 653 浏览 1 评论 0原文

我面临以下问题:在一页中,我列出了应用程序的所有用户,并为每个用户提供了一个“编辑”按钮,这是一个带有 ?id=.

编辑页面在元数据中有一个
如果我犯了一些输入错误并提交(我使用 CDI Weld Bean 验证),页面会再次显示,但我丢失了 URL 中的 ?id=... ,因此失去了用户我正在编辑的用户的 id。

我看过 JSF 验证错误,丢失值中描述的类似问题,但是使用 inputhidden 的解决方案(或更糟的是,使用战斧(看起来有点大材小用)需要大量丑陋的代码。

我尝试过使用 CDI 添加“对话”,它正在工作,但对我来说,它看起来又太过分了。

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

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

I'm facing the following issue: in one page, I list all users of my application and have an "edit" button for each one, which is a "GET" link with ?id=<userid>.

The edit page has a <f:viewParam name="id" value="#{editUserBean.id}"/> in metadata.
If I made some input mistakes and submit (I use CDI Weld Bean validation), the page is displayed again, but I've lost the ?id=... in the URL and so lose the user id of the user I'm editing.

I've looked at a similar problem described in JSF validation error, lost value, but the solution with inputhidden (or worse, with tomahawk, which looks overkill) requires lot of uggly code.

I've tried adding a "Conversation" with CDI, and it is working, but it looks like too much overkill to me again.

Does there exists a simple solution in JSF to preserve view parameters in case of validation errors?

[My environment: Tomcat7 + MyFaces 2.1.0 + Hibernate Validator 4.2.0 + CDI(Weld) 1.1.2]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

天生の放荡 2024-12-04 01:09:32

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

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>

Bean:

@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将永久丢失。之后输入有效的内容不会将其恢复。这几乎看起来像是一个错误,但我尝试了 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() 为 true,它将尝试从模型(支持 bean)获取值。但由于该 bean 是请求范围的,因此该请求不会有任何值,因为验证刚刚失败,因此尚未设置任何值。实际上,UIViewParameter 的有状态值会被支持 bean 返回的默认值(通常为 null,但这当然取决于您的 bean)覆盖。

一种解决方法是使您的 bean @ViewScoped ,无论如何,这通常是一个更好的范围(我假设您使用该参数从服务获取用户,并且可能没有必要一遍又一遍地这样做在每次回发时)。

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

Interesting case. For everyone, the following minimal code reproduces this:

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>

Bean:

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

If you call the Facelet with viewparam.xhtml?id=12 it will display the 12 onscreen. If you then input something valid, e.g. aaaaa, the id will disappear from the URL, but keeps being displayed on screen (owning to the stateful nature of ui components).

However... as OP mentioned, as soon as any validator error occurs (e.g. entering a), the id will be permanently lost. Entering valid input afterwards will not bring it back. It almost seems like a bug, but I tried both Mojarra 2.1 and Myfaces 2.1 and both have the same behavior.

Update:

After some inspection, the problem seems to be in this method of `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));
}

And then more specifically this method:

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

Because hasValueExpression() is true, it will try to get the value from the model (the backing bean). But since this bean was request scoped it will not have any value for this request, since validation has just failed and thus no value has ever been set. In effect, the stateful value of UIViewParameter is overwritten by whatever the backing bean returns as a default (typically null, but it depends on your bean of course).

One workaround is to make your bean @ViewScoped, which is often a better scope anyway (I assume you use the parameter to get a user from a Service, and it's perhaps unnecessary to do that over and over again at every postback).

Another alternative is to create your own version of UIViewParameter that doesn't try to get the value from the model if validation has failed (as basically all other UIInput components do).

话少情深 2024-12-04 01:09:32

您实际上并没有丢失视图参数。 f:viewParam 是有状态的,因此即使它不在 URL 中,它仍然存在。只需在绑定到查看参数的 setter 中放置一个断点或 system.out 即可。

(如果你在 viewParam stateless stateful 上搜索,你会找到更多信息)

You don't actually loose the view parameter. f:viewParam is stateful, so even if it's not in the URL, it's still there. Just put a break point or system.out in the setter bound to view param.

(if you google on viewParam stateless stateful you'll find some more info)

安稳善良 2024-12-04 01:09:32

我的应用程序中也有同样的内容。我切换到@ViewAccessScoped,它允许更优雅的实现。

I've the same in my Application. I switched to @ViewAccessScoped which allows way more elegant implementations.

青春如此纠结 2024-12-04 01:09:32
 <f:metadata>
        <f:viewParam id="id" name="id" value="#{baen.id}"/>
    </f:metadata>

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

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

Or when you the first time get parameter from url, save it in session map and continue use from that map, and after save/or update the form clean map.

时光磨忆 2024-12-04 01:09:32

这很棘手,但您可以尝试使用 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>

This is tricky, but you can try to restore view parameters with 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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文