ASP.NET MVC Framework (3ª Parte): Pasando ViewData desde Controladores a las Vistas

En estas semanas he estado trabajando en una serie de post sobre ASP.NET MVC Framework. Es una aproximación opcional que podéis usar para estructurar vuestras aplicaciones ASP.NET para separar claramente los diferentes aspectos de esas aplicaciones, y hacer mucho más sencillo realizar test en vuestro código.

En el primer post creamos una aplicación de e-comerce sencilla para navegar/listar los productos. Vimos los conceptos de más alto nivel que hay detrás de MVC, y vimos cómo crear un nuevo proyecto ASP.NET MVC desde cero hasta implementar y testear esas funcionalidades. El segundo post de la serie se introducia un poco más en la arquitectura de rutado de URLs, y vimos cómo funciona y algunos escenarios de rutado de URLs avanzados.

En el post de hoy veremos cómo interactúan los Controladores con las Vistas, y veremos cómo pasar datos desde un Controlador a una Vista para que renderize la respuesta al cliente.

Recapitulemos un poco

En el primer post creamos un sitio de e-comerce que nos permitía navegar y listar los poductos. Lo implementamos con ASP.NET MVC Framework, lo que nos llevó a estructurar el código de una manera natural para distinguir entre Controladores, modelos y vistas.

Cuando un navegador envia una petición HTTP al sitio web, ASP.NET MVC usará su motor de rutado de URL para mapear la petición a un método de acción de una clase controlador que lo procesará. Los controladores de MVC son los responsables de procesar las peticiones entrantes, manejar las entradas de usuarios y sus interacciones, y de ejecutar la lógica de la aplicación (obteniendo y actualizando el modelo de datos, etc).

Cuando lleva tiempo renderizar la respuesta HTML al cliente, los controladores usan los componentes llamados “Vistas” – que están implementados como clases separadas de los controladores, y se encargar de encapsular toda la lógica de presentación.

Las vistas NO DEBEN contener ninguna lógica de la aplicación ni ningún código de acceso a datos, sino que toda la lógica de la aplicación y de datos sólo puede estar en las clases controlador. La motivación que hay detrás de esta distinción  es para ayudar a mantener una serparción clara de la lógica de aplicación, datos y de la interfaz de usuario. Esto hace más sencillo crear test unitarios en cada una de las partes de la aplicación.

Las vistas SOLO DEBEN renderizar la salida usando unos datos específicos pasados por la clase Contorlador. En ASP.NET MVC llamamos a esos datos “ViewData”. El resto del post cubrirá algunas aproximaciones diferentes que podemos usar para pasar estos “ViewData” del controlador a la Vista.

Un escenario simple: listado de productos

Para ayudar a comprender algunas de las técnicas que vamos a usar para pasar ViewData del contorlador a la visa, crearemos una página de listado de productos:

Usaremos el entero CategoryID para filtrar los productos que queremos mostrar en la página. Fijáos cómo estamos metiendo el CategoryID como parte de la URL (por ejemplo: /Products/Category/2 o /Products/Category/4).

Nuestra página de listado está renderizando dos elementos. El primero es el nombre de la categoría que se muestra (por ejemplo: “Condiments”). El segundo es una lista HTML <ul><li/></ul> de nomres de productos. Los he rodeado los dos en rojo.

Ahora veremos dos formas diferentes de implementar la clase “ProductsController” que procesen la petición, obtenga los datos, y se los pase a la vista “List” para renderizarlo. En la primera forma usaremos un objeto dicionario enlazado a los datos. En la segunda uramos una clase fuertemente tipada.

1: Pasando ViewData con el Diccionario Controller.ViewData

La clase base Controller tienen una propiedad diccionario “ViewData” que podemos usar para meter los datos que queremos pasarle a la Vista. Añadimos objetos a este diccionario con un patrón clave/valor.

Aquí tenéis la clase ProductsController con un método de acción “Category” que implementa el listado de producots. Fijáos cómo se usa el parámetro ID de las categorías para buscar el nombre de la categoría, y cómo obtenemos la lista de productos de esa categoría. Está guardando esos datos en la colección Controller.ViewData usando “CategoryName” y “Products”:

La acción Category llama al método RenderView(“List”) para indicar qué plantilla queremos renderizar. Cuando llamamos a RenderView de esta forma, le pasará el diccionario ViewData a la Vista.

Implementando nuestra Vista

Implementaremos la vista List usando el archivo List.aspx que está bajo el directorio ViewsProducts. Esta página heredará de la masterpage Site.Master.MasterPage del directorio ViewsShared (clic con el botón derecho en VS 2008 y seleccionamos Add New Item -> MVC View Content Page para crear una master page cuando creemos una nueva página de vista:

Cuando creamos la página List.aspx con la plantilla MVC View Content Page no deriva de la clase System.Web.UI.Page, sino que lo hace de System.Web.Mvc.ViewPage (que es una subclase de al clase Page):

La clase ViewPage nos proporciona una propiedad “ViewData” que podemos usar en la página para acceder a los objetos que hayan sido añadidos por el Controlador. Entonces podemos cojer estos objetos y usarlos para renderizar la salida HTML usando tanto controles de servidor como  los tags <%= %>

Implentando la vista con controles de servidor.

Aquí tenéis un ejemplo sobre cómo usar <asp:literal> y <asp:repeater> para implementar la salida HTML:

Podemos enlazar el ViewDAta a estos controlres con el siguiente código en la clase del código trasero (fijáos cómo usamos el diccionario ViewData):

Nota: como no tenemos un <form runat=”server”> en la página, no se emitirá ningún view-state. Los controles siguientes tampoco renderizan ningún valor ID – con lo que tenemos control total sobre el control HTML.

Implementando la vista usando <%= %>

Si preferís usar código inline para generar la salida, lo podemos hacer de la siguiente manera:

Nota: Como el diccionario ViewData contiene objetos del tipo “object” tenemos que hacer un casting de ViewData[“Products”] a una lista List<Product> o a un IEnumerable<Product> para poder usar la sentencia foreach. Estamos importando los namespaces System.Collections.Generic y MyStore.Models en la página para no tener que escribirlo una y otra vez en List<T> y en el tipo Products.

Nota: El uso de la palabra “var” es un ejemplo de uso de la inferencia de tipos de C# y VB de VS 2008 (leed este post sobre el tema). Como tenemos que hacer un cast de ViewData[“Products”] a List<Product> tendremos intellisense completo en las variables de Product en el archivo List.aspx:

2. Pasando ViewData con Clases Fuertemente Typadas

También podemos pasar estos ViewData a través de objetos fuertemente tipados desde los Controladores a las Vistas. Hay un par de ventajas usando esta forma:

  1. No tenemos que usar strings para buscar objetos, y tenemos chequeo en tiempo de compilación tanto en el código de los Controladores como en las Vistas.
  2. No tenemos que hacer castings específicos de los objetos del diccionario  cuando usemos un lenguaje fuertemente tipado como C#.
  3. Tendremos intellisense automático en los objetos ViewData tanto en el .aspx como en el código trasero.
  4. Podremos usar herramientas de refactoring para automatizar cambios en la aplicación y en los test.

Aquí tenéis una clase fuertemente tipada “ProductsListViewData” que encapsula los datos necesarios para la vista List.aspx. Tiene dos propiedades CategoryName y Products (implementados usando el soporte de c# de las propiedades automáticas):

Ahora podemos actualizar la implementación de ProductsController para que use este objeto para pasar un objeto ViewData fuertemente tipado a la vista:

Fijáos cómo le pasamos el objeto fuertemente tipado ProductsListViewData añadiéndolo como un parámetro extra al método RenderView().

Usando el diccionario de ViewData de las vistas como un objeto fuertemente tipado.

En las implementaciones anteriores de List.aspx segurán funcionando con la nueva clase ProductsController – no hace falta ningún cambio. Esto es debido a que cuando el objeto ViewData es fuertemente tipado y lo pasamos a una vista que deriva de la clase ViewPage, el diccionario ViewData usará la reflexión automáticamente sobre las propiedades del objeto fuertemente tipado para buscar los valores. Así que el código siguiente:

usará la reflexión para obtener el valor de la propiedad CategoryName del objeto ProductsListViewData que le pasamos cuando llamamos al método RenderView.

Usando ViewPage<T> como clase base para ViewData fuertemente tipados.

Además de soportar un diccionario como clase base para los ViewPage, ASP.NET MVC también tiene una implementación genérica para ViewPage<T>. Si las vistas derivan de ViewPage<T> – donde T indica el tipo de la clase ViewData que el controlador pasa a la vista – entonces la propiedad ViewData será fuertemente tipada usando esa clase T.

Por ejemplo, podemos actualizar el código trasero List.aspx.cs para que derive de ViewPage, pero de ViewPage<ProductsListViewData>:

Cuando hacemos esto, la propiedad ViewData de la página pasará de ser un diccionario al tipo ProductsListViewData. Esto significa que en lugar de usar un diccionario basado en strings para buscar los datos, podemos usar propiedades fuertemente tipadas:

Podemos usar tanto controles de servidor como <%= %> para renderizar el HTML de esos ViewData.

Implementar nuestro ViewPage<T> con controles de servidor

Aquí tenéis un ejemplo de cómo usar los controles <asp:literal> y <asp:repeater> para implementar la interfaz HTML. Es exactamente el mismo contenido que usamos en List.aspx derivada de ViewPage:

Aquí vemos cómo es el código trasero. Fijáos que como derivamos de ViewPage<ProductsListViewData> podemos acceder a las propiedades directamente – y no tenemos que hacer ningún casting (también podemos aprovecharnos de esto a la hora de hacer refactoring cuando queramos cambiar elnombre de alguna propiedad):

Implementando nuestra ViewPage<T> con código inline <%= %>

Si preferimos usar código inline para generar la salida, podemos obtener el mismo resultado usando el siguiente List.aspx:

Usando ViewPage<T> no necesitamos buscar string en los ViewData. Más importante aún, fijáos que tampoco tenemos que hacer ningún casting – ya que es fuertemente tipado. Esto implica que podemos escribir un foreach (var product in ViewData.Products) sin tener que hacer un casting a Products. También tenemos intelisense en la variable product dentro del bucle:

Resumen

Hemos visto más detalles sobre cómo los controladores pasan datos a las Vistas para renderizar las respuestas al cliente. Podemos usar tanto diccionarios fuertemente tipados como no.

La primera vez que intentamos crear y ejecutar una aplicación MVC veremos que el concepto de separar la lógica de los controladores de la generación de la interfaz de usuario puede ser un poco raro. Puede que nos cueste un poco de tiempo hasta que nos sintamos cómodos y hasta que cambiemos nuestra forma de pensar cuando procesamos una petición, ejecutando toda la lógica de la aplicación, empaquetando los viewdata necesarios para crear las respuestas HTML y luego tratarlos de forma separada para renderizarlo. Importante: si no os sentís cómodos con este modelo entonces nolo uséis – el modelo MVC es totalmente opcional, y no creemos que sea algo que todo el mundo deba usar.

Los beneficios y las metas tras esta forma de dividir una aplicación es que nos permite ejecutar y testear nuestras aplicaciones y la lógica de datos a parte de la generación de la interfaz de usuario. Esto hace más fácil de desarrollar test unitarios más comprensibles para nuestra aplicación, así como usar TDD(test driven development) a medida que creamos aplicaciones. En próximos post veremos esto en más detalle, y también veremos unas buenas prácticas para testear de forma fácil nuestro código.

Espero que sirva.

Scott.

Traducido por: Juan María Laó Ramos.

0 comentarios sobre “ASP.NET MVC Framework (3ª Parte): Pasando ViewData desde Controladores a las Vistas”

Deja un comentario

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