This page looks best with JavaScript enabled

ASP.NET MVC – Patrón PRG sin sesión

 ·  ☕ 5 min  ·  ✍️ eiximenis

Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí

Buenas! El patrón PRG (Post – Redirect – Get) es un patrón muy usado en el desarrollo web. Consiste en que la respuesta de una petición POST es siempre una redirección, lo que genera un GET del navegador y de ahí el nombre.

La idea que subyace tras el patrón PRG es, que dado que dado que las peticiones GET son (¡deberían ser!) idempotentes esas son las únicas que el usuario debe poder refrescar. De hecho los navegadores nos avisan si refrescamos una petición POST:

image

La razón de este aviso no es tanto notificar al usuario que se reenviarán esos datos, la razón es que el hecho de que se envíen via POST hace que se asuma que dicha petición no es idempotente, o dicho de otro modo modifica datos en el sistema (da de alta un usuario, o borra un producto o realiza una compra). Es pues un mecanismo de protección.

Para evitar esto en el patrón PRG cada método de acción que gestiona un POST, no devuelve la vista con el resultado de dicha petición si no que devuelve una redirección a otra acción que es la que muestra el resultado. Así si tenemos una vista que tiene un formulario como el siguiente:

@using (Html.BeginForm())
{
    @Html.TextBox("somevalue")
    <input type="submit" value="send post" />
}

El método de acción podría ser algo como:

[HttpPost]
public ActionResult Index(string somevalue)
{
    // Procesar resultados.
    var id = new Random().Next();
    // ...
    return RedirectToAction("View", new { id = id });
}

El método que procesa el POST realiza las tareas que sean necesarias y luego redirecciona a otra acción, por lo que lo después de enviar el formulario el usuario refresca la página, refrescará la última petición web que es la petición GET (en lugar de intentar refrescar el POST).

Usar el patrón PRG es una muy buena práctica, pero conlleva un pequeño problema: como pasar información desde la acción POST hacia la acción GET.

P. ej. en el POST podemos crear o modificar datos de alguna entidad y luego en el GET podemos mostrar esa entidad creada o modificada. Una solución es pasarle el ID de la entidad (tal y como se hace en el código anterior). Eso es perfectamente válido pero implica un round-trip a la BBDD. En el POST teníamos los datos de toda la entidad, pero en el GET debemos recuperarlos de nuevo ya que solo tenemos el ID.

Si hubiese alguna manera de pasar todos los datos necesarios (p. ej. toda la entidad) des del POST hacía el GET entonces, en algunos casos, nos podríamos ahorrar tener que ir a buscar datos que ya teníamos.

En ASP.NET MVC existe un mecanismo pensado para transferir datos entre redirecciones, que es justo lo que necesitamos y es TempData:

[HttpPost]
public ActionResult Index(string somevalue)
{
    // Procesar resultados.
    var id = new Random().Next();
    var someData = new Person() { 
       Id = id, Name = somevalue 
     };
    TempData["someData"] = someData;
    return RedirectToAction("View", 
          new { id = id });
}
 
[ActionName("View")]
public ActionResult ViewGet(string id)
{
    var data = TempData["someData"] as Person;
    if (data == null)
    {
        // Recargar data usando el id que 
        //tenemos por parámetro
    }
    return View(data);
}

Fíjate que usar TempData no exime de pasar el id igualmente a la acción GET ya que los datos que recogemos de TempData pueden no existir. Si el usuario refresca la página o bien teclea directamente la URL de la acción View los datos de TempData no existirán. Recuerda que TempData es un contenedor que permite una sola lectura (los datos desaparecen una vez leídos).

Bien, el punto a tener en cuenta (y motivo principal de este post) al usar TempData es que éste usa sesión. Y no siempre podemos o queremos usar la sesión. Si por cualquier razón no queremos usar la sesión, ¿podemos seguir usando TempData?

La respuesta es que si, pero debes implementarte un custom TempData provider. Claro que la otra pregunta es donde podemos guardar esos datos. Recuerda que TempData debe persistir entre peticiones (de ahí que por defecto se use la sesión). No hay muchos lugares más donde lo podamos guardar, así que si estás pensando en cookies has dado en el clavo. Si en el POST enviamos una cookie, cuando el navegador realice la petición GET posterior reenviará la cookie que contendrá los datos de TempData.

Para crear un proveedor propio de TempData tenemos que seguir 2 pasos:

  1. Crear una clase que implemente ITempDataProvider. Esta interfaz define dos métodos (SaveTempData y LoadTempData).
  2. Redefinir el método CreateTempDataProvider de la clase Controller y devolver una instancia de nuestra clase que implementa ITempDataProvider. Esto debemos hacerlo en cada controlador que queramos que use nuestro TempData provider o bien lo podemos poner en un controlador base.

No voy a colgarme medallas que no me pertenecen poniendo la implementación de un proveedor de TempData que use cookies, ya que hay varios por internet e incluso en el código fuente de MVC4 viene uno. Está en el código fuente pero no en los binarios, ya que forma parte del paquete Mvc4Futures. Si quieres usarlo, debes instalar primero este paquete usando Install-Package Mvc4Futures desde la cónsola de NuGet o bien usando la GUI.

Y ya puedes usar TempData sin necesidad de usar la sesión! 😉

Saludos!

Si quieres, puedes invitarme a un café xD

eiximenis
ESCRITO POR
eiximenis
Compulsive Developer