Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 2 de junio de 2009

¡Menuda vista! El framework ASP.NET MVC utiliza en varios puntos la filosofía que suele denominarse convención sobre configuración, ahorrando tiempo y esfuerzos al desarrollador que decida asumir una serie de normas preestablecidas, a la vez que facilita la homogeneidad y coherencia en las soluciones que las implementen.

Por ejemplo, todos sabemos que las vistas de una aplicación ASP.NET MVC deben encontrarse en la carpeta Views. Si se trata de una vista compartida como puede ser una página maestra, debemos colocarlas en la subcarpeta Shared; en caso contrario, deberá existir una subcarpeta con el nombre del controlador en la que se encontrarán las vistas para las distintas acciones, nombradas igual que éstas. Esto, a la postre, evita que sea el desarrollador el que tenga que decidir dónde ubicar dichos archivos y configurar el sistema para que sea capaz de localizarlos cuando  sean necesarios: simplemente deberá seguir la convención preestablecida.

Pero, ¿qué ocurre cuando estas convenciones no encajan con nuestras necesidades o preferencias?

En este post voy a describir cómo es posible modificar la ubicación de las vistas en una aplicación ASP.NET MVC 1.0, saltándonos las convenciones establecidas por el framework. Pero sobre todo, ojo:

Saltarse las convenciones = malo

Hazlo sólo cuando realmente esté justificado... o por diversión, como es el caso ;-)

1. Cómo localiza ASP.NET MVC las vistas

Cada vez que el framework necesita localizar una vista, por ejemplo después de ejecutar una acción cuyo resultado indica que debe mostrarse al usuario una página,  recorre los directorios donde, por convención, se supone que debe estar. Esta información se encuentra definida a nivel de código en el constructor de la clase WebFormViewEngine, es decir, en el motor de vistas Webforms que se utiliza por defecto en ASP.NET MVC. Su definición es la siguiente (recordad que está disponible el código fuente del framework):

public class WebFormViewEngine : VirtualPathProviderViewEngine
{
[...]
public WebFormViewEngine()
{
base.MasterLocationFormats = new string[]
{
"~/Views/{1}/{0}.master",
"~/Views/Shared/{0}.master"
};

base.ViewLocationFormats = new string[]
{
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
};
base.PartialViewLocationFormats = base.ViewLocationFormats;
}
[...]
}

Como se puede observar, existen tres propiedades en el motor del vistas, heredadas de su antecesor, que indican los directorios donde se encuentran los archivos con la definición del interfaz:

  • MasterLocationFormats es un array con las rutas en las que se buscarán sucesivamente las páginas maestras. En el código puede verse que por defecto el sistema intentará localizar las masters en ~/Views y ~/Views/Shared; los parámetros {0} y {1} corresponden al nombre de la master y del controlador que están siendo buscados, respectivamente.

    Es importante tener en cuenta que el valor de esta propiedad sólo se aplica cuando se establece desde el código la página maestra con la que debe ser mostrada una vista.

  • ViewLocationFormats contiene las rutas donde se intentarán localizar las vistas. En la implementación por defecto, puede verse cómo el framework intenta localizar primero en ~/Views/{1} (recordad que este parámetro se sustituirá por el nombre del controlador) y luego en ~/Views/Shared una página .aspx o .ascx con el nombre de la vista (parámetro {0}). 
  • Por último, PartialViewLocationFormats indica las carpetas donde se deberán buscar las vistas parciales. Por defecto, se buscarán en los mismos directorios que las vistas normales.

2. Lo que pretendemos

Nueva estructura de carpetas para las vistasUna vez visto en qué se basa el framework para localizar las vistas, vamos al ataque.  Nuestra intención es saltarnos las convenciones creando una nueva estructura de carpetas para las mismas, como la que puede observarse en la imagen lateral. Existirá un directorio raíz, llamado "Interfaz", en el que podemos encontrar dos subdirectorios: "Vistas" y "Masters". En ellos almacenaremos, respectivamente, las vistas y las páginas maestras de nuestra aplicación.

La estructura de carpetas del proyecto será, por tanto, diferente a la propuesta por las plantillas de Visual Studio. Además, no tiene demasiado sentido, desde el punto de vista práctico, pero nos valdrá como ejemplo.

En los siguientes apartados vamos a ver dos formas distintas de conseguirlo.


3. Cómo lograrlo

La forma más sencilla de conseguir reemplazar las rutas de búsqueda de las vistas es, sin duda, alterar el contenido de las propiedades del motor descritas en el primer punto (MasterLocationFormats, ViewLocationFormats y PartialViewLocationFormats).

Esto puede conseguirse introduciendo el siguiente código en la inicialización de la aplicación, en el archivo global.asax:

public class MvcApplication : System.Web.HttpApplication
{
[...]
protected void Application_Start()
{
ReplacePaths();
// Reemplaza las rutas en el View Engine actual
RegisterRoutes(RouteTable.Routes);
}

private void ReplacePaths()
{
WebFormViewEngine eng
= ViewEngines.Engines[0] as WebFormViewEngine;
eng.MasterLocationFormats
=
new string[] {
"~/Interfaz/Masters/{0}.master"
};
eng.ViewLocationFormats
=
new string[] {
"~/Interfaz/Vistas/{0}.aspx",
"~/Interfaz/Vistas/{0}.ascx"
};
eng.PartialViewLocationFormats
= eng.ViewLocationFormats;
}
[...]
}

Como se observa en el código anterior, en el método ReplacePaths() en primer lugar se obtiene una referencia al motor de vistas por defecto, que sabemos que es del tipo WebFormViewEngine. Después, modificamos las propiedades para introducir las rutas que nos interesan. Tan simple como eso.

Es obvio que de esta forma estamos realizando una actualización destructiva de las propiedades. Si quisiéramos mantener las localizaciones por defecto del WebFormViewEngine pero añadir nuevas ubicaciones de búsqueda deberíamos copiar el contenido original del array, añadirle las rutas adicionales, y establecerlo en las propiedades.

Veamos otra forma de conseguir lo mismo.

4. Cómo lograrlo, toma 2

ASP.NET MVC framework ha sido diseñado desde sus orígenes con la extensibilidad en mente. Prácticamente cualquiera de sus comportamientos puede ser personalizado sustituyendo componentes como si se trataran de piezas de un mecano.

Una de las posibilidades de extensión incluidas en la plataforma es la sustitución del motor de vistas que utiliza para componer los interfaces de usuario. Esto, llevado al extremo, permite la utilización de motores distintos al proporcionado por defecto (WebForms) y utilizar alternativas como NHaml, Spark, Brail, o NVelocity, entre otros.

Pero nosotros no iremos tan lejos. Para cumplir con nuestros objetivos simplemente necesitamos crear y registrar en el sistema un nuevo motor de vistas que herede de WebFormViewEngine y reemplace la inicialización de las propiedades que definen la localización de las vistas.

El nuevo motor, al que llamaremos AcmeViewEngine, será algo como lo siguiente:

 public class AcmeViewEngine : WebFormViewEngine
{
public AcmeViewEngine()
{
MasterLocationFormats
=
new string[] {
"~/Interfaz/Masters/{0}.master"
};

ViewLocationFormats
=
new string[] {
"~/Interfaz/Vistas/{0}.aspx",
"~/Interfaz/Vistas/{0}.ascx",
};

PartialViewLocationFormats
= base.ViewLocationFormats;
}
}

Y ahora sólo nos faltaría registrar el motor de vistas en ASP.NET MVC framework, tarea que podemos realizar en el global.asax, así:

protected void Application_Start()
{
ReplaceViewEngine();
// Registra el nuevo View Engine
RegisterRoutes(RouteTable.Routes);
}

private void ReplaceViewEngine()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(
new AcmeViewEngine());
}

Observad que antes de añadir el nuevo motor de vistas a la colección estamos vaciándola (llamando a su método Clear()), lo cual asegurará que nuestro AcmeViewEngine será el utilizado por la plataforma.

5. Conclusión

No podremos saltar directamente a la vista desde el controladorComo ya he comentado al principio, saltarnos las convenciones no es buena idea. Por ejemplo, un daño colateral de romper con la convención propuesta por el framework MVC para la localización de las vistas es que Visual Studio no será capaz de proporcionarnos la función que nos  permite abrir la vista asociada con una acción seleccionando la opción “Go to View” del menú contextual, o intentará insistentemente crear las nuevas vistas (con la opción “Add View”) en las carpetas en las que deberían encontrarse según la convención.

Pero en cualquier caso, es un ejercicio interesante para conocer las interioridades de este framework y comprobar de primera mano la flexibilidad con la que ha sido implementado.

Si lo deseas, puedes descargar el código fuente de los ejemplos (requiere Visual Studio 2008 o Web Developer Express con SP1 y ASP.NET MVC 1.0):



Publicado en: www.variablenotfound.com

9 Comentarios:

gamer2083 dijo...

Hola, me parece interesante, pero como tu bien mencionas no parece lo mas recomendable, pero me queda la duda de si esta accion podria brindar mayor seguridad a nuestras aplicaciones en MVC al separanos ligeramente de las convenciones del Framework.

por otra parte tengo una pequeña duda, puedo integrar herramientas de terceros con MVC o no es posible?. perdon por preguntarlo aqui, la verdad en que es el mejor blog que e encontrado de asp.net MVC y me esta agradando bastante este framenwork.

gracias.

josé M. Aguilar dijo...

Hola, Gamer2083, ante todo gracias por tus comentarios; y por supuesto que puedes preguntar por aquí lo que quieras ;-)

En cuanto a tu primera cuestión, como comentaba en el post, no creo que sea recomendable dejar a un lado las convenciones, salvo cuando esté claramente justificado. Teniendo en cuenta que desde el lado cliente todo sigue igual (la ubicación física de las vistas no afectan para nada a la salida), no creo que esto pueda aportar alguna ventaja desde el punto de vista de la seguridad.

Eso sí, como siempre, es conveniente saber que existe la posibilidad de hacerlo, y es un ejemplo creo que válido para demostrar la flexibilidad del framework.

Respecto a tu segunda pregunta, ¿a qué tipo de herramientas de terceros te refieres? Por ejemplo, si hablas de controles de servidor para asp.net, no te valdrán aquellos que utilicen el viewstate, los postback, eventos de página, y características propias del tradicional webforms. Hay otros, en cambio, que podrían valerte directamente...

Un saludo.

gamer2083 dijo...

Gracias.

Pues me interesa principalmente poder implementar algún tipo de Grid, y e visto algunos de 3º que tiene mucha flexibilidad, concretamente los de ComponentArt UI Framework 2009.1 for .NET pues manejan AJAX y silverlight que son 2 herramientas que me interesa mezclar con MVC. Pero después estuve leyendo un poco de jQuey, mencionan que se puede implementar un grid muy robusto a trabes de jQuery, así como implementar librerías de AJAX, lo de silverlight me parece que es un poco menos complicado, pero en cuanto a el información de un buen Grid que se pueda implementar en MVC, casi no e encontrado información.

En lo personal MVC me callo como anillo al dedo, solo necesito resolver estos dos puntos AJAX y un buen Grid. Sobre todo el Grid (si me pudieras orientar sobre el Grid seria un super alivio pues lo de AJAX ya lo estoy checando con otro post que tan amablemente nos haces favor de publicar ).

De ante mano gracias por todas las molestias.

josé M. Aguilar dijo...

Hola de nuevo. Disculpa por la demora en la respuesta, ando algo liadillo...

Los controles normales, como te comentaba, no funcionarán correctamente con ASP.NET, y de momento, ASP.NET MVC no trae "de serie" nada para implementar grids de forma rápida.

Pero como indicas, hay soluciones espectaculares con jQuery; por ejemplo, échale un vistazo a las demos de jquery grid. Phil Haack habló de él y mostró cómo se utilizaba hace algunas semanas en su blog.

También, si tus necesidades son simples, puedes usar las ideas de Maarten Balliauw, o probar el Grid helper del MVC Contrib (yo no lo he visto aún, no puedo contarte que tal van, aunque creo que está un pcoo verde todavía).

Espero que te sea de ayuda.

Saludos.

Hotel Amandi dijo...

Esto para mi es el maná y es exactamente lo que me hace falta. He comenzado con una aplicación con múltiples usuarios y plantillas diferentes por cada usuario, utilizando recursos y vistas diferentes por cada uno.

Richof dijo...

Hola José, en mi aplicacion MVC2 tengo el Area Admin, he implementado la solucion que propones y funciona muy bien con el area raiz, pero no con el area Admin, en mi clase CustomViewEngine tengo declaradas todas las rutas posibles hacia todas las vistas, y en la MasterLocationFormats tengo declarado esto:
"~/Areas/Admin/Views/Shared/{0}.master",
"~/Views/Shared/{0}.master"


, sera que tengo que declarar algo dentro del Area para que reconozca las rutas definidas en mi ViewEngine?
De antemano muchas gracias.
Saludos

josé M. Aguilar dijo...

Hola, Richof.

Cuando se introdujeron las áreas, se añadieron nuevas propiedades a los motores de vistas para almacenar las rutas de éstas.

Por tanto, además de ViewLocationFormats, MasterLocationFormats y PartialViewLocationFormats, ahora también tienes AreaMasterLocationFormats, AreaPartialViewLocationFormats y AreaViewLocationFormats.

Para ver el contenido por defecto de cualquiera de ellas puedes usar por ejemplo el siguiente código:

var viewEngine = ViewEngines.Engines[1] as VirtualPathProviderViewEngine;
var formats = viewEngine.AreaViewLocationFormats;

Una vez hayas comprobado el formato en que se introducen (verás que el nombre del área se especifica como parámetro {2}), puedes usar la misma técnica descrita en este post para cambiar su contenido a tu gusto.

Un saludo.

Майкл Рене dijo...

buenas tardes programador, necesito una pronta respuesta de alguno de uds, en la actualidad estoy en mi predefensa de tesis donde tengo como patron de diseño planteado en la tesis como modelo vista controlador(mvc) , al principio estuve trabajando con la estructura de ese diseño que consta de 3 archivos por separados como lo es modelo en un lugar, vistas en otro y controlador. pero a pasar del tiempo fui modificando mi manera de programar juntando modelo y controlador en un solo archivo, cosa que cambia el patron de diseño dejando de ser mvc. entonces la pregunta es : ¿que patron de diseño puedo hablar en mi defensa de tesis si ya no estoy usando mvc por esas modificaciones, entonces como se podria llamar mi tipo de patron de diseño nuevo ?

José María Aguilar dijo...

Hola!

Uuf, como recomendación general, si usas un patrón deberías ceñirte a él ;D

Probablemente podrías encajar (con algo de esfuerzo) tu diseño el alguna variante de diseño por capas: la vista pertenecería a la capa presentación, controlador+modelo en lógica, y base de datos (si la usas) en datos. Pero como te digo, es un poco forzado.

Suerte!