¿Un ViewState en ASP.NET MVC?

ASP.NET MVC Esta cuestión, lanzada por un amigo de Variable not found en Facebook, comenzaba un pequeño e interesante debate hace algún tiempo, tras la publicación del enlace al post Persisting model state in ASP.NET MVC using Serialize HTMLHelper, en el que se describía la posibilidad de mantener el estado entre peticiones utilizando un mecanismo similar al infame ViewState de Webforms.

Esta herramienta, como otras muchas, se encuentra en el proyecto MvcFutures, un ensamblado distribuido a través de CodePlex, que ha sido creado por el mismo equipo del framework MVC para experimentar nuevas características y funcionalidades futuras del producto.

Su funcionamiento es muy sencillo. Imaginemos el siguiente controlador, que envía a la vista un objeto de tipo User:

public ActionResult Prueba()
{
    return View("MiVista", new User
                                {
                                    Nombre = "José",
                                    FechaNacimiento = DateTime.Now,
                                    Email = "micorreo@correo.com",
                                    Pais = "España",
                                    Provincia = "Sevilla"
                                });
}

Desde la vista correspondiente podemos serializar el objeto del Modelo cuyo estado nos interese preservar, como en el siguiente ejemplo, en el que hacemos persistir las propiedades del objeto User almacenado en la propiedad  Model de la vista:

<% using (Html.BeginForm()) { %>
    <%= Html.Serialize("Userdata", Model) %>
    ... (resto del formulario)
<% } %>

Y ya desde la acción receptora de los datos del formulario, para volver a materializar el objeto serializado bastaría con incluirlo como parámetro de la misma, decorándolo con el atributo [Deserialize] de la siguiente forma:

[HttpPost]
public ActionResult Prueba([Deserialize]User userData, ... ) // Otros parámetros recibidos
{
    // ...
}

Como de costumbre, el nombre del parámetro de la acción debe coincidir con el asignado al campo oculto donde se ha almacenado la información, que coincide con el identificador que hemos indicado ("Userdata") en la llamada al helper en la vista.

El único requisito fundamental para poder obrar esta magia es que la clase y todas sus propiedades sean serializables, como la siguiente:

[Serializable]
public class User
{
    public string Nombre { get; set; }
    public DateTime FechaNacimiento { get; set; }
    public string Pais { get; set; }
    public string Provincia { get; set; }
    public string Email { get; set; }
    public string FacebookUser { get; set; }
    public string TwitterUser { get; set; }
}

¿Y cómo ve ve esto en tiempo de ejecución? El resultado será algo como el siguiente, donde podemos apreciar un cierto tufillo a ViewState:

<form action="/user/prueba" id="form0" method="post">
    <input name="userData" type="hidden" 
        value="/wEy6QIAAQAAAP////8BAAAAAAAAAAwCAAAATk12Y0Z1dHVyZXNTZXJp />
              YWxpemF0aW9uLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhb
              CwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAI012Y0Z1dHVyZXNTZXJpYW
              xpemF0aW9uLk1vZGVscy5Vc2VyBwAAABc8Tm9tYnJlPmtfX0JhY2tpbmd
              GaWVsZCA8RmVjaGFOYWNpbWllbnRvPmtfX0JhY2tpbmdGaWVsZBU8UGFp
              cz5rX19CYWNraW5nRmllbGQaPFByb3ZpbmNpYT5rX19CYWNraW5nRmllb
              GQWPEVtYWlsPmtfX0JhY2tpbmdGaWVsZB08RmFjZWJvb2tVc2VyPmtfX0
              JhY2tpbmdGaWVsZBw8VHdpdHRlclVzZXI+a19fQmFja2luZ0ZpZWxkAQA
              BAQEBAQ0CAAAACgAAAAAAAAAACgoKCgoL"
    ... (resto del formulario)
</form>

Por defecto, el contenido del objeto se volcará en la página utilizando codificación Base64, aunque esto puede modificarse utilizando la sobrecarga del helper Serialize() que permite indicar el tipo de serialización (SerializationMode) a emplear, eligiéndola entre texto plano (por defecto, PlainText o Base64), cifrada, firmada, o firmada y cifrada.

En cualquier caso, se trata de agregar un chorizo de cierto volumen a la página, que la hará más pesada y afectará al rendimiento de nuestro sistema, sobre todo si el objeto que queremos hacer persistir es complejo, por lo que podría pensarse…

¿Estamos trayendo de nuevo el fantasma del ViewState a las aplicaciones MVC?

En mi opinión, no. Y si bien es cierto que estamos utilizando una técnica parecida al ViewState para conservar el estado de objetos, no creo que sea un mal comparable.

El ViewState en Webforms no es una opción si realmente queremos sacar provecho de su potencia; si lo eliminamos totalmente, dejaríamos Webforms como un ASP clásico con esteroides ;-). Aunque ASP.NET 4 ha mejorado mucho el control sobre éste, sigue siendo necesario su uso para mantener el estado de la vista y dar soporte al modelo stateful de esta tecnología.

En ASP.NET MVC, la persistencia de un objeto completo en la página es un caso excepcional; no se me ocurren muchos escenarios en los que pueda sernos útil, y ninguno donde sea la única solución existente. Por ejemplo, si tenemos un proceso que el usuario debe realizar en varios pasos, u otro escenario en el que debamos mantener el estado del Modelo, está claro que debemos hacerlo persistir de algún modo, ya sea en cliente o en servidor.

Como comentaba el amigo Eduard Tomás hace un tiempo, utilizar variables de sesión no es siempre la mejor opción, y a veces hay que recurrir al cliente para almacenar cierta información. Imaginemos, por ejemplo, un asistente para el registro de usuarios en una comunidad on-line; si realmente no nos interesa almacenar dato alguno en el servidor hasta que el usuario confirme su registro en el último paso, una posibilidad bastante interesante sería delegar el almacenamiento temporal, la información introducida en cada uno de los pasos del asistente, a la capa cliente.

Aún en este escenario, el uso de la serialización de MvcFutures es totalmente opcional. De hecho, es perfectamente posible conseguir el mismo efecto introduciendo manualmente las propiedades de los objetos en campos hidden individuales, y recuperándolas posteriormente desde el controlador  utilizando el Model Binder. Sin embargo, cuando el objeto sea complejo, el helper Serialize() puede ahorrarnos muchas pulsaciones de tecla y evitar errores, aunque sea a costa de aumentar el peso de la página (imaginemos un objeto compuesto a su vez por otros, o colecciones).

En lo que seguro que estamos totalmente de acuerdo, como siempre ocurre en estos casos, es en la necesidad de aplicar el sentido común y prudencia en su uso. Antes de utilizar esta técnica hay que tener en cuenta el peso adicional que vamos a añadir a las páginas; asimismo, desde el punto de vista de la seguridad, siempre tener en mente que la información almacenada en cliente es fácilmente manipulable.

Publicado originalmente en: Variable not found.

3 comentarios en “¿Un ViewState en ASP.NET MVC?”

  1. Buenas maestro! 🙂
    Persistir el estado en cliente entre peticiones en el cliente no es nada nuevo. Antes de Webforms lo hacíamos manualmente en campos hidden.
    El “problema” del Viewstate, es que por un lado debes usarlo sí o sí y por otro es dificil determinar que (y cuando) poner en este viewstate, lo que hace que crezca demasiado. En fin, que no es lo mismo serializar un pequeño objeto que el estado de TODA una vista.

    En fin, como comentas es una opción que está ahí, pero que debe mirarse con cierto… respeto.

    Un saludo!

  2. Hola, Eduard!

    Pues sí, probablemente la persistencia en cliente es la técnica más antigua para el mantenimiento de estado entre peticiones. Antes de ASP, en la oscura época de los CGI a pelo, era extensamente utilizada, más que nada por la ausencia de otras mejores ;-D.

    Creo que esa es la cuestión: está bien conocer las herramientas de las que podemos hacer uso, saber sus ventajas e inconvenientes, y luego utilizar el sentido común.

    Ya lo decía el gran Miyagi: “El equilibrio es la clave”. En esto, también 😉

    Gracias por comentar!

  3. Estoy totalmente de acuerdo con el post, y me atrevo a ir incluso un poco más lejos. Dentro de las opciones para persistir estado creo que es la mejor por varias razones:

    1) El estado se guarda en el cliente y no ocupa memoria en el servidor (y tenemos de media muchos más clientes que servidores).

    2) Como se hace de forma manual se utiliza mientras se necesita y desaparece automáticamente tras acabar la petición en la que lo enviamos de vuelta, evitando tener que hacer limpieza de variables.

    3) Es más rápido que lo traiga directamente el cliente a que el cliente realice una petición y luego nosotros tengamos que consultar a una caché distribuida o BBDD por el objeto (En un escenario de WebFarm). Es por decirlo de alguna forma como hacer un eager loading.

    Gastas más red, pero mientras lo mantengas en un tamaño pequeño creo que las diferencias de rendimiento son inapreciables y merecen por mucho la pena con respecto al almacenamiento en servidor.

    Un saludo,

    Javier.

Deja un comentario

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