jsf logo

Navegación en JSF y paso de parámetros con f:viewParam y @ManagedProperty

Visión general

A pesar de que existen múltiples formas para el paso de párametros en JSF, por simplicidad y por su uso generalizado nos hemos centrado en el estudio y uso de la etiqueta viewParam y la anotación ManagedProperty.

La etiqueta <f:viewParam> gestiona el establecimiento, conversión y validación de parámetros en JSF, de forma similar a <h:inputText>, ya que extiende de UIInput, pero aplicada a parámetros GET.

Esta etiqueta se usa dentro de la faceta de metadatos de una vista <f:metadata>, lo que hace que un UIViewParameter se adjunte como metadatos para la vista actual.

La otra forma analizada es la anotación ManagedProperty, que puede utilizarse tanto como para la inyección de beans, anotados con @ManagedBean, como para la inyección de propiedades. Con esta herramienta podemos obtener parámetros en JSF desde la vista.

Cada opción tienes sus ventajas e inconvenientes, por lo que su uso debe ser evaluado por su idoneidad. A continuación describimos sus características y presentamos ejemplos de su uso.

Navegación por defecto en JSF

JSF, como framework MVC dirigido aplicaciones basada en formularios, envía el formulario POST a la misma URL donde se solicitó la página con el formulario. Esto se conoce postback. Este tipo de navegación no causa una nueva solicitud a la nueva URL, sino que carga la página de destino como contenido de la respuesta, por lo que la URL no cambia. Esto es realmente confuso cuando simplemente se desea una navegación de página a página.

Eligiendo solicitudes GET y POST

En general, el enfoque correcto en cuanto a la navegación / redirección depende de los requisitos comerciales y la idempotencia de la solicitud.

  • Si la solicitud es idempotente, simplemente se usa un formulario / enlace GET en lugar del formulario POST (es decir, usamos <a><form><h:link> or <h:button> en vez de <h:form><h:commandXxx> ). Por ejemplo, navegación de página a página, formularios de búsquedas simples, etc.
  • Si la solicitud no es idempotente, solo se debería mostrar los resultados condicionalmente en la misma vista (es decir, devolvemos nulo o void en el método de acción y utilizamos, por ejemplo, <h:message> y/o rendered con AJAX. Por ejemplo, entrada / edición de datos en la página, asistente tipo wizard, diálogo modal, formulario de confirmación, etc.
  • Si la solicitud no es idempotente, pero la página de destino es idempotente, simplemente se envía una redirección después de POST (es decir, devolvemos el resultado añadiendo ?faces-redirect = true desde el método de acción. Por ejemplo, mostrar la lista de todos los datos después de una edición exitosa, redirigir después de iniciar sesión, etc.

Navegación página a página

La navegación de página a página generalmente es idempotente y aquí es muchas personas que se inician en JSF fallan al abusar de los componentes <h:commandXxx> para eso y luego se quejan de que las URL no cambian. También tenga en cuenta que los casos de navegación rara vez se usan en aplicaciones del mundo real, ya que se desarrollan teniendo en cuentas las reglas orientadas a SEO / UX. Para generar las peticiones aplicando redirección podríamos declarar un método en nuestra aplicación que hiciera un tratamiento de los vínculos tal como:

    public String createOutcome(final String outcome, final String folder, final boolean redirect) {
        if (redirect) {
            // En caso de redirección se mantienen los mensajes al usuario.
            final ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
            externalContext.getFlash().setKeepMessages(true);
        }

        String result = "";
        if ((folder != null) && (folder.trim().length() > 0)) {
            result = (((folder != null) && (folder.trim().length() > 0)) ? folder + "/" : "");
            while (result.startsWith("/")) {
                result = result.substring(1);
            }
            result = "/" + result;
        }
        result += outcome + ((redirect) ? (getSeparador(outcome) + "faces-redirect=true") : "");        
        return result;
    }

    private String getSeparador(String outcome) {
        String separador = "?";
        if (outcome.contains("?")) {
            separador = "&";
        }
        return separador;
    }

Seguridad de las peticiones GET y POST

A pesar de lo que se suele comentar, el uso de peticiones POST no es absolutamente «más seguro» que GET porque los parámetros de solicitud no son visibles de inmediato en la URL. Todavía son visibles en el cuerpo de la solicitud HTTP y aún son manipulables. No hay absolutamente ninguna razón de peso para preferir POST para solicitudes idempotentes en cuestiones realativas a seguridad. La seguridad real está en usar HTTPS en lugar de HTTP y verificar los métodos del modelo de negocio y verificar si el usuario actualmente conectado puede consultar la entidad X, o manipular la entidad X, etc.

<f:viewParam> vs @ManagedProperty

Cuál es la diferencia entre definir viewParams como:

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

O definir ManagedProperty en el bean tal como:

@ManagedProperty(value = "#{param.id}")
private Integer id;

Etiqueta <f:viewParam>

  • Establece el valor solamente durante la fase de actualización del modelo (update phase).
  • El valor a establecer no está disponible durante la ejecución de @PostConstruct, por lo que se necesita un evento adicional <f:event type="preRenderView" listener="myBean.initMethod" /> dentro del <f:metadata>.
  • Permite <f:converter> y <f:validator> anidados, así como un <h:message>.
  • Pueden ser incluidos en una petición GET usando el atributo includeViewParams de <h:link> o añadiendo includeViewParams=true en cualquier URL.
  • Puede ser utilizado con un ámbito de @RequestScope pero requiere que el bean sea @ViewScope si se pretende que los parámetros se mantengan en caso de fallo de errores de validación en alguno de los formularios de la vista.

Ejemplo:

<f:metadata>
    <f:viewParam id="userId" name="id" value="#{user.id}"
        required="true" requiredMessage="Acceso no permitido. Por favor indique su ID de usuario."
        converter="userConverter" converterMessage="ID de usuario desconocido o no válido."
    />
</f:metadata>
<h:message for="userId" />

Con el bean:

private User user;

Anotación @ManagedProperty

  • Establece el valor inmediatamente después de la construcción del bean.
  • El valor a establecer está disponible durante la ejecución de @PostConstruct, por lo que permite una fácil inicialización de otras propiedades que dependan de este valor.
  • No permite conversiones ni validaciones de forma declarativa en la vista.
  • Las propiedades administradas #{param} no están permitidas en beans con un ámbito mayor que el de solicitud, por lo que se requiere que el bean sea @RequestScoped.
  • Si se quiere garantizar que una propiedad administrada de #{param} esté presente en solicitudes POST posteriores, se debe incluir como <f:param> en los componentes de tipo UICommand.

Ejemplo:

@ManagedProperty("#{param.id}")
private Long id;

private User user;
@ManagedProperty("#{userService}") private UserService userService;@PostConstruct
public void init() {
    user = userService.find(id);
}

viewAction vs preRenderView

Aunque ambas técnicas se utilizan para el mismo fin, realizar evaluaciones antes de que una página sea renderizada, la nueva función viewAction proporciona una serie de ventajas importantes respecto a preRenderView:

  • Las acciones viewAction se disparan desde el principio, antes de que se contruya el árbol de componentes, resultando en una llamada más ligera.
  • Las acciones viewAction se pueden utilizar en el mismo contexto que la petición GET.
  • Las acciones viewAction admiten navegación implícita e explícita.
  • Las acciones viewAction admiten solicitudes «non-faces» (iniciales) y «faces» (postback).

Un caso práctico de acción en fase inicial es una verificación de autorización dependiente del contexto, por ejemplo, cuando un usuario de su aplicación está intentando cargar una página compleja, pero el usuario no está autorizado para ver la página.

Al usar un componente viewAction, es fácil evaluar las credenciales del usuario y navegar en consecuencia. Esta verificación se puede realizar en una fase inicial, en lugar de en la fase de renderización, evitando que ocurran otros efectos secundarios (posiblemente costosos) o impidiendo que se ejecuten procesos innecesarios.

En resumen, el componente viewAction simplifica el proceso para realizar verificaciones condicionales en las solicitudes iniciales y de devolución de datos, permite controlar en qué fase del ciclo de vida se realiza una acción y habilita la navegación tanto implícita como declarativa.

Usos de <f:metadata>, <f:viewParam> y <f:viewAction>

Proceso de parámetros GET

Teniendo una petición GET tal como:

https:/nestoralmeida.com/foo.xhtml?id=10

Como en el ejemplo anterior:

<f:metadata>
    <f:viewParam id="id" name="id" value="#{user.id}" required="true">
        <f:validateLongRange minimum="10" maximum="20" />
    </f:viewParam>
</f:metadata>
<h:message for="id" />

Se hace lo siguiente:

  • Obtener de la request el parámetro con nombre id
  • Convertir y validar el resultado, si aplica.
  • Si la conversión y validación son correctos, se establece el valor en el bean #{user.id}.

Al abrir la página, entonces el valor obtenido para el parámetro id se establece de esta forma, justo antes de que la vista sea renderizada.

Como validación, en el ejemplo se define el parámetro como requerido, con un valor entre 10 y 20. En caso de no cumplir se mostraría un mensaje de validación incorrecta.

Acciones de negocio con parámetros GET

En caso de trabajar con JSF 2.0/2.1 utilizamos el evento preRenderView y si se trabaja con JSF 2.2/2.3 se puede utilizar <f:viewAction>.

<f:metadata>
    <f:viewParam id="id" name="id" value="#{user.id}" required="true">
        <f:validateLongRange minimum="10" maximum="20" />
    </f:viewParam>
        // JSF2.0/2.1
        //<f:event type="preRenderView" listener="#{bean.onload}" />
        <f:viewAction action="#{bean.onload}" />
</f:metadata>
<h:message for="id" />

Con el método onload() en el bean Bean

public void onload() {
    // ...
}

Lanzará el método en cada petición. Será necesario comprobar que no se trate de un postback:

public void onload() {
    if (!FacesContext.getCurrentInstance().isPostback()) {
        // ...
    }
}

También, si se desea ignorar los errores de validación o de conversión, se puede hacer lo siguiente:

public void onload() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    if (!facesContext.isPostback() && !facesContext.isValidationFailed()) {
        // ...
    }
}

Paso de parámetros a la vista siguiente

Se pueden traspasar los parámetros a la vista siguiente añadiendo el atributo includeViewParams=true como parámetro en la solicitud.

<h:link outcome="bar" includeViewParams="true">
<!-- Otra forma -->
<h:link outcome="bar?includeViewParams=true">

Lo que genera el siguiente link:

<a href="bar.xhtml?id=10">

Con el valor original, requiriendo solamente que bar.xhtml tenga también declarado el parámetro con <f:viewParam>, si no, no será pasado.

Resumen

  • Utilizar <f:viewAction> para realizar acciones en las propiedades establecidas por <f:viewParam> si se está utilizando JSF 2.2/2.3.
  • Utilizar preRenderView si se está utilizando JSF 2.0/2.1.
  • Utilizar @PostContruct para realizar acciones en dependencias inyectadas y propiedades administradas @ManagedProperty.
Valoración: 1 estrella2 estrellas3 estrellas4 estrellas5 estrellas (Ninguna valoración todavía)
Cargando...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *