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!
Mostrando entradas con la etiqueta .net. Mostrar todas las entradas
Mostrando entradas con la etiqueta .net. Mostrar todas las entradas
lunes, 20 de julio de 2009

En la plataforma .NET existen distintas formas de hacer que una llamada a un método sea omitida bajo determinadas circunstancias. Por ejemplo, los métodos parciales permiten, en C# 3.0 y VB 9.0, que el compilador omita la llamada a funciones no implementadas. También existe la posibilidad de utilizar las clásicas directivas (como #if… #endif ) para incluir código cuando existan constantes de compilación.

Es menos conocida, sin embargo, la existencia del atributo ConditionalAttribute, que aplicado a un método hace que las llamadas a éste no sean incluidas en el ensamblado si no existe la constante de compilación cuyo nombre se indica en el parámetro.

Por ejemplo, explorando un poco el espacio de nombres System.Diagnostics, vemos que todos los métodos de la clase Debug, están adornados por este atributo así:

   1: [Conditional("DEBUG")]
   2: public static void WriteLine(string message)
   3: {
   4:     TraceInternal.WriteLine(message);
   5: }

Ahora, imaginad que tenemos un código que usa esta clase, por ejemplo de la siguiente manera:


   1: public static void Main(string[] args)
   2: {
   3:     for(int i=0; i < 1000; i++)
   4:     {
   5:         Debug.WriteLine("Iteración " + i);
   6:         ProcesaAlgoComplejo(i);
   7:         int j = ProcesaOtraCosa(i);
   8:         Debug.WriteLine("Obtenido " + j);
   9:         // ...
  10:     }
  11: }

Si compilamos en modo “debug” (o simplemente está definida la constante de compilación con dicho nombre), las llamadas a estos métodos serán omitidas en el ensamblado resultante, por lo que el código finalmente generado será totalmente equivalente a:


   1: public static void Main(string[] args)
   2: {
   3:     for(int i=0; i < 1000; i++)
   4:     {
   5:         ProcesaAlgoComplejo(i);
   6:         int j = ProcesaOtraCosa(i);
   7:         // ...
   8:     }
   9: }

Pero ojo, que se omiten tanto las llamadas al método como la evaluación de sus parámetros, y esto puede provocar errores difíciles de detectar. Por ejemplo, el siguiente código daría lugar a un bucle infinito de los de toda la vida ;-) si compilamos en modo “release”; sin embargo, compilando en “debug” funcionaría correctamente:

   1: int j = 0;
   2: while (j < 100)
   3: {
   4:     Console.WriteLine("Hey " + j);
   5:     Debug.WriteLine("Procesado " + (j++));  // <-- Esta línea desaparece cuando
   6:                                             //     compilamos en modo release!
   7: }

Aunque en el anterior ejemplo estamos jugando con la constante predefinida DEBUG, este atributo podemos utilizarlo con otras constantes, tanto existentes como personalizadas. Como muestra, podéis echar un vistazo a la definición de la clase System.Diagnostics.Trace, que vincula el uso de sus métodos a la existencia de la constante TRACE:


   1: public sealed class Trace
   2: {
   3:     // ...
   4:  
   5:     private Trace();
   6:     [Conditional("TRACE")]
   7:     public static void Assert(bool condition);
   8:     [Conditional("TRACE")]
   9:     public static void Assert(bool condition, string message);
  10:     [Conditional("TRACE")]
  11:     public static void Assert(bool condition, string message, string detailMessage);
  12:     [Conditional("TRACE")]
  13:     public static void Close();
  14:     [Conditional("TRACE")]
  15:     public static void Fail(string message);
  16:     [Conditional("TRACE")]
  17:     public static void Fail(string message, string detailMessage);
  18:     [Conditional("TRACE")]
  19:     public static void Flush();
  20:     [Conditional("TRACE")]
  21:  
  22:     //...
  23: }

Si vais a utilizar el atributo sobre vuestros métodos, condicionándolos a la existencia de constantes de compilación personalizadas, recordad que las constantes podéis definirlas:

  • desde las propiedades del proyecto en el IDE
  • en la línea de comandos del compilador (por ejemplo, /define:LOG)
  • variables de entorno del sistema operativo (set LOG=1)
  • en directivas sobre vuestro propio código (directiva #define LOG)

Eso sí, tened en cuenta que el método siempre será compilado e introducido en el ensamblado, son las invocaciones a éste las que son omitidas en caso de no existir la constante indicada.

Hay que tener en cuenta las siguientes observaciones para el uso del atributo Conditional:

  • Los métodos a los que se aplica no pueden tener tipo de retorno, es decir, serán void. Esto tiene bastante sentido, si pensamos en los efectos laterales que podría causar la desaparición de una invocación tras la cual se haga uso del valor retornado.
  • Sólo se pueden aplicar a métodos en clases o estructuras. Nada de interfaces (¡tampoco tendría mucho sentido!).
  • Si un método virtual está marcado como Conditional, las reescrituras de éste realizadas desde clases descendientes también lo estarán.  Es bastante lógico, puesto que así se mantienen las dependencias íntegras.
  • Si el atributo se aplica a un método, éste no puede ser un reemplazo del comportamiento de un antecesor (o sea, que no puede ser un override). También resulta muy lógico.

En resumen, se trata de un buen método para incluir código condicional en nuestros desarrollos, dependiente del contexto de compilación, evitando tener que usar directivas #if… #endif. Pero no lo olvidéis: cuidado con los efectos laterales citados.


Publicado en: Variable not found.

martes, 14 de julio de 2009

Formulario PDFUn post en .NET Answers me ha recordado que hace tiempo tenía pendiente escribir una entrada para comentar la técnica que he utilizado en más de una ocasión para generar documentos PDF desde mis aplicaciones .NET de forma muy sencilla, y que puede aplicarse en escenarios donde se conozca de antemano el diseño del documento a imprimir y sólo sea necesario introducir información concreta en espacios muy definidos. Un caso muy habitual es el rellenado de formularios o impresos, aunque usando un poco la imaginación seguro que podéis encontrarle muchas más utilidades.

La técnica consiste en crear, utilizando alguna herramienta de diseño como Adobe Acrobat, un documento PDF que contenga todos los contenidos estáticos del documento que deseamos generar. En cada zona donde queremos inyectar contenido deberemos introducir un campo de formulario (por ejemplo, “nombre”, “apellidos”, etc.) adaptando el tipo de letra, límites y las propiedades del campo que sean necesarias.

imageYa desde código el procedimiento será bien sencillo: abrimos la plantilla, introducimos los valores en cada uno de los campos, y hacemos con el documento resultante lo que nos convenga según la ocasión: enviarlo por email, salvarlo a disco, ofrecerlo para la descarga, etc.

Para conseguir estos objetivos utilizaremos iTextSharp, una adaptación para .NET de la librería iText, muy conocida en el mundo Java. Por tanto, en primer lugar, es necesario descargarla desde Sourceforge, y referenciarla en el proyecto desde el cual vamos a utilizarla.

El siguiente código implementa un método realiza el procedimiento descrito:

   1: public void FillPDF(string templateFile, Stream stream)
   2: {
   3:     // Abrimos la plantilla y creamos una copia, sobre
   4:     // la cual trabajaremos...
   5:     PdfReader reader = new PdfReader(templateFile);
   6:     PdfStamper stamp = new PdfStamper(reader, stream);
   7:  
   8:     // Introducimos el valor en los campos del formulario...
   9:     stamp.AcroFields.SetField("Nombre", "Juan");
  10:     stamp.AcroFields.SetField("Apellidos", "Rodríguez Méndez");
  11:  
  12:     // Fijamos los valores y enviamos el resultado al stream...
  13:     stamp.FormFlattening = true;
  14:     stamp.Close(); 
  15: }

Así, para almacenar la plantilla con los datos en un nuevo PDF, bastaría con invocar al método anterior de la siguiente manera:


   1: Stream file = new FileStream("FormularioRelleno.pdf", FileMode.Create);
   2: FillPDF("plantilla.pdf", file);

O si queremos que desde una aplicación ASP.NET el usuario pudiera descargarlo directamente, podríamos utilizar un código como el siguiente:



   1: protected void btnGenerarPDF_Click(object sender, EventArgs e)
   2: {
   3:     Response.Clear();
   4:     Response.ContentType = "application/pdf";
   5:     Response.AddHeader("content-disposition", "attachment;filename=Formulario.pdf");
   6:     FillPDF(Server.MapPath("Plantilla.pdf"), Response.OutputStream);
   7: }

Por último, sólo comentar que iTextSharp no sólo es útil para rellenar formularios, ni mucho menos. Se trata de una librería muy potente que permite la creación y edición al vuelo de documentos PDF completos, y dispone de un complejo amplísimo API que nos permite hacer casi de todo con ellos. Además, se distribuye bajo licencias LGPL y MPL, muy permisivas ambas, por lo que pueden ser utilizadas en prácticamente cualquier tipo de sistemas.

Para el que no se le apetezca teclear mucho, ahí va una solución para Visual Studio 2005 con dos proyectos, uno web y otro de consola, demostrando el funcionamiento.






Publicado en: Variable not found.

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.