ExternalContext#redirect() 与 includeViewParams=true

ExternalContext#redirect() with includeViewParams=true

使用字符串形式的货币列表如下。

<p:selectOneMenu id="currency"
                 value="#{currencyRateBean.currency}"
                 onchange="changeCurrency([{name: 'currency', value: this.value}]);">

    <f:selectItems var="row"
                   value="#{currencyBean.currencies}"
                   itemLabel="#{row}"
                   itemValue="#{row}"/>
</p:selectOneMenu>

沿<p:remoteCommand>.

<p:remoteCommand ignoreAutoUpdate="true"
                 name="changeCurrency"
                 partialSubmit="true"
                 process="@this"
                 update="@none"
                 action="#{currency.currencyAction}"/>

设置货币值的托管 bean 通过上述 <p:remoteCommand> 作为参数传递给 JavaScript 函数。

@Named
@RequestScoped
public class Currency {

    @Inject
    @HttpParam
    private String currency;

    @Inject
    private CurrencyRateBean currencyRateBean;

    public Currency() {}

    public String currencyAction() throws MalformedURLException, IOException {

        try (Scanner scanner = new Scanner(new URL("http://www.exchangerate-api.com/INR/" + currency + "/1?k=FQRxs-xT2tk-NExQj").openConnection().getInputStream(), "UTF-8");) {
            currencyRateBean.setCurrencyRate(scanner.nextBigDecimal());
            currencyRateBean.setCurrency(currency);
        } catch (UnknownHostException | ConnectException e) {}

        return FacesContext.getCurrentInstance().getViewRoot().getViewId() + "?faces-redirect=true&includeViewParams=true";
    }
}

然后将提供的货币值设置为另一个会话范围内的托管 bean CurrencyRateBean,来自上面的操作方法 currencyAction(),最终根据 viewId 的当前值进行重定向以及重要的 includeViewParams=true


现在,情况发生了变化,#{currencyRateBean.currencies} 已更改为具有复合对象列表,而该列表到目前为止一直是字符串列表。

以下情况不适用于 includeViewParams=true,这很重要。

<p:selectOneMenu value="#{currencyRateBean.currencyHolder}">

    <f:selectItems var="row" value="#{currencyBean.currencies}"
                   itemLabel="#{row.currency}"
                   itemValue="#{row}"/>

    <p:ajax event="change"
            listener="#{currency.currencyAction}"
            partialSubmit="true"
            process="@this"
            update="@none"/>
</p:selectOneMenu>
public void currencyAction()  throws IOException {
    // ...
    FacesContext facesContext = FacesContext.getCurrentInstance();
    String viewId = facesContext.getViewRoot().getViewId();

    ExternalContext externalContext = facesContext.getExternalContext();
    externalContext.redirect(externalContext.getRequestContextPath() + viewId + "?includeViewParams=true");
}

includeViewParams=true 已添加,仅用于装饰。这是行不通的。

由于 <p:ajax> 中的 listener 无法像 <p|h:commandXxx>action 那样根据导航案例结果进行重定向,ExternalContext#redirect() 有无论如何都要使用。

<p:remoteCommand> 可用于完成 <p:ajax> 但这将不必要地涉及到服务器的两次往返,首先将货币值设置为关联的托管 bean,然后进行重定向.

如何在给定的示例中使用 includeViewParams=true 进行重定向?

faces-redirect=true 一样,includeViewParams=true 仅适用于导航结果,不适用于您传递给 ExternalContext#redirect() 的 "plain" 网址。

使用NavigationHandler#handleNavigation().

FacesContext context = FacesContext.getCurrentInstance();
String outcome = viewId + "?includeViewParams=true";
context.getApplication().getNavigationHandler().handleNavigation(context, null, outcome);

或者,OmniFaces.

Faces.navigate(viewId + "?includeViewParams=true");

一个可疑的替代方法是自己收集所有视图参数并将它们转换为查询字符串,这样您就可以使用 ExternalContext#redirect()。使用 OmniFaces 更容易。

Faces.redirect(viewId + "?" + Servlets.toQueryString(Faces.getViewParameterMap()));