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!
miércoles, 1 de julio de 2009

Piezas de Lego Hace unas semanas, en el post Cambiar la ubicación de las vistas en ASP.NET MVC estuvimos viendo cómo era posible aprovechar la flexibilidad del framework para saltarse las convenciones respecto a la ubicación de los archivos de vistas (.aspx, .ascx y .master).

En esta ocasión vamos a seguir profundizando en los puntos de extensión del framework, centrándonos en la facilidad con la que podemos modificar el ControllerFactory, que es el componente encargado de localizar y crear las instancias de los controladores que deben procesar las peticiones.

Y vamos a ilustrarlo con un ejemplo, en el que desmontaremos otra de las convenciones del framework MVC: el nombre de los controladores. Como sabemos, cuando se recibe una petición que según su ruta debe ser procesada por un controlador llamado XYZ, el motor por defecto intentará instanciar una clase denominada XYZController.  Lo que vamos a hacer es retocar este comportamiento para que nuestros controladores puedan denominarse “ControladorDeXYZ”, un nombre mucho más acertado en nuestro idioma.

En este post veremos cómo podemos modificar la forma en que este componente localiza las clases de controladores y adaptarla a nuestras preferencias.

Pero antes, recuerda:

Saltarse las convenciones = malo

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

Lo que vamos a hacer en el post no es una implementación eficiente, y por supuesto no es válida para un entorno de producción; se trata simplemente de un experimento que demuestra la facilidad para sustituir ciertos componentes del framework, y la flexibilidad que nos aporta. Los conceptos que vamos a tratar pueden ser utilizados con otros fines, como facilitar la inyección de dependencias en controladores, que probablemente veamos en un futuro post.

Y ahora, al tema…

1. El proceso básico de la petición

Antes de empezar la faena, es conveniente estudiar un poco el funcionamiento del framework; así podremos entender mejor qué estamos tocando.

Cuando se recibe una petición, su tratamiento pasa a través de una serie de componentes que se encargan de procesarla a distintos niveles siguiendo el ciclo de vida definido para el framework. Así, una vez que el sistema de enrutamiento ha terminado su tarea detectando la información presente en la URL y determinando, por tanto, su destino, utilizará un manejador MvcHandler para continuar procesando la petición desde el método ProcessRequest().

La porción de código de ASP.NET MVC framework donde se define este método es la siguiente (en el archivo MvcHandler.cs, recordad que el código fuente está disponible en Codeplex):

   1: protected internal virtual void ProcessRequest(HttpContextBase httpContext) 
   2: {
   3:     [...]
   4:     
   5:     // Get the controller type
   6:     string controllerName = 
   7:         RequestContext.RouteData.GetRequiredString("controller");
   8:     
   9:     // Instantiate the controller and call Execute
  10:     IControllerFactory factory = 
  11:         ControllerBuilder.GetControllerFactory();
  12:             
  13:     IController controller = 
  14:         factory.CreateController(RequestContext, controllerName);
  15:             
  16:     if (controller == null) 
  17:     {
  18:         throw new InvalidOperationException(...); // Simplificado
  19:     }
  20:     try 
  21:     {
  22:         controller.Execute(RequestContext);
  23:     }
  24:     finally 
  25:     {
  26:         factory.ReleaseController(controller);
  27:     }
  28: }

Resumidamente, podríamos decir que el sistema procesa las peticiones siguiendo estos pasos:

  • en primer lugar, obtiene el nombre del controlador desde el contexto de la petición (normalmente desde la ruta). En el código anterior, se guarda en la variable controllerName.
  • a continuación obtiene una instancia de la factoría de controladores desde la clase ControllerBuilder.
  • una vez obtenida la factoría según el paso anterior, se le pide mediante una llamada a su método CreateController() que cree el controlador que procesará la petición, enviándole como parámetro el nombre obtenido anteriormente.
  • y por último, se invoca al método Execute del controlador para que realice las acciones oportunas, liberando al final los recursos utilizados mediante una llamada a ReleaseController.

Como en otras muchas partes del framework, se puede observar en el código anterior la utilización de interfaces en lugar de clases para los elementos que intervienen en el proceso, lo que da pistas sobre la flexibilidad de ASP.NET MVC: casi cualquier elemento puede ser sustituido por otro siempre que se cumpla el contrato definido.

2. La factoría de controladores, a corazón abierto

Visto el procedimiento anteriormente descrito, está claro que la lógica de localización e instanciación del controlador es responsabilidad de las factorías de controladores, y más exactamente del método CreateController() que éstas deben implementar.

ASP.NET MVC utiliza como factoría la clase DefaultControllerFactory  de forma predeterminada, sin embargo, nada en el código anterior obliga a que esto sea así. Si logramos crear una nueva factoría que implemente el interfaz IControllerFactory e indicamos al framework que la utilice, ya casi tendremos el trabajo hecho.

En el código de la clase DefaultControllerFactory (la factoría predeterminada), se puede observar que el proceso de creación está compuesto por dos pasos; en el primero de ellos se obtiene el tipo de controlador mediante una llamada a GetControllerType(), mientras que en el segundo se obtiene una instancia de dicho tipo llamando a GetControllerInstance(), que es la que retorna el método:


   1: public virtual IController 
   2:     CreateController(RequestContext requestContext, string controllerName) 
   3: {
   4:     [...] // Simplificado
   5:     Type controllerType = GetControllerType(controllerName);
   6:     IController controller = GetControllerInstance(controllerType);
   7:     return controller;
   8: }

Dado que lo único que queremos sustituir es el mecanismo de localización, lo más sencillo es crear nuestra nueva factoría, a la que llamaremos AcmeControllerFactory, heredando de DefaultControllerFactory y sobrescribir el método GetControllerType, que afortunadamente es virtual.

El código completo para esta nueva clase es el siguiente:


   1: public class AcmeControllerFactory: DefaultControllerFactory
   2: {
   3:   protected override Type GetControllerType(string controllerName)
   4:   {
   5:       Type r = base.GetControllerType(controllerName);
   6:       if (r == null)
   7:       {
   8:           Assembly current = Assembly.GetExecutingAssembly();
   9:           r = (from t in current.GetTypes()
  10:                where
  11:                (
  12:                        t.Name.Equals("ControladorDe" + controllerName,
  13:                                       StringComparison.InvariantCultureIgnoreCase)
  14:                   && t.IsPublic
  15:                   && !t.IsAbstract 
  16:                   && typeof(IController).IsAssignableFrom(t)
  17:                )
  18:                select t).FirstOrDefault();
  19:       }
  20:       return r;
  21:   }
  22: }

Como podemos observar, lo primero que hacemos es intentar localizar el controlador con el nombre que nos llega en el parámetro, pero utilizando el método propuesto por la factoría por defecto, es decir, llamando al método GetControllerType() original. Es decir, vamos a ser al menos algo respetuosos con las convenciones de nombrado existentes ;-) … primero intentaremos localizar el controlador ciñéndonos a ellas, y sólo si no tenemos éxito aplicaremos nuestro propio criterio de nombrado.

Así, ante una petición rutada hacia el controlador Cliente, primero buscaremos la clase ClienteController (según la convención), y si no existe es cuando intentaremos localizar un tipo llamado ControladorDeCliente. Obviamente, podríamos hacerlo al revés, es decir, buscar primero según nuestro criterio de nombrado y si no tenemos éxito buscar utilizando el estándar, o bien ni siquiera intentar esta última opción.

Por tanto, si el método por defecto no es capaz de encontrar un controlador correspondiente a dicha denominación es cuando ejecutamos nuestra lógica adicional, que consiste en buscar en el ensamblado actual, mediante una consulta LINQ, aquellas clases cuyo nombre corresponda con el patrón que pretendemos emplear (“ControladorDe”+controllerName), y que sean públicas, instanciables, e implementen el interfaz IController.

No se han implementado optimizaciones, como la inclusión de un caché, que permitan mejorar el rendimiento en esta consulta. Cada petición deberá recorrer, en el peor de los casos, todo el ensamblado en busca de un controlador apropiado.

Tampoco se ha implementado ningún tipo de resolución de conflictos; si existe más de un controlador con el mismo nombre en distintos namespaces, se retornará el primero que se encuentre.

Eso os lo dejo de deberes ;-)

3. Sustituir la factoría de controladores

Ya tenemos creada la clase AcmeControllerFactory, nuestra propia factoría de controladores, y está lista para entrar en acción. Pero, ¿cómo le decimos al framework que queremos utilizar una factoría de controladores distinta a la predeterminada?

Volvamos a estudiar el código de procesado de las peticiones que vimos anteriormente. Como se puede observar, el IControllerFactory a utilizar es obtenido desde la clase ControllerBuilder:


   1: protected internal virtual void ProcessRequest(HttpContextBase httpContext) 
   2: {
   3:   [...]
   4:   // Instantiate the controller and call Execute
   5:   IControllerFactory factory = 
   6:             ControllerBuilder.GetControllerFactory();
   7:             
   8:   IController controller = 
   9:             factory.CreateController(RequestContext, controllerName);
  10:     [...]
  11: }

Y aquí de nuevo entra en juego la flexibilidad del diseño de ASP.NET MVC: ControllerBuilder viene preparada para que podamos sustituir muy fácilmente la factoría a instanciar, por lo que podemos indicarle el tipo concreto que queremos que utilice.

En el siguiente código estamos indicando al ControllerBuilder que nuestra factoría por defecto, es decir, la clase encargada de buscar e instanciar los controladores, será AcmeControllerFactory. Como  se define en la inicialización de la aplicación (Application_Start del global.asax), todas las peticiones utilizarán esta factoría para crear los controladores:


   1: protected void Application_Start()
   2: {
   3:     RegisterRoutes(RouteTable.Routes);
   4:     ControllerBuilder
   5:         .Current
   6:         .SetControllerFactory(typeof(AcmeControllerFactory));
   7: }

4. Probar que realmente funciona

Podemos comprobar el funcionamiento de todo este invento de forma muy sencilla. Una vez seguidos los pasos anteriores, basta con crear un nuevo controlador que ya atienda a nuestra propia convención de nombrado, como el siguiente:


   1: [HandleError]
   2: public class ControladorDeCliente : Controller
   3: {
   4:   public ActionResult Listar()
   5:   {
   6:       return View();
   7:   }
   8:  
   9:   public ActionResult Editar()
  10:   {
  11:       return View();
  12:   }
  13: }

Creamos, asimismo, una vista “Listar.aspx” y otra “Editar.aspx” en la carpeta “Views\Cliente” para que el motor pueda localizarlas y podamos ver algún resultado en pantalla. A partir de este momento, ejecutando el proyecto, podremos comprobar cómo si introducimos la URL  http://localhost:tupuerto/Cliente/Listar el flujo de ejecución pasará por el método Listar() visto anteriormente. También podemos invocar a la acción Editar(), y el resultado será el esperado.

Y para los perezosos, ahí va el código fuente del proyecto (requiere Visual Studio 2008 o Web Developer Express con SP1 y ASP.NET MVC 1.0):



Publicado en: Variable not found.

martes, 30 de junio de 2009

Lo sé. Estoy desactualizado... pero me sigue gustando Office 2003, como supongo que ocurre a mucha gente todavía. Y de hecho, continúo creando mis documentos con Microsoft Word 2003.

image Sin embargo, a pesar del largo camino que hemos recorrido juntos, aún no he sido capaz de verle sentido al modo diseño de lectura, esa extraña forma de presentar los documentos por defecto al abrirlos,  o que podemos activar voluntariamente desde el menú “edición”. ¿A quién se le ocurrió esa idea? ¿Alguien la ha visto útil alguna vez? ¿Tiene, quizás, alguna utilidad oculta que no he sido capaz de intuir en todos estos años?

Bueno, en cualquier caso, podemos conseguir muy fácilmente que no utilice el diseño de lectura en el momento de abrir los documentos, accediendo al cuadro de diálogo de configuración (Herramientas >> Opciones) y desmarcando el check “Permitir el inicio en diseño Lectura”:

image

Sencillo, ¿eh? Desde luego, no tengo excusa para haber aguantado este odioso comportamiento durante más de un lustro…

Publicado en: Variable not found.

lunes, 29 de junio de 2009

He recibido algunos mensajes de lectores de Variable not found que quieren iniciarse en ASP.NET MVC, pero no saben por dónde empezar, y me sugieren que escriba algunos posts que expliquen desde cero este nuevo framework. Posiblemente lo haga algún día, pero mientras tanto, les he recordado que existen recursos gratuitos que pueden ser de mucha utilidad para dar los primeros pasos (e incluso profundizar un poco) en esta tecnología.

Por ejemplo, recientemente han aparecido una gran cantidad de libros sobre ASP.NET MVC, y de la mayoría de ellos se pueden descargar capítulos gratuitos que, además de ayudarnos a decidir cuál de ellos puede ser de nuestro interés, tienen valor por la información que nos pueden aportar. Como siempre, el problema es la dispersión de esta información, por lo que he decidido hacer esta recopilación para tenerlos todos a mano. Conforme vaya descubriendo nuevos ejemplares iré ampliando la lista; y por supuesto, si conocéis alguno que no esté aquí, me lo comentáis y lo añadimos.

Eso sí, todos en inglés, aunque al ser muy técnicos creo que son fáciles de comprender.

Pro ASP.NET MVC FrameworkPro ASP.NET MVC Framework 
Autor: Steve Sanderson
Post de presentación: Now Published Pro ASP.NET MVC Framework (Apress) « Steve Sanderson’s blog

Capítulo gratuito:  Chapter 2: Your First ASP.NET MVC Application (23 páginas)En este capítulo, el autor muestra muy detalladamente, prácticamente paso a paso, cómo crear nuestra primera aplicación simple con ASP.NET MVC. En primer lugar, crea un proyecto muy básico mediante el cual explica los fundamentos básicos de la programación siguiendo este modelo, y a continuación entra en la creación de una mini-aplicación con entrada de datos, validaciones y lógica simple.

Professional ASP.NET MVC 1.0Professional ASP.NET MVC 1.0
Autores: Scott Hanselman, Rob Conery, Phil Haack y Scott Guthrie

Capítulo gratuito: Chapter 1: Nerdinner (192 páginas) 
Este extenso capítulo describe la creación de un sitio web medianamente complejo, www.nerddinner.com, utilizando gran cantidad de tecnologías relacionadas con el framework MVC como filtros, Ajax, LinqToSql, pruebas unitarias, etc. Muy recomendable, imprescindible.
imageASP.NET MVC 1.0 Quickly
Autor: Maarten Balliauw
Post de presentación: Announcing my book- ASP.NET MVC 1.0 Quickly

Capítulo gratuito: Chapter 2: Your first ASP.NET MVC application (20 páginas) 
Breve recorrido por el proceso de creación de una aplicación MVC, el sistema de rutas, controladores, vistas y realización de pruebas unitarias. Bastante básico, quizás demasiado “quickly”, pero válido en cualquier caso.

imageASP.NET MVC in Action
Autores: Jeffrey Palermo, Ben Scheirman, Jimmy Bogard
Post de presentación: Announcing ASP.NET MVC in Action (from Manning) - Jeffrey Palermo (.com) - CodeBetter.Com

Recurso gratuito: Getting Started with the ASP.NET MVC Framework (Green Paper – PDF) (18 páginas) (El enlace a este documento te lo envían por correo electrónico tras facilitar tu dirección). Se trata de otra introducción al framework, el sistema de rutas, controladores y vistas partiendo desde cero.

Capítulo gratuito: Chapter 9: AJAX in ASP.NET MVC (21 páginas)
Interesante capítulo de introducción al uso de la tecnología Ajax sobre ASP.NET MVC utilizando jQuery y los Ajax helpers para intercambiar datos con el servidor.

imageASP.NET MVC Framework Unleashed
Autor: Stephen Walther

Capítulos gratuitos: el autor ha publicado varios capítulos en su blog, y los retirará en cuanto el libro esté disponible en Amazon, así que daos prisa que los contenidos son excelentes.
Chapter 1 - An Introduction to ASP.NET MVC
Chapter 2 - Building a Simple ASP.NET MVC Application
Chapter 3 - Understanding Controllers
Chapter 4 - Understanding Views
Chapter 5 - Understanding Models
Chapter 6 - Understanding HTML Helpers
Chapter 9 - Understanding Routing

imageBeginning ASP.NET MVC 1.0
Autores: Simone Chiaretta, Keyvan Nayyeri

Capítulo gratuito: Chapter 9: Testing ASP.NET MVC Applications (38 páginas)
Interesantísimo capítulo que describe distintas técnicas para la realización de pruebas unitarias de aplicaciones creadas con este framework, incluyendo la creación de mocks, inyección de dependencias en controladores, testeo de rutas y de refilón, algo de TDD.

Publicado en: Variable not found
domingo, 14 de junio de 2009

Sorpresa El control de errores en aplicaciones web es fundamental si queremos ofrecer un interfaz robusto y amigable para los usuarios en cualquier situación. No hay nada más frustrante para un usuario que una pantalla de error con contenidos indescifrables y que no le aportan alternativas de salida.

El framework ASP.NET MVC nos ofrece mecanismos de control de errores muy potentes basada en la utilización del atributo HandleError, el cual definirá la vista que será mostrada al usuario cuando se produzca alguna excepción no controlada en el código de los controladores, siempre que en el web.config se haya activado el uso de errores personalizados mediante la propiedad CustomErrors.

En este post vamos a profundizar en el uso del atributo HandleError, comentando cómo se implementa en el controlador, su ámbito de actuación, los parámetros que ofrece y la forma de crear las vistas para mostrar los errores de forma amigable.

El controlador

HandleError puede ser declarado tanto a nivel de clase (controlador) como a nivel de acción. En el primer caso, se establecerá el comportamiento general para todas las acciones del controlador, mientras que en el segundo será aplicable sólo a la acción a la que se asocie el atributo:

// Control de errores a nivel de clase de controlador, 
// que será aplicado a todas las acciones del mismo.
[HandleError()]
public class HomeController : Controller
{
...
}


// Control de errores a nivel de acción concreta...
[HandleError(ExceptionType=typeof(ArgumentException), View="ArgumentError")]
public ActionResult Calculate(int a, int b)
{
ViewData[
"results"] = calculateSomething();
return View();
}

En realidad, el comportamiento definido en el atributo HandleError a nivel de clase también se aplicará a los errores generados por las vistas u otros resultados (ActionResult) retornados por los controladores. Es decir, sobre el segundo de los ejemplos anteriores, la vista “ArgumentError” (que existirá en un archivo llamado ArgumentError.aspx) será invocada cuando la excepción ArgumentException sea lanzada bien por el propio controlador, o bien por la vista “Calculate” que retorna por defecto.

Un último detalle sobre esto: el atributo HandleError puede ser especificado tantas veces como necesitemos sobre la misma acción o controlador, indicando comportamientos para distintos tipos de excepción. El atributo que se tendrá en cuenta cuando se produzca un error será el primero que se encuentre cuyo tipo de excepción (parámetro ExceptionType) sea compatible con la excepción lanzada.

El manejador de errores que se empleará en una acción será el primero que corresponda al tipo de excepción producida, teniendo en cuenta tanto los atributos que adornan la acción como los que acompañan a su controlador, y siempre según un orden preestablecido.

Veamos con más detenimiento los parámetros que admite la declaración del atributo.


Parámetros de HandleError


        [HandleError(
ExceptionType
=typeof(DivisionByZero),
View
="ErrorPersonalizado",
Master
="MaestraErrores",
Order
= 0)
]
public ActionResult Index()
...

  • ExceptionType permite indicar el tipo de excepción que se pretende controlar. Por defecto, el sistema entenderá que la regla se refiere al tipo base Exception, por lo que se aplicará a todos los errores que se generen, pues todas las excepciones heredan de esta clase.
  • View, el nombre de la vista que será mostrada al usuario. Por defecto se tomará el valor “Error”, por eso en la plantilla de proyectos ASP.NET MVC ya existe una vista con este nombre.
  • Master, la página maestra con la que será renderizada la vista, independientemente de lo que tenga declarado ésta.
  • Order, un valor numérico (por defecto –1, el más prioritario) que indica la prioridad de aplicación de esta regla cuando el sistema encuentre varios atributos HandleError aplicables al mismo elemento y que puedan presentar conflictos. Los valores más pequeños, Por ejemplo, si no se indicara este parámetro en el siguiente caso, el resultado dependería del orden de declaración, lo cual no es demasiado recomendable:
    [HandleError(Order=10, View="ErrorGenerico")]
    public class HomeController : Controller
    {
    ...
    [HandleError (ExceptionType
    =typeof(DivisionByZero),
    View
    ="OperacionIncorrecta", Order=1)]
    public ActionResult Calculate()
    ...

    Como puede intuirse, esto hará que en caso de producirse una división por cero, se muestre la vista “OperacionIncorrecta” y no la “ErrorGenerico”.

Cada vez que utilicemos HandleError es conveniente tener muy en cuenta la prioridad (definida en la propiedad Order), el alcance (las excepciones a tratar, definidas en la propiedad ExceptionType), así como los valores por defecto en cada caso. Esto evitará comportamientos misteriosos del sistema una vez se produzcan errores en tiempo de ejecución.

Acceso desde la vista a la información del error

Ya hemos comentado anteriormente que la vista que será mostrada a los usuarios cuando se produzca un error será la indicada en el parámetro View del atributo HandleError, o la vista "Error", si este parámetro no es informado. Sea cual sea, el archivo nombredevista.aspx deberá estar localizable por el motor de vistas en el momento de su lanzamiento (por cierto, si no te gustan las ubicaciones por defecto, puedes ver cómo modificar la forma en la que se buscan las vistas en este post).

La vista de un error es una página .aspx normal, como una vista más de la web, pero con la particularidad de que puede recibir información sobre el error que ha provocado su presentación. De hecho, se trata de una vista tipada que hereda de ViewPage<System.Web.Mvc.HandleErrorInfo>, de forma que  la propiedad Model será del tipo HandleErrorInfo, clase que nos ofrece completa información sobre el origen del problema:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits
="System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>" %>

<asp:Content ID="errorTitle" ContentPlaceHolderID="TitleContent" runat="server">
Error en el sistema
</asp:Content>
<asp:Content ID="errorContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Ups, se ha producido un ligero inconveniente...
</h2>
<p>
La acción
<%= Model.ActionName %>
del controlador
<%= Model.ControllerName %>
ha lanzado la excepción
<%= Model.Exception.GetType().Name %> con
el mensaje "
<%= Model.Exception.Message %>".
</p>
</asp:Content>

A pesar de no ser un buen ejemplo como pantalla del error amigable para el usuario ;-), el código anterior ilustra cómo es posible acceder desde la vista a los datos del contexto del error proporcionados por el entorno, y cómo la clase HandleErrorInfo nos ofrece una información sobre la acción en la que se ha lanzado la excepción, el controlador en el que se encuentra la misma, y lo más interesante, nos ofrece en la propiedad Exception la excepción producida, por lo que tendremos acceso a su tipo, descripción e incluso al estado de la pila de llamadas en el momento de producirse el error.



Publicado en: www.variablenotfound.com

domingo, 7 de junio de 2009
Code TagHace unos días publiqué CodeTag, un sencillo plugin para Windows Live Writer que permite insertar pequeñas porciones de código fuente en línea en los contenidos de los posts. Resumidamente, lo único que hace esta utilidad es envolver entre etiquetas <code> y </code> el texto que tengamos seleccionado en el momento de su utilización, pero evita tener que entrar al modo de edición HTML para hacer estos ajustes.

Basándome en el código fuente de CodeTag (que podéis descargar al final de este post), voy a describir, paso a paso, cómo podemos crear complementos para Writer que nos faciliten un poco las tareas cotidianas de edición.

0. Antes de empezar…

Para poder desarrollar el plugin necesitáis básicamente dos cosas:

  • en primer lugar, el propio software Live Writer, más que nada porque necesitamos referenciar algunos de sus ensamblados, y por ir probando el complemento durante el desarrollo.
  • en segundo lugar, Visual Studio 2005 o 2008, aunque sea una edición express. Este desarrollo vamos a realizarlo en C#, pero la traducción a Visual Basic .NET sería trivial.
En este post voy a incluir capturas de pantalla correspondientes a Microsoft Visual C# 2008 Express, que es el único que tengo a mano.

1. Preparación del proyecto

Los plugins para Live Writer son archivos .dll, es decir, ensamblados .NET, que se colocan en el directorio "plugins" de la carpeta de instalación del programa. En mi caso, están en la carpeta C:\Archivos de programa\Windows Live\Writer\Plugins, y si no habéis cambiado las rutas de instalación por defecto, será allí donde los podéis encontrar también. Durante el proceso de arranque, Writer examinará dicha carpeta y cargará los complementos que encuentre en ella.

Crear librería de clasesLo que vamos a hacer es crear desde Visual Studio un proyecto de librería de clases. Cada vez que compilemos, copiaremos el ensamblado resultante en dicho directorio (veremos cómo podemos automatizar esta tarea) y podremos lanzar Live Writer para comprobar el funcionamiento.

Por tanto, en primer lugar creamos un proyecto de librería de clases como siempre, al que le damos el nombre CodeTag. Obviamente, podéis dar al proyecto el nombre que estiméis conveniente.

Una vez que el IDE crea la estructura, debemos añadir dos referencias al proyecto, que serán necesarias para poder continuar:

  • Añadir referencias La primera referencia es al ensamblado que contiene el API de Live Writer, que podéis encontrar en el directorio de instalación de la herramienta. El  archivo a incluir es WindowsLive.Writer.Api.dll .
  • La segunda es a System.Windows.Forms. Hay que tener en cuenta que Live Writer es una herramienta de escritorio, y esta referencia es importante para poder interactuar con el mismo.

Otro detalle más que nos va a facilitar las cosas: vamos a indicar a Visual Studio que el resultado de la compilación lo copie en el directorio de plugins de Live Writer. Para ello, lo único que tenemos que hacer es acudir a las propiedades del proyecto, pestaña “eventos de generación” e incluir la siguiente orden en el cuadro “línea de comandos del evento posterior a la generación” (obviamente, modificando la ruta si es necesario):

XCOPY /D /Y /R "$(TargetPath)" "C:\Archivos de Programa\Windows Live\Writer\Plugins\"

Evento posterior a la generación

Ojo, es importante tener en cuenta algo más cuando vayamos a compilar: si Live Writer está abierto, no podremos sobrescribir nuestro plugin con una nueva versión, pues éste se encontrará en uso. En estos casos debemos cerrar el programa antes de copiar el archivo, o antes de compilar.

Con estas operaciones ya tenemos el proyecto listo para empezar a codificar el plugin.

2. Escribimos la clase principal

Un plugin sencillo como este no requiere demasiada programación, pero sí hay que cumplir una serie de requisitos para que se integre correctamente en Live Writer.

En primer lugar, creamos una clase, a la que llamaremos CodeTag, e insertamos el siguiente código:

  public class CodeTag: ContentSource
{
public override DialogResult CreateContent(IWin32Window dialogOwner,
ref string content)
{
if (string.IsNullOrEmpty(content))
content
= "YourCodeHere";

content
= "<code>" + content + "</code> ";
return DialogResult.OK;
}
}

Como se puede observar, la clase hereda de ContentSource, un tipo definido en WindowsLive.Writer.Api, que sirve como base para la creación de complementos sencillos como el que nos ocupa. Para casos más complejos, que permitieran por ejemplo edición de contenidos bidireccional entre el contenido de la página y un editor personalizado podríamos heredar de SmartContentSource, pero no profundizaremos en ello ahora.

La codificación de la lógica del plugin puede realizarse en varios puntos, dependiendo de la forma en que Writer lo active; en nuestro caso, el complemento se lanzará cuando el usuario presione un botón en la barra lateral o seleccione la opción correspondiente en el menú "insertar", por lo que simplemente deberemos sobrescribir el método CreateContent.

Dicho método recibe dos parámetros. El primero de ellos hace referencia a la ventana activa, que podemos utilizar como "padre" si quisiéramos crear un cuadro de diálogo desde nuestro código. El segundo parámetro contendrá una referencia al texto seleccionado en el momento de lanzar el plugin, pudiendo darse los dos casos que se contemplan en la codificación:

  • Si la variable content viene vacía o con valor nulo, es que el complemento ha sido lanzado en modo inserción, es decir, no existía ningún texto seleccionado y por lo tanto lo que se pretende es añadir las etiquetas de código para introducir posteriormente contenido. Como puede observarse en el código, lo que se hace en este caso es insertar las etiquetas <code> con un texto arbitrario para que el usuario lo modifique a su antojo más adelante.
  • En caso contrario, si la variable content trae algún contenido, lo que se hace es rodear éste por la apertura y cierre de la etiqueta <code>.

En ambos casos se retorna un valor DialogResult.OK, que indica a Live Writer que debe insertar el texto contenido en content en la ubicación donde se encuentre el cursor, o bien sustituir el texto seleccionado por el nuevo valor.

3. Añadimos metadatos

Heredar de la clase ContentSource no es el único requisito para que esta sea considerada como un componente de Writer, es necesario adornarla con un conjunto de atributos como los mostrados en el siguiente código:
[WriterPlugin("98497c2b-bbfd-4bd1-b343-226f3c9e766b", "Code Tag",
Description
= "Crea etiquetas <code></code> en el contenido",
PublisherUrl
= "http://www.variablenotfound.com")]
[InsertableContentSource(
"Etiqueta <code>")]
public class CodeTag: ContentSource
{
[...]
}

El atributo [WriterPlugin] es el que realmente identifica la clase como plugin de Live Writer. Los parámetros que se le están enviando son los siguientes:
  • El primer parámetro es un GUID, es decir, un identificador único que debemos generar para el plugin, utilizando este servicio on-line u otras herramientas como la incluida en Visual Studio.
  • El segundo parámetro es el nombre del plugin. Es la identificación que aparece en el cuadro de diálogo de configuración de complementos de Live Writer.
  • Description permite añadir una descripción textual ampliada del plugin.
  • PublisherUrl es una dirección web de referencia al autor. Pura propaganda ;-)


El atributo [InsertableContentsource] indica que el complemento debe aparecer en el menú "insertar" de las barra de tareas de Writer y en el menú principal. El parámetro que le estamos enviando es simplemente el texto que aparecerá en estos menús.

4. Compilamos el proyecto

Con lo hecho hasta el momento ya podemos compilar e intentar probar nuestro complemento. Este procedimiento lo repetiremos varias veces durante el desarrollo, por lo que es posible que nos encontremos a menudo con un error como el siguiente:

The command "XCOPY /D /Y /R "CodeTag.dll" "C:\Archivos de Programa\Windows Live\Writer\Plugins\"" exited with code 4

Esto simplemente quiere decir que Live Writer está usando CodeTag.dll y no puede ser sobrescrito, por lo que ¡cerrad Live Writer antes de compilar!

CodeTag en el menú "insertar", sin icono Una vez superado este leve impedimento, si aparece, ya podremos comenzar a disfrutar de nuestro plugin. Veremos que aparece en el menú "insertar", con la descripción apropiada, y funcionando correctamente.

Pero vaya, es cierto que en el menú aparece, pero destaca sobre el resto de complementos porque es el único que no tiene un icono, así que habrá que mejorarlo un poco…

5. Añadirle un icono al plugin

Incluir un icono a nuestro complemento le dará sin duda un aspecto más profesional, vamos a ello.

image Lo primero que necesitamos es una imagen de 16x16 píxeles, por ejemplo en formato .bmp, que actuará como icono. Como el diseño gráfico decididamente no es lo mío, he creado una imagen muy simple pero creo que bastante ilustrativa de la tarea que realiza el complemento: la propia etiqueta <code> que pretendemos crear. Incrustar la imagen como recurso en el ensamblado

El archivo debemos incluirlo, para no tener problemas, en el directorio raíz del proyecto, y a continuación, hay que indicar a Visual Studio que dicha imagen será un recurso incrustado (embedded resource) en el ensamblado. Este paso es importante, pues si no se hace correctamente, la imagen no será incluida en la DLL.

A continuación es necesario indicar a Live Writer la imagen a utilizar como icono, lo que se consigue añadiendo un parámetro más (ImagePath) en la definición del atributo [WriterPlugin] con la ruta hacia el fichero que hemos incrustado. Eso sí, no me refiero a la ruta física del archivo .bmp, sino a la representación como espacio de nombres de la misma (por ejemplo, si la imagen se llama logo.bmp y está en (raíz)\recursos, la ruta hacia ella será “recursos.logo.bmp”).

Como en este caso hemos depositado la imagen en el directorio raíz, la declaración de atributos quedará como sigue:
    [WriterPlugin("98497c2b-bbfd-4bd1-b343-226f3c9e766b", "Code Tag",
Description
= "Crea etiquetas <code></code> en el contenido",
ImagePath
= "CodeTag.bmp",
PublisherUrl
= "http://www.variablenotfound.com")]
[InsertableContentSource(
"Etiqueta <code>")]
public class CodeTag : ContentSource
{
[...]
}

Un último apunte relativo a este tema: si al iniciar Live Writer éste es incapaz de localizar el recurso indicado en el parámetro ImagePath, el plugin funcionará, pero aparecerá el siguiente mensaje en el arranque de la aplicación:

Advertencia: no se ha podido cargar el mapa de bits para el complemento CodeTag desde la ruta especificada (CodeTag.bmp)

6. O, por si no quieres teclear…

CodeTag en el menú "insertar", con icono … he colgado en SkyDrive el código fuente del proyecto, para el que quiera utilizarlo como base de creación de sus propios complementos, o simplemente para trastearlo un poco.

Requiere, como mínimo, Visual C# 2008 Express, con su correspondiente Service Pack 1.


Publicado en: Variable not found.
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