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
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.

lunes, 11 de mayo de 2009

Joe Cartano, perteneciente al Visual Web Development Team de Microsoft, ha publicado en el blog oficial (Visual Web Developer Team Blog) un post anunciando que ha actualizado la plantilla que permite crear directamente proyectos ASP.NET MVC con tests unitarios NUnit.

Así, una vez instalado, al crear un proyecto de tipo ASP.NET MVC nos aparecerá un cuadro de diálogo como el siguiente, en el que se nos brindará la oportunidad de crear en la solución un proyecto de pruebas unitarias utilizando NUnit:

Crear proyecto de pruebas unitarias

Para los poseedores de algunas de las versiones profesionales de Visual Studio 2008, esta ventana no es nueva, puesto que por defecto ya se incluyen los proyectos de tests propios de Microsoft, pero sí lo es para la versión express del IDE, que no incluye el framework de pruebas y en el que había que crear el proyecto a mano.

El proyecto, que se crea automáticamente, realiza 27 tests sobre la plantilla original de proyectos ASP.NET MVC, y pueden servir como base para seguir creando nuestro propio conjunto de pruebas:

¡La plantilla ASP.NET MVC valida!

La plantilla se instala descargando el archivo .zip cuyo enlace encontraréis en el post original y ejecutando, si es necesario con un usuario con privilegios de administrador, el script installNUnit.cmd disponible en el raíz del directorio donde lo hayáis descomprimido.

Para que todo vaya bien, debéis contar con NUnit previamente instalado en vuestro equipo. Pero ojo, las plantillas están preparadas para la versión 2.4.8.0 y la versión actual es la 2.5.0.9122, por lo que puede que no os funcionen bien si estáis a la última.

En este caso, es necesario tocar a mano los archivos de plantillas. Podéis seguir estos pasos:

  1. Descargad el archivo de plantillas desde el blog del Visual Web Developer Team.
  2. Descomprimidlo sobre cualquier carpeta.
  3. En los subdirectorios CSharp y Visual Basic encontraréis un archivo llamado MvcApplication.NUnit.Tests.zip. Son las plantillas para cada uno de esos lenguajes.
  4. El interior de dicho zip está el archivo de proyecto (MvcApplication.NUnit.Test.vbproj en el caso de Visual Basic, MvcApplication.NUnit.Test.csproj en C#). Descomprimid sólo estos archivos y buscar en ambos la cadena “2.4.8.0” y sustituirla por la versión de NUnit que tengáis instalada (en mi caso, “2.5.0.9122”). Al acabar, actualizad de nuevo el .zip con el archivo que acabáis de editar.
  5. Ejecutad el script de instalación installNUnit.cmd.

Publicado en: Variable not found.

domingo, 3 de mayo de 2009

Existen numerosas aplicaciones que permiten analizar nuestros desarrollos con el objetivo final de incrementar la calidad de los mismos. FxCop, por ejemplo, es capaz de analizar los ensamblados y avisarnos cuando se encuentra con incumplimientos de las pautas de diseño para desarrolladores de librerías para .Net Framework (Design Guidelines for Class Library Developers). También hace tiempo comenté por aquí la disponibilidad de  Microsoft Source Analysis for C#, una herramienta que se centra en el código fuente y el cumplimiento de reglas de codificación.

Ahora, gracias al ofrecimiento de Patrick Smacchia, lead developer del producto, he podido probar NDepend, una herramienta de análisis de código de la que había oído hablar y que va mucho más allá que las citadas anteriormente.

La principal misión de NDepend es ayudarnos a incrementar la calidad de nuestros sistemas desarrollados con .NET mediante el análisis de sus ensamblados y código fuente desde distintas perspectivas, como la complejidad de sus módulos, el tamaño de los mismos, las interdependencias entre ellos, etc. Es decir, a diferencia de otros analizadores, su objetivo no es avisarnos de aspectos como la utilización de convenciones de codificación (aunque también puede hacerlo), o el uso de buenas prácticas de diseño, sino ayudarnos a determinar, por ejemplo, cuándo ponemos en peligro la mantenibilidad y evolución de un sistema debido a la complejidad  o al fuerte acoplamiento de algunos de sus componentes, por citar sólo algunos criterios.

El entorno gráfico

VisualNDepend es el entorno gráfico de la herramienta, y nos ofrece un entorno de trabajo muy potente e intuitivo, aunque durante los primeros minutos pueda resultar algo complejo debido a la cantidad de información mostrada.

Iniciar el análisis de un sistema es muy sencillo; una vez seleccionado el ensamblado, conjunto de ensamblados o proyecto a estudiar, el sistema realiza el análisis, del cual se obtiene un informe bastante completo, basado en web, sobre el mismo (podéis ver un ejemplo de informe en la página del producto). En él se recogen:

  • métricas de la aplicación, entre las que encontramos el número de líneas de código, comentarios, número de clases, métodos, etc., así como datos estadísticos relativos a la complejidad, extensión y estructura del código.
  • métricas por ensamblado, donde se refleja, por cada uno de los ensamblados que componen la solución, datos sobre su tamaño, grados de cohesión, acoplamiento, y otros aspectos relativos a su complejidad e interdependencia.
  • Vista de estructura vista de estructura (captura de la derecha) que muestra la distribución de componentes, la granularidad y su complejidad relativa según el tamaño de los bloques visualizados.
  • diagrama de abstracción e inestabilidad, que posiciona cada ensamblado en función del número de clases abstractas e interfaces que presenta y su dependencia del resto de elementos.
  • relación entre ensamblados, que detalla las interdependencias entre ensamblados del proyecto, los posibles ciclos, así como un posible orden de generación.
  • consultas y restricciones CQL, que realiza una serie de consultas predefinidas sobre los ensamblados y el código que nos ayuda a detectar una infinidad de problemas en nuestros desarrollos, desde aspectos relativamente simples como el exceso de métodos en clases o el incumplimiento de ciertas convenciones de nombrado, hasta problemas en la cobertura de los tests generados con NCover o Visual Studio Team System.

Pero lo mejor del entorno gráfico no es poder generar un análisis en formato web para poder consultarlo más adelante, de hecho esto puede conseguir también con la aplicación de consola que incluye NDepend. Lo mejor son las fantásticas herramientas interactivas que nos permiten navegar a través de nuestras aplicaciones, cambiar de vista, ampliar información sobre cualquier elemento, y realizar consultas en tiempo real, siempre ofreciendo unos resultados muy claros y visuales, como:

Diagrama de dependencias

  • diagrama de dependencias entre todo tipo de elementos, como clases, espacios de nombres, o ensamblados. Resulta muy útil, además, configurar el tamaño de los bloques, el grosor del borde y el de las flechas de unión para que sean proporcionales a la complejidad, tamaño y una larga lista de criterios.
  • Tabla de dependencias matriz de dependencias, que muestra de forma visual las relaciones de utilización entre espacios de nombres, tipos, métodos o propiedades, con posibilidad de ir ampliando información.
  • comparación entre ensamblados, mostrándonos los cambios producidos entre, por ejemplo, dos versiones de una misma librería o aplicación.
  • navegación avanzada por el código a través del uso del menú contextual que facilita la rápida localización de referencias, directas e indirectas, hacia y desde un método, propiedad o tipo existente.
  • enlace con Reflector, una herramienta indispensable, con la que se integra perfectamente gracias a su plugin.

 

CQL (Code Query Language)

Sin duda, una de las características más interesante que tiene NDepend es el soporte del lenguaje de consulta CQL (Code Query Language), que nos ofrece la posibilidad de tratar nuestro código y ensamblados como si fuesen una gigantesca base de datos sobre la que podemos realizar consultas de forma muy natural.  Las posibilidades que esto ofrece son tan amplias que prácticamente todas las funcionalidades de la aplicación están basadas en órdenes CQL prediseñadas que acompañan al producto, aunque podemos crear todas las consultas personalizadas que necesitemos, como por ejemplo:

/* Obtiene los métodos que escriben una propiedad */
SELECT METHODS WHERE IsDirectlyWritingField "Model.Cliente.Nombre"

/* Obtiene métodos que acceden incorrectamente a los
datos desde la capa de interfaz
*/
SELECT METHODS FROM NAMESPACES "Interfaz" WHERE IsDirectlyUsing "MySql.Data"

/* Obtiene los 10 métodos con más líneas de código */
SELECT TOP 10 METHODS ORDER BY NbLinesOfCode DESC

/* Obtiene los métodos considerados "peligrosos" según su complejidad ciclomática */
SELECT METHODS WHERE CyclomaticComplexity > 20


Editor de CQL con intellisenseAdemás de consultas CQL cuyo resultado podremos observar de forma directa tanto visualmente como en forma de listado, es posible incluir restricciones (WARN) que hará que el sistema nos alerte durante el análisis de un proyecto que cumpla las condiciones especificadas. Por ejemplo, la siguiente restricción nos avisará cuando exista un cierto tufillo a complejidad excesiva en un método (que haya sido definido con más de 10 parámetros):

WARN IF Count > 0 IN  SELECT METHODS WHERE NbParameters > 10

Estas consultas pueden ser añadidas (en la versión Pro) e integradas en los análisis, así como modificar las existentes, de forma que el producto puede ser personalizado a nuestras convenciones o necesidades específicas.

Otra posibilidad es incluir dentro del código de un ensamblado las restricciones que deseamos que se cumplan, expresándolas en lenguaje CQL embebido en un atributo de tipo CQLConstraint aplicado a sus elementos:

Afortunadamente existe mucha información disponible en la web sobre el lenguaje CQL, y consultas que nos muestran cómo sacarle el máximo partido.

Instalación e integraciones

NDepend se distribuye en formato .zip, y que puede ejecutarse directamente una vez descomprimido el contenido sobre una carpeta. La licencia, una vez obtenida, es un archivo XML firmado digitalmente que debemos colocar en el directorio de la aplicación, y listo.

Integración con VS y Reflector

Ya en ejecución, desde el menú “options” es posible configurarlo para que se integre con Visual Studio 2005, 2008 y como he comentado anteriormente, con el magnífico Reflector, con el que hace una excelente pareja.

Asimismo, es posible utilizarlo en sistemas como MSBuild y NAnt, facilitando así su uso en entornos de integración continua y montaje automatizado, para los que ofrece librerías

Finalmente, he de aclarar que NDepend es una aplicación comercial, aunque dispone de una versión limitada gratuita utilizable por universidades, desarrolladores open source e incluso, durante un tiempo determinado, de prueba en proyectos comerciales.

En cualquier caso, no dudéis en probarlo: os dará un control sobre vuestro código difícil de conseguir con otras herramientas.


Publicado en: www.variablenotfound.com

martes, 2 de diciembre de 2008
Código fuenteAl escribir el post "Métodos genéricos en C#", estuve pensando en tratar este tema también en VB.NET de forma simultánea, pero al final preferí limitarme a C# para no hacer la entrada más extensa de lo que ya iba a resultar de por sí.

Esto, unido a un comentario de Julio sobre el propio post en el que preguntaba si existía algo parecido en Visual Basic .NET, ha hecho que reedite el mismo, pero centrándome esta vez en dicho lenguaje.

Los métodos genéricos son interesantes herramientas que están con nosotros desde los tiempos del .NET Framework 2.0 y pueden resultarnos muy útiles de cara a la construcción de frameworks o librerías reutilizables.

Podríamos considerar que un método genérico es a un método tradicional lo que una clase genérica a una tradicional; por tanto, se trata de un mecanismo de definición de métodos con tipos parametrizados, que nos ofrece la potencia del tipado fuerte en sus parámetros y devoluciones aun sin conocer los tipos concretos que utilizaremos al invocarlos.

Vamos a profundizar en el tema desarrollando un ejemplo, a través del cual podremos comprender por qué los métodos genéricos pueden sernos muy útiles para solucionar determinado tipo de problemas, y describiremos ciertos aspectos, como las restricciones o la inferencia, que nos ayudarán a sacarles mucho jugo.

Escenario de partida

Como sabemos, los métodos tradicionales trabajan con parámetros y retornos fuertemente tipados, es decir, en todo momento conocemos los tipos concretos de los argumentos que recibimos y de los valores que devolvemos. Por ejemplo, en el siguiente código, vemos que el método Maximo, cuya misión es obvia, recibe dos valores Integer y retorna un valor del mismo tipo:
  Function Maximo(ByVal uno As Integer, ByVal otro As Integer) _
As Integer
If uno > otro Then Return uno
Return otro
End Function
 
Hasta ahí, todo correcto. Sin embargo, está claro que retornar el máximo de dos valores es una operación que podría ser aplicada a más tipos, prácticamente a todos los que pudieran ser comparados. Si quisiéramos generalizar este método y hacerlo accesible para otros tipos, se nos podrían ocurrir al menos dos formas de hacerlo.

La primera sería realizar un buen puñado de sobrecargas del método para intentar cubrir todos los casos que se nos puedan dar:

Function Maximo(ByVal uno As Integer, ByVal otro As Integer) _
As Integer
' ...
End Function
Function Maximo(ByVal uno As Long, ByVal otro As Long) _
As Long
' ...
End Function
Function Maximo(ByVal uno As Decimal, ByVal otro As Decimal) _
As Decimal
' ...
End Function

' Y así hasta que te aburras...
 
Obviamente, sería un trabajo demasiado duro para nosotros, desarrolladores perezosos como somos. Además, según Murphy, por más sobrecargas que creáramos seguro que siempre nos faltaría al menos una: justo la que vamos a necesitar ;-).

Otra posibilidad sería intentar generalizar utilizando las propiedades de la herencia. Es decir, si asumimos que tanto los valores de entrada del método como su retorno son del tipo base Object, aparentemente tendríamos el tema resuelto. Lamentablemente, al finalizar nuestra implementación nos daríamos cuenta de que no es posible hacer comparaciones entre dos Object's, por lo que, o bien incluimos en el cuerpo del método código para comprobar que ambos sean comparables (consultando si implementan IComparable), o bien elevamos el listón de entrada a nuestro método, así:
  Function Maximo(ByVal uno As IComparable, ByVal otro As Object) As Object
If uno.CompareTo(otro) > 0 Then Return uno
Return otro
End Function
 
Pero efectivamente, como ya habréis notado, esto tampoco sería una solución válida para nuestro caso. En primer lugar, el hecho de que ambos parámetros sean Object o IComparable no asegura en ningún momento que sean del mismo tipo, por lo que podría invocar el método enviándole, por ejemplo, un String y un Integer, lo que provocaría un error en tiempo de ejecución. Y aunque es cierto que podríamos incluir código que comprobara que ambos tipos son compatibles, ¿no tendríais la sensación de estar llevando a tiempo de ejecución problemática de tipado que bien podría solucionarse en compilación?

El método genérico

Fijaos que lo que andamos buscando es simplemente alguna forma de representar en el código una idea conceptualmente tan sencilla como: "mi método va a recibir dos objetos de un tipo cualquiera T, que implemente IComparable, y va a retornar el que sea mayor de ellos". En este momento es cuando los métodos genéricos acuden en nuestro auxilio, permitiendo definir ese concepto como sigue:
  Function Maximo(Of T As IComparable) _
(ByVal uno As T, ByVal otro As T) As T
If uno.CompareTo(otro) > 0 Then Return uno
Return otro
End Function
 
En el código anterior, podemos distinguir una porción de código que aparece resaltada justo después del nombre del método, y antes de comenzar a definir sus parámetros. Es la forma de indicar que Maximo es un método genérico y operará sobre un tipo cualquiera al que llamaremos T, y mediante una restricción estamos indicando que deberá implementar obligatoriamenter el interfaz IComparable (más adelante trataremos esto en profundidad).

A continuación, podemos observar que los dos parámetros de entrada son del tipo T, así como el retorno de la función. Si no lo ves claro, sustituye mentalmente la letra T por Integer (por ejemplo) y seguro que mejora la cosa.

Lógicamente, estos métodos pueden presentar un número indeterminado de parámetros genéricos, como en el siguiente ejemplo. Observad que la palabra clave Of sólo se indica al principio:
  Function MiMetodo(Of T1, T2, TResult) _
(ByVal par1 As T1, ByVal par2 As T2) As TResult
 
Y una aclaración antes de continuar: lo de usar la letra T para identificar el tipo es pura convención, podríamos llamarlo de cualquier forma (por ejemplo Maximo(Of MiTipo)(ByVal uno as MiTipo, ByVal otro as MiTipo) As MiTipo), aunque ceñirse a las convenciones de codificación es normalmente una buena idea.

Restricciones en parámetros genéricos

Retomemos un momento el código de nuestro método genérico:
  Function Maximo(Of T As IComparable) _
(ByVal uno As T, ByVal otro As T) As T
If uno.CompareTo(otro) > 0 Then Return uno
Return otro
End Function
 
Antes había comentado que en este caso estabamos creando un método que podría actuar sobre cualquier tipo, aunque mediante una restricción forzábamos a que éste implementara, obligatoriamente, el interfaz IComparable, lo que nos permitiría realizar la operación de comparación que necesitamos.

Obviamente, las restricciones no son obligatorias; de hecho, sólo debemos utilizarlas cuando necesitemos limitar de alguna forma los tipos permitidos como parámetros genéricos, como en el ejemplo anterior. Está permitida la utilización de las siguientes reglas:
  • As Structure, indica que el argumento debe ser un tipo valor.
  • As Class, indica que T debe ser un tipo referencia.
  • As New, fuerza a que el tipo T disponga de un constructor público sin parámetros; es útil cuando desde dentro del método se pretende instanciar un objeto del mismo.
  • As nombredeclase, indica que el argumento debe heredar o ser de dicho tipo.
  • As nombredeinterfaz, el argumento deberá implementar el interfaz indicado.
  • As nombredetipogenérico, indica que el argumento al que se aplica debe ser igual o heredar del tipo, también argumento del método, indicado por nombredetipogenérico (observad en el siguiente ejemplo el parámetro T2).
Un último detalle relativo a esto: a un mismo parámetro se pueden aplicar varias restricciones, en cuyo caso se introducirán entre llaves ("{" y "}") separadas por comas, como aparece en el siguiente ejemplo:
  Function MiMetodo(Of T1 As {Class, IEnumerable}, _
T2 As {T1, New}, _
TResult As New) _
(ByVal par1 As T1, ByVal par2 As T2) As TResult
' ... Cuerpo del método
End Function
 

Uso de métodos genéricos

A estas alturas ya sabemos, más o menos, cómo se define un método genérico, pero nos falta aún conocer cómo podemos consumirlos, es decir, invocarlos desde nuestras aplicaciones. Aunque puede intuirse, la llamada a los métodos genéricos debe incluir tanto la tradicional lista de parámetros del método como los tipos que lo concretan. Vemos unos ejemplos:
  Dim mazinger As String = Maximo(Of String)("Mazinger", "Afrodita")
Dim i99 As Integer = Maximo(Of Integer)(2, 99)
 
Una interesantísima característica de la invocación de estos métodos es la capacidad del compilador para inferir, en muchos casos, los tipos que debe utilizar como parámetros genéricos, evitándonos tener que indicarlos de forma expresa. El siguiente código, totalmente equivalente al anterior, aprovecha esta característica:
  Dim mazinger As String = Maximo("Mazinger", "Afrodita")
Dim i99 As Integer = Maximo(2, 99)
 
El compilador deduce el tipo del método genérico a partir de los que estamos utilizando en la lista de parámetros. Por ejemplo, en el primer caso, dado que los dos parámetros son String, puede llegar a la conclusión de que el método tiene una signatura que coincide con la definición del genérico, utilizando String como tipo parametrizado.

Otro ejemplo de método genérico

Veamos un ejemplo un poco más complejo. El método CreaLista, aplicable a cualquier clase, retorna una lista genérica (List(Of T)) del tipo parametrizado del método, que rellena inicialmente con los argumentos (variables) que se le suministra:
  Function CreaLista(Of T)(ByVal ParamArray pars() As T) As List(Of T)
Dim list As New List(Of T)
For Each elem As T In pars
list.Add(elem)
Next
Return list
End Function

' ...
' Uso:

Dim nums = CreaLista(Of Integer)(1, 2, 3, 4, 5, 6, 7)
Dim noms = CreaLista(Of String)("Pepe", "Juan", "Luis")
 
Otros ejemplos de uso, ahora beneficiándonos de la inferencia de tipos:
  Dim nums = CreaLista(1, 2, 3, 4, 5, 6, 7)
Dim noms = CreaLista("Pepe", "Juan", "Luis")

' Incluso con tipos anónimos de VB.NET 9
Dim v = CreaLista( _
New With {.X = 1, .Y = 2}, _
New With {.X = 3, .Y = 4} _
)
Console.WriteLine(v(1).Y) ' Muestra "4"
 
En resumen, se trata de una característica de la plataforma .NET, reflejada en lenguajes como C# y VB.Net, que está siendo ampliamente utilizada en las últimas incorporaciones al framework, y a la que hay que habituarse para poder trabajar eficientemente con ellas.

Publicado en: www.variablenotfound.com.
domingo, 30 de noviembre de 2008
Es habitual que las aplicaciones que desarrollamos necesiten enviar emails: alertas, notificaciones automáticas, formularios de contacto, o envíos masivos de información, entre otros, son ejemplos de utilización muy habituales.

Y en estos casos la inclusión de imágenes incrustadas suele ser un requisito fundamental cuando se trata de enviar contenidos con formato HTML, de forma que, aunque normalmente se incrementa de forma notable el tamaño del paquete a enviar, se evita que los clientes tengan que descargar estos recursos adicionales desde sus equipos, cosa que además suele estar bloqueada por defecto.

.NET framework nos ofrece varias vías para hacerlo usando las clases provistas en System.Net.Mail, pero vamos a utilizar una que nos ofrece dos ventajas importantes. La primera es que las imágenes enviadas no se muestran como adjuntos (evitando, por ejemplo, el curioso efecto presente en Outlook Express, que repite las imágenes a continuación del texto del mensaje) y la segunda es que permite especificar distintas vistas dentro desde el mismo mensaje, para que el cliente de correo utilice la que sea más apropiada.

El siguiente código muestra una forma de montar un mensaje con dos vistas: la primera será utilizada por aquellos agentes de usuario (clientes de correo) que únicamente pueden mostrar texto plano, mientras que en la segunda utilizará HTML para maquetar el contenido, incluyendo una imagen que aparecerá totalmente integrada en el cuerpo del mensaje, sin mostrarse como elemento adjunto del mismo.

Podréis observar que aunque en el ejemplo muestro un código muy rígido, es fácilmente generalizable para poder utilizarlo en cualquier escenario. Como en otras ocasiones, está en C#, mi lenguaje favorito, pero sería fácilmente portable a VB.NET, por ejemplo.

// Necesitaremos estos namespaces...
using System.Net.Mail;
using System.Net.Mime;
...

// Montamos la estructura básica del mensaje...
MailMessage mail = new MailMessage();
mail.From = new MailAddress("origen@miservidor.com");
mail.To.Add("destinatario@miservidor.com");
mail.Subject = "Mensaje con imagen";

// Creamos la vista para clientes que
// sólo pueden acceder a texto plano...


string text = "Hola, ayer estuve disfrutando de "+
"un paisaje estupendo.";

AlternateView plainView =
AlternateView.CreateAlternateViewFromString(text,
Encoding.UTF8,
MediaTypeNames.Text.Plain);


// Ahora creamos la vista para clientes que
// pueden mostrar contenido HTML...


string html = "<h2>Hola, mira dónde estuve ayer:</h2>" +
"<img src='cid:imagen' />";

AlternateView htmlView =
AlternateView.CreateAlternateViewFromString(html,
Encoding.UTF8,
MediaTypeNames.Text.Html);

// Creamos el recurso a incrustar. Observad
// que el ID que le asignamos (arbitrario) está
// referenciado desde el código HTML como origen
// de la imagen (resaltado en amarillo)...


LinkedResource img =
new LinkedResource(@"C:\paisaje.jpg",
MediaTypeNames.Image.Jpeg);
img.ContentId = "imagen";

// Lo incrustamos en la vista HTML...

htmlView.LinkedResources.Add(img);

// Por último, vinculamos ambas vistas al mensaje...

mail.AlternateViews.Add(plainView);
mail.AlternateViews.Add(htmlView);

// Y lo enviamos a través del servidor SMTP...

SmtpClient smtp = new SmtpClient("smtp.miservidor.com");
smtp.Send(mail);
 

La siguiente imagen muestra una captura de pantalla del mismo mensaje leído desde un cliente con capacidad HTML como Outlook Express y uno que no la tiene, en este caso basado en web:

El mismo mensaje visto desde dos agentes de usuario distintos. Pulsa para ampliar.


Publicado en: www.variablenotfound.com.
martes, 18 de noviembre de 2008
Código fuenteLos métodos genéricos son interesantes herramientas que están con nosotros desde los tiempos del .NET Framework 2.0 y pueden resultarnos muy útiles de cara a la construcción de frameworks o librerías reutilizables.

Podríamos considerar que un método genérico es a un método tradicional lo que una clase genérica a una tradicional; por tanto, se trata de un mecanismo de definición de métodos con tipos parametrizados, que nos ofrece la potencia del tipado fuerte en sus parámetros y devoluciones aun sin conocer los tipos concretos que utilizaremos al invocarlos.

Vamos a profundizar en el tema desarrollando un ejemplo, a través del cual podremos comprender por qué los métodos genéricos pueden sernos muy útiles para solucionar determinado tipo de problemas, y describiremos ciertos aspectos, como las restricciones o la inferencia, que nos ayudarán a sacarles mucho jugo.

Escenario de partida

Como sabemos, los métodos tradicionales trabajan con parámetros y retornos fuertemente tipados, es decir, en todo momento conocemos los tipos concretos de los argumentos que recibimos y de los valores que devolvemos. Por ejemplo, en el siguiente código, vemos que el método Maximo, cuya misión es obvia, recibe dos valores integer y retorna un valor del mismo tipo:
  public int Maximo(int uno, int otro)
{
if (uno > otro) return uno;
return otro;
}
 
Hasta ahí, todo correcto. Sin embargo, está claro que retornar el máximo de dos valores es una operación que podría ser aplicada a más tipos, prácticamente a todos los que pudieran ser comparados. Si quisiéramos generalizar este método y hacerlo accesible para otros tipos, se nos podrían ocurrir al menos dos formas de hacerlo.

La primera sería realizar un buen puñado de sobrecargas del método para intentar cubrir todos los casos que se nos puedan dar:
  public int Maximo(int uno, int otro) { ... }
public long Maximo(long uno, long otro) { ... }
public string Maximo(string uno, string otro) { ... }
public float Maximo(float uno, float otro) { ... }
// Hasta que te aburras...
 
Obviamente, sería un trabajo demasiado duro para nosotros, desarrolladores perezosos como somos. Además, según Murphy, por más sobrecargas que creáramos seguro que siempre nos faltaría al menos una: justo la que vamos a necesitar ;-).

Otra posibilidad sería intentar generalizar utilizando las propiedades de la herencia. Es decir, si asumimos que tanto los valores de entrada del método como su retorno son del tipo base object, aparentemente tendríamos el tema resuelto. Lamentablemente, al finalizar nuestra implementación nos daríamos cuenta de que no es posible hacer comparaciones entre dos object's, por lo que, o bien incluimos en el cuerpo del método código para comprobar que ambos sean comparables (consultando si implementan IComparable), o bien elevamos el listón de entrada a nuestro método, así:
  public object Maximo(IComparable uno, object otro)
{
if (uno.CompareTo(otro) > 0) return uno;
return otro;
}
 
Pero efectivamente, como ya habréis notado, esto tampoco sería una solución válida para nuestro caso. En primer lugar, el hecho de que ambos parámetros sean object o IComparable no asegura en ningún momento que sean del mismo tipo, por lo que podría invocar el método enviándole, por ejemplo, un string y un int, lo que provocaría un error en tiempo de ejecución. Y aunque es cierto que podríamos incluir código que comprobara que ambos tipos son compatibles, ¿no tendríais la sensación de estar llevando a tiempo de ejecución problemática de tipado que bien podría solucionarse en compilación?

El método genérico

Fijaos que lo que andamos buscando es simplemente alguna forma de representar en el código una idea conceptualmente tan sencilla como: "mi método va a recibir dos objetos de un tipo cualquiera T, que implemente IComparable, y va a retornar el que sea mayor de ellos". En este momento es cuando los métodos genéricos acuden en nuestro auxilio, permitiendo definir ese concepto como sigue:
  public T Maximo<T>(T uno, T otro) where T: IComparable
{
if (uno.CompareTo(otro) > 0) return uno;
return otro;
}
 
En el código anterior, podemos distinguir el parámetro genérico T encerrado entre ángulos "<" y ">", justo después del nombre del método y antes de comenzar a describir los parámetros. Es la forma de indicar que Maximo es genérico y operará sobre un tipo cualquiera al que llamaremos T; lo de usar esta letra es pura convención, podríamos llamarlo de cualquier forma (por ejemplo MiTipo Maximo<MiTipo>(MiTipo uno, MiTipo otro)), aunque ceñirse a las convenciones de codificación es normalmente una buena idea.

A continuación, podemos observar que los dos parámetros de entrada son del tipo T, así como el retorno de la función. Si no lo ves claro, sustituye mentalmente la letra T por int (por ejemplo) y seguro que mejora la cosa.

Lógicamente, estos métodos pueden presentar un número indeterminado de parámetros genéricos, como en el siguiente ejemplo:
  public TResult MiMetodo<T1, T2, TResult>(T1 param1, T2 param2)
{
// ... cuerpo del método
}
 

Restricciones en parámetros genéricos

Retomemos un momento el código de nuestro método genérico Maximo:
  public T Maximo<T>(T uno, T otro) where T: IComparable
{
if (uno.CompareTo(otro) > 0) return uno;
return otro;
}
 
Vamos a centrarnos ahora en la porción final de la firma del método anterior, donde encontramos el código where T: IComparable. Se trata de una restricción mediante la cual estamos indicando al compilador que el tipo T podrá ser cualquiera, siempre que implementente el interfaz IComparable, lo que nos permitirá realizar la comparación.

Existen varios tipos de restricciones que podemos utilizar para limitar los tipos permitidos para nuestros métodos parametrizables:
  • where T: struct, indica que el argumento debe ser un tipo valor.
  • where T: class, indica que T debe ser un tipo referencia.
  • where T: new(), fuerza a que el tipo T disponga de un constructor público sin parámetros; es útil cuando desde dentro del método se pretende instanciar un objeto del mismo.
  • where T: nombredeclase, indica que el argumento debe heredar o ser de dicho tipo.
  • where T: nombredeinterfaz, el argumento deberá implementar el interfaz indicado.
  • where T1: T2, indica que el argumento T1 debe ser igual o heredar del tipo, también argumento del método, T2.
Un último detalle relativo a esto: a un mismo parámetro se pueden aplicar varias restricciones, en cuyo caso se separarán por comas, como aparece en el siguiente ejemplo:
  public TResult MiMetodo<T1, T2, TResult>(T1 param1, T2 param2)
where TResult: IEnumerable
where T1: new(), IComparable
where T2: IComparable, ICloneable
{
// ... cuerpo del método
}
 
En cualquier caso, las restricciones no son obligatorias. De hecho, sólo debemos utilizarlas cuando necesitemos restringir los tipos permitidos como parámetros genéricos, como en el ejemplo del método Maximo<T>, donde es la única forma que tenemos de asegurarnos que las instancias que nos lleguen en los parámetros puedan ser comparables.

Uso de métodos genéricos

A estas alturas ya sabemos, más o menos, cómo se define un método genérico, pero nos falta aún conocer cómo podemos consumirlos, es decir, invocarlos desde nuestras aplicaciones. Aunque puede intuirse, la llamada a los métodos genéricos debe incluir tanto la tradicional lista de parámetros del método como los tipos que lo concretan. Vemos unos ejemplos:
  string mazinger = Maximo<string>("Mazinger", "Afrodita");  
int i99 = Maximo<int>(2, 99);
 
Una interesantísima característica de la invocación de estos métodos es la capacidad del compilador para inferir, en muchos casos, los tipos que debe utilizar como parámetros genéricos, evitándonos tener que indicarlos de forma expresa. El siguiente código, totalmente equivalente al anterior, aprovecha esta característica:
  string mazinger = Maximo("Mazinger", "Afrodita");  
int i99 = Maximo(2, 99);
 
El compilador deduce el tipo del método genérico a partir de los que estamos utilizando en la lista de parámetros. Por ejemplo, en el primer caso, dado que los dos parámetros son string, puede llegar a la conclusión de que el método tiene una signatura equivalente a string Maximo(string, string), que coincide con la definición del genérico.

Otro ejemplo de método genérico

Veamos un ejemplo un poco más complejo. El método CreaLista, aplicable a cualquier clase, retorna una lista genérica (List<T>) del tipo parametrizado del método, que rellena inicialmente con los argumentos (variables) que se le suministra:
  public List<T> CreaLista<T>(params T[] pars)
{
List<T> list = new List<T>();
foreach (T elem in pars)
{
list.Add(elem);
}
return list;
}

// ...
// Uso:

List<int> nums = CreaLista<int>(1, 2, 3, 4, 6, 7);
List<string> noms = CreaLista<string>("Pepe", "Juan", "Luis");
 
Otros ejemplos de uso, ahora beneficiándonos de la inferencia de tipos:
  List<int> nums = CreaLista(1, 2, 3, 4, 6, 7);
List<string> noms = CreaLista("Pepe", "Juan", "Luis");

// Incluso con tipos anónimos de C# 3.0:
var p = CreaLista(
new { X = 1, Y = 2 },
new { X = 3, Y = 4 }
);
Console.WriteLine(p[1].Y); // Pinta "4"
 
En resumen, se trata de una característica de la plataforma .NET, reflejada en lenguajes como C# y VB.Net, que está siendo ampliamiente utilizada en las últimas incorporaciones al framework, y a la que hay que habituarse para poder trabajar eficientemente con ellas.

Publicado en: www.variablenotfound.com.