Gerardo Contijoch

Experiencias del día a día trabajando con .NET – ASP.NET, C#, ASP.NET MVC y demas…

Posts Tagged ‘.net’

Inicialización del ModelState en ASP.NET MVC

Posted by Gerardo Contijoch en junio 21, 2009

Hoy mientras trabajaba en un site ASP.NET MVC aprendí una lección que espero no olvidar jamás (su olvido me podría traer muchos dolores de cabeza).

El problema se presentó cuando uno de los campos del form con el que estaba trabajando no se actualizaba luego de un postback. El form no tenia nada de especial, sólo un par de campos, y su posteo provocaba la ejecución de una acción que tampoco hacia nada muy loco. Lo único destacable era que el parámetro con problemas era cargado con un ModelBinder personalizado, pero el mismo funcionaba bien y lo inicializaba correctamente al valor. Lo primero que hice fue verificar que las variables del post lleguen correctamente al server. También verifiqué que no haya ningún código javascript interfiriendo con el llenado de campos. Lo que más me desconcertó fue que dentro de la acción, el parámetro cuyo valor tenia problemas estaba correctamente inicializado, cosa que no esperaba (lo que me llevó a pensar que no había un error en el ModelBinder). Sin embargo había un detalle que pasé de largo: entre los valores del ModelState del request no se encontraba el parámetro con problemas. Fue cuando descubrí eso que supe donde estaba el problema. Un poco más de investigación me confirmó las sospechas. Pero antes de explicarles el problema, veamos el código del ModelBinder:

   1: public class TipoDeOperacionModelBinder : IModelBinder {
   2:     #region IModelBinder Members
   3:
   4:     public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
   5:
   6:         if (bindingContext == null) {
   7:             throw new ArgumentNullException("bindingContext");
   8:         }
   9:
  10:         ValueProviderResult val;
  11:         if (!bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out val)) {
  12:             // Si no encontramos el valor, devolvemos null y ASP.NET MVC se encarga de provocar la excepción necesaria
  13:             return null;
  14:         }
  15:
  16:         int realVal = (int)val.ConvertTo(typeof(int));
  17:         return (TipoDeOperacion)realVal;
  18:     }
  19:
  20:     #endregion
  21: }

Este ModelBinder simplemente toma un valor (indicado por bindingContext.ModelName) y lo transforma en un Enum de tipo TipoDeOperacion. Cabe aclarar que este ModelBinder esta registrado para el tipo TipoDeOperacion, por lo que el propio framework es el encargado de hacer uso del mismo cuando necesite hacer el binding de un parámetro de tipo TipoDeOperacion.

Como dije antes, el parámetro en cuestión se inicializaba correctamente, lo que confirmaba que las variables del post llegaban sin problemas. El problema estaba en que no se encontraba dentro del ModelState y eso se debe sencillamente a que en ningún lugar del código del ModelBinder lo estoy agregando. Resulta que el ModelBinder no solo es responsable de cargar los parámetros de las acciones y actualizar el modelo con el que estamos trabajando, sino que ¡también debe inicializar el ModelState! El resto de los parámetros de la acción no tenían problemas ya que eran procesados por la clase DefaultModelBinder, la cual se encarga de todo. Todo se resolvió cuando agregué el siguiente código en la línea 15:

bindingContext.ModelState.SetModelValue(bindingContext.ModelName, val);

Parece una pavada de detalle, pero esa sola línea de código me hizo perder varias horas de trabajo por no saber que el ModelState era cargado por los ModelBinders. Esto demuestra la importancia de entender bien las herramientas y frameworks con los que uno trabaja. ASP.NET MVC nos provee de montones de puntos de extensión, pero de nada sirve si no los sabemos aprovechar correctamente.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Anuncios

Posted in ASP.NET MVC | Etiquetado: , , | Leave a Comment »

Mis problemas con ASP.NET AJAX

Posted by Gerardo Contijoch en abril 5, 2009

Mi relación con ASP.NET AJAX nunca fue buena. Siempre tuve muchos problemas con la implementación de AJAX de Microsoft. Nunca me gustó usarla, me da la impresión de que es muy pesada y hay que saber demasiado para usarla (los problemas de éste y éste post son un ejemplo de ello). Por cuestiones de trabajo, hace unos meses me vi obligado a usarla y todavía me sigo encontrando con problemas de no muy obvia solución.

Recuerdo cuando usé AJAX por primera vez. Fue con Ajax.NET Professional. Tenia que manejar todo con javascript y no había controles ni nada de eso, el HTML se generaba en el servidor y se enviaba como respuesta al request. Por supuesto que esto no tiene porque ser así necesariamente, se puede generar el HTML en el cliente, pero aún así era mas sencillo de usar. Uno tenia que ensuciarse un poco mas las manos con javascript, pero por lo menos uno tenia control sobre lo que hacía. No me pasa lo mismo con ASP.NET AJAX, no se que es lo que pasa por detrás, no se cuando puede fallar ni donde, el framework inyecta scripts por todos lados… no me termina de convencer. Ojo, no quiero decir que Ajax Pro sea mejor que ASP.NET AJAX, simplemente digo que la solución de Microsoft no me da la seguridad que busco.

Por ejemplo, el caso que mencioné en este post. El error que obtenía era básicamente ‘no se puede cargar el archivo xxxx.js’. Resulta que para cargar un archivo de scripts correctamente (dadas ciertas condiciones, no ocurre siempre) tengo que modificarlo de modo que el mismo notifique que su carga ha finalizado. No es algo muy intuitivo que digamos… Uno puede buscar en muchos lados la respuesta hasta encontrarla.

Otro de los problemas que tengo es el uso de los UpdatePanels. Si no se saben usar, pueden resultar muy peligrosos. Éste post muestra un ejemplo de ello. Creo que una de las características más peligrosas de los UpdatePanels es justamente su facilidad de uso. Da la impresión de que al arrastrar el control a una pagina se nos solucionan muchos problemas, pero podemos estar trayendo muchos mas. Para evitarlo, hay que entender como funcionan y saber como hacerlos funcionar como queremos, pero lamentablemente no es mucha la gente que se toma el trabajo (o tiene tiempo) de leer sobre el funcionamiento interno de los controles y por ello muchas veces son más un problema que una solución.

No se… no me termina de convencer todo esto, supongo que será hasta que me acostumbre…

¿A alguien le sucede lo mismo?

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in ASP.NET, Desarrollo Web | Etiquetado: , , | 4 Comments »

Simplificar la depuración con el uso de DebuggerTypeProxyAttribute

Posted by Gerardo Contijoch en marzo 25, 2009

En el último post les hable sobre DebuggerDisplayAttribute, un atributo que nos permite cambiar la información que nos muestra el debugger de Visual Studio sobre un tipo. Ahora les voy a presentar otro atributo muy similar, pero cuya función es cambiar completamente lo que se muestra de un tipo, incluso sus propiedades. Estoy hablando de DebuggerTypeProxyAttribute. Al aplicar este atributo sobre una clase o estructura lo que hacemos es decirle al debugger que no use nuestra clase (o estructura) cuando inspeccionemos un objeto de ese tipo en el Debugger, sino que use un proxy a la misma. La idea detrás de esto es ocultar los miembros de nuestra clase (porque no son relevantes posiblemente) y mostrar los del proxy, los cuales son los que nos van a interesar ver cuando estemos depurando nuestro código.

Veamos como se vería un posible proxy para una clase Persona (tomada del post anterior y modificada ligeramente) junto a ella:

   1: [DebuggerDisplay("Nombre: {Nombre, nq}, Año de nacimiento: {FechaDeNacimiento.Year}, ¿Es Rosarino?: {Ciudad == \"Rosario\" ? \"Sí\" : \"No\", nq}")]
   2: public class Persona {
   3:     public string Nombre { get; set; }
   4:     public string Apellido { get; set; }
   5:     public int Edad { get; set; }
   6:     public string Ciudad { get; set; }
   7:     public DateTime FechaDeNacimiento { get; set; }
   8:
   9:     public bool EstaCasado { get; set; }
  10:     public Persona Conyuge { get; set; }
  11:
  12:     public Persona Padre { get; set; }
  13:     public Persona Madre { get; set; }
  14:
  15:     public IEnumerable<Persona> Hermanos { get; }
  16:
  17:     public bool PracticaDeportes { get; set; }
  18:     public IEnumerable<string> Deportes { get; }
  19:
  20:     public IEnumerable<string> Estudios { get; }
  21:
  22:     /// <summary>
  23:     /// Proxy para la clase Persona.
  24:     /// </summary>
  25:     internal class PersonaDebugView {
  26:         private Persona Persona { get; set; }
  27:
  28:         public PersonaDebugView(Persona p) {
  29:             Persona = p;
  30:         }
  31:
  32:         public string Nombre {
  33:             get {
  34:                 if (Persona.Madre != null) {
  35:                     return string.Format("{0} {1} {2}", Persona.Nombre, Persona.Apellido, Persona.Madre.Apellido);
  36:                 }
  37:
  38:                 return string.Format("{0} {1}", Persona.Nombre, Persona.Apellido);
  39:             }
  40:         }
  41:
  42:         public string NombreConyuge {
  43:             get {
  44:                 if (Persona.EstaCasado) {
  45:                     return Persona.Conyuge.Nombre;
  46:                 }
  47:
  48:                 return "No tiene conyuge";
  49:             }
  50:         }
  51:
  52:         public bool EsMayorDeEdad {
  53:             get { return Persona.Edad >= 21; }
  54:         }
  55:
  56:         public string PaisDeOrigen {
  57:             get { return RecuperarPaisAPartirDeCiudad(Persona.Ciudad); }
  58:         }
  59:     }
  60: }

Quisiera aclarar un par de puntos en este ejemplo. Primero, vemos que la clase proxy se encuentra dentro de la clase Persona. Esto es recomendable ya de ese modo el proxy puede tener acceso a los miembros privados de la clase que lo contiene y tiene el beneficio extra de que no se mezcla con el resto de las clases de nuestra aplicación. También es un requisito que el constructor del proxy tenga un parámetro del tipo de la clase que se desea ocultar.

Otro detalle que puede haber saltado para los que no leyeron el post anterior (¿qué están esperando?), es el uso de DebuggerDisplayAttribute. Si les interesa saber porque lo uso, pueden encontrar acá el motivo.

Si depuramos nuestra aplicación (aún sin haber aplicado el atributo DebuggerTypeProxyAttribute), veremos que la clase Persona se ve así en el debugger:

DebuggerProxyType-persona

Son muchos datos para una persona y si la mayoría de ellos no nos interesa (recordemos que estamos trabajando sobre un ejemplo) puede volverse un poco incómodo inspeccionar nuestras variables. Veamos ahora como se vería esta pantalla si aplicamos el atributo DebuggerTypeProxyAttribute de la siguiente manera:

   1: [DebuggerTypeProxy(typeof(PersonaDebugView))]
   2: [DebuggerDisplay(/*...*/)]
   3: public class Persona {
   4:     /*...*/
   5: }

DebuggerProxyType-personaconProxy

Las propiedades que estamos viendo son las del proxy (que no tienen que corresponderse necesariamente con las de la clase Persona) y nos muestran una vista totalmente diferente de la variable que estamos inspeccionando.

Presten atención a un nuevo miembro: RawView. Éste último, generado automáticamente, lo que hace es mostrarnos la vista original o sin modificar de nuestra clase y si lo abrimos veremos que tenemos acceso a todas las propiedades de la clase Persona, tal cual la definimos.

El uso de este atributo es muy común en las colecciones en donde se ocultan muchos miembros y solo se muestran unos pocos que son los más relevantes a la hora de trabajar con una colección. Siempre que inspeccionen una variable y vean el miembro RawView, van a estar viendo un proxy para esa clase y no su verdadera implementación.

Aplicando el atributo a nivel de ensamblado

Al igual que con DebuggerDisplayAttribute, DebuggerTypeProxyAttribute puede usarse a nivel de ensamblado exactamente de la misma manera. En el post anterior explico en detalle como se hace esto.

Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0, Visual Studio | Etiquetado: , , | 2 Comments »

DebuggerDisplayAttribute, una gran ayuda durante la depuración

Posted by Gerardo Contijoch en marzo 19, 2009

Hace un tiempo, revisando el código fuente de Castle.Core, uno de los componentes más importantes de Castle Microkernel, redescubrí (porque me había olvidado por completo de el) uno de los atributos más útiles que existen en el .NET Framework, DebuggerDisplayAttribute, el cuál nos permite personalizar la información que se muestra de un objeto o miembro en las ventanas de debug en Visual Studio así como también en los tooltips que aparecen al pasar el cursor sobre una variable.

Imaginemos que tenemos la clase Persona definida de la siguiente manera:

   1: public class Persona {
   2:     public string Nombre { get {/*...*/} set {/*...*/} }
   3:     public int Edad { get{/*...*/} set{/*...*/} }
   4:     public string Ciudad { get {/*...*/} set {/*...*/} }
   5:     public DateTime FechaDeNacimiento { get {/*...*/} set {/*...*/} }
   6: }

Normalmente mientras estamos depurando nuestra aplicación al inspeccionar una variable de tipo Persona la ventana QuickWatch (Inspección rápida) nos muestra la variable de la siguiente manera:

DebuggerDisplayAttribute-personaSinNombre

Se puede ver que el valor (columna Value) mostrado es el propio tipo de la variable, lo cual es redundante ya que el tipo aparece aclarado en su propia columna. En nuestro caso, eso no importa mucho ya que al expandir la variable vamos a ver todas sus propiedades, que es lo que realmente nos interesa, pero imaginen que tienen una lista de personas y precisan ubicar una en particular por alguna de sus propiedades. El trabajo de encontrarla puede ser muy tedioso:

DebuggerDisplayAttribute-personasSinNombre

Por eso para ayudarnos en estos casos esta DebuggerDisplayAttribute, el cual nos permite personalizar el valor mostrado en la columna Value de modo que podamos mostrar información más útil que el tipo de la variable o miembro bajo inspección. La propiedad más importante de este atributo es Value (de tipo string) que no es ni más ni menos que el valor a mostrar en la columna Value. Esta cadena puede contener llaves ({}) entre las cuales se puede especificar el nombre de un miembro cuyo valor queremos mostrar. Veamos como podemos decorar nuestra clase con este atributo:

   1: [DebuggerDisplay("Esta persona es {Nombre}")]
   2: public class Persona {
   3:    /*...*/
   4: }

Como resultado de este cambio, ahora la clase se va a ver así en la ventana de Debug:

DebuggerDisplayAttribute-personasConNombre

Si queremos sacar las comillas de los valores de tipo string, simplemente agregamos nq al miembro al que hacemos referencia:

   1: [DebuggerDisplay("Esta persona es {Nombre, nq}")]
   2: public class Persona {
   3:     /*...*/
   4: }

DebuggerDisplayAttribute-personaConNombreSinComillas

Incluso podemos incluir expresiones simples en nuestra descripción, pero sólo si estamos trabajando con C#:

   1: [DebuggerDisplay("Nombre: {Nombre, nq}, Año de nacimiento: {FechaDeNacimiento.Year}, ¿Es Rosarino?: {Ciudad == \"Rosario\" ? \"Sí\" : \"No\", nq}")]
   2: public class Persona {
   3:     /*...*/
   4: }

DebuggerDisplayAttribute-personaConNombreYExpresion

Otra forma de uso

El hecho de usar este atributo para decorar las clases puede no ser muy bien visto ya que es algo que afecta al entorno de desarrollo y no tiene relación con el contenido de nuestras clases en si. Es ajeno a nuestros sistemas y su presencia no afecta (o al menos no debería afectar) al comportamiento de los mismos. Estamos modificando nuestras clases solo porque queremos que Visual Studio nos las muestre como a nosotros más nos gusta.

Para evitar involucrar la lógica de nuestras clases con la lógica de presentación particular de Visual Studio (otro IDE puede no soportar este atributo e ignorarlo por completo) podemos usar este atributo a nivel de ensamblado y crear un nuevo ensamblado con únicamente la declaración del atributo. De este modo, tendremos que especificar, además de la información a mostrar, la clase o estructura sobre la cual queremos aplicarla mediante la propiedad Target.

Para que quede claro esto último lo voy a explicar. La idea de usarlo a nivel de ensamblado es crear una nueva dll (proyecto de tipo Librería de clases). Este proyecto tiene que referenciar al ensamblado que contiene el tipo que queremos decorar. Esto significa que si queremos decorar el tipo XmlDocument, entonces vamos a tener que referenciar al ensamblado System.Xml.dll, en cambio, si queremos decorar nuestra clase Persona, vamos a tener que referenciar a la dll que genere nuestro proyecto.

Una vez dentro del nuevo proyecto buscamos el archivo AssemblyInfo (o cualquier otro archivo, simplemente me parece que éste es el lugar más apropiado para hacerlo) y agregamos algo similar a esto (yo voy a usar mi ejemplo):

   1: [assembly: DebuggerDisplayAttribute("Nombre: {Nombre, nq}, Año de nacimiento: {FechaDeNacimiento.Year}, ¿Es Rosarino?: {Ciudad == \"Rosario\" ? \"Sí\" : \"No\", nq}",
   2:                                     Target = typeof(DebuggerDisplayAttributeTest.Persona))]

Como se ve, usé la propiedad Target para especificar al tipo al que vamos a decorar.

Ahora solo resta compilar este ensamblado y copiarlo al path de visualizadores de Visual Studio (solo el ensamblado, no sus dependencias). Este path varia dependiendo la versión de Visual Studio y Windows. En el caso de Windows Vista, el path tiene la siguiente forma:

%userprofile%\Documents\Visual Studio [VERSION_VS]\Visualizers

Sobre Windows XP y 2003 (e imagino que en 2000 también) es el siguiente:

%userprofile%\My Documents\Visual Studio [VERSION_VS]\Visualizers

Una vez copiado el ensamblado reiniciamos Visual Studio y listo, ya tenemos nuestras clases decoradas sin necesidad de modificar su código.

Si prestan atención, en la carpeta de visualizadores van a encontrar un archivo llamado autoexp.cs. Si inspeccionan el archivo, van a ver que clases de .NET Framework fueron decoradas con este atributo. Son libres de modificarlo (¡no sin antes hacer un backup!) y agregarle todo lo que quieran, pero es responsabilidad de ustedes compilarlo en algún proyecto.

Nos vemos en el próximo post!

Referencias:

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0, Visual Studio | Etiquetado: , , | 1 Comment »

Ejecutar tareas de forma asincrónica en ASP.NET 2.0 (Parte 2)

Posted by Gerardo Contijoch en enero 30, 2009

En el post anterior hable sobre el método AddOnPreRenderCompleteAsync de la clase Page, el cual nos permite ejecutar una tarea de manera asincrónica en un hilo diferente al que esta procesando la página, pero como vimos entonces, este método tiene la desventaja de que sólo nos permite ejecutar una sola tarea y nada más. Hay muchos casos en los que precisamos realizar diferentes operaciones, no necesariamente relacionadas entre sí y para esos casos hay otra solución. Sugiero la lectura de la primera parte de esta serie ya que parte del código que voy a mostrar acá ya fue explicado allí.

La clase Page tiene un método que nos permite registrar tareas que deseamos que sean ejecutadas independientemente del procesamiento de la página. Estoy hablando de RegisterAsyncTask(). Este método acepta un objeto del tipo PageAsyncTask que contiene toda la información necesaria para poder lanzar las tareas y lograr que seamos notificados en su finalización. Estas tareas se van a ejecutar luego del evento PreRender y antes de que se produzca el evento PreRenderComplete (en la primer parte explico un poco el ciclo de vida de las páginas).

Al igual que con AddOnPreRenderCompleteAsync, es necesario agregar en la directiva Page la siguiente propiedad:

   1: <%@ Page Async="true" ... %>

Ahora, con todo listo no hay nada mejor que un ejemplo para entender su uso. Imaginemos que tenemos una página con un DropDownList y un GridView, los cuales se cargan independientemente el uno del otro. La carga de ambos podemos lograrla de la siguiente manera:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:
   3:     private EventHandler ehRecuperarDatosDeGrillaPrincipal = null;
   4:     private EventHandler ehRecuperarDatosDeDropDownList = null;
   5:
   6:     // Datasets usados para guardar los datos a mostrar
   7:     private DataSet dsGrillaPrincipal;
   8:     private DataSet dsDropDownList;
   9:
  10:     protected void Page_Load(object sender, EventArgs e) {
  11:
  12:         // Creamos 2 tareas
  13:         PageAsyncTask pat1 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeGrillaPrincipal), new EndEventHandler(this.CargarGrillaPrincipal), null, null, true);
  14:         PageAsyncTask pat2 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeDropDownList), new EndEventHandler(this.CargarDropDownList), null, null, true);
  15:
  16:         // Registramos las 2 tareas para que se ejecuten asincrónicamente
  17:         Page.RegisterAsyncTask(pat1);
  18:         Page.RegisterAsyncTask(pat2);
  19:     }
  20:
  21:     IAsyncResult RecuperarDatosDeGrillaPrincipal(object sender, EventArgs e, AsyncCallback cb, object state) {
  22:         this.ehRecuperarDatosDeGrillaPrincipal = new EventHandler(this.RecuperarSetDeDatos1);
  23:         // Ejecutamos asincrónicamente el método que recupera o procesa los datos que necesitamos
  24:         return this.ehRecuperarDatosDeGrillaPrincipal.BeginInvoke(this, EventArgs.Empty, cb, null);
  25:     }
  26:
  27:     void CargarGrillaPrincipal(IAsyncResult ar) {
  28:         this.ehRecuperarDatosDeGrillaPrincipal.EndInvoke(ar);
  29:
  30:         this.GridView1.DataSource = this.dsGrillaPrincipal;
  31:         this.GridView1.DataBind();
  32:     }
  33:
  34:     IAsyncResult RecuperarDatosDeDropDownList(object sender, EventArgs e, AsyncCallback cb, object state) {
  35:         this.ehRecuperarDatosDeDropDownList = new EventHandler(this.RecuperarSetDeDatos2);
  36:         // Ejecutamos asincrónicamente el método que recupera o procesa los datos que necesitamos
  37:         return this.ehRecuperarDatosDeDropDownList.BeginInvoke(this, EventArgs.Empty, cb, null);
  38:     }
  39:
  40:     void CargarDropDownList(IAsyncResult ar) {
  41:         this.ehRecuperarDatosDeDropDownList.EndInvoke(ar);
  42:         this.DropDownList1.DataSource = this.dsDropDownList;
  43:         this.DropDownList1.DataBind();
  44:     }
  45:
  46:     private void RecuperarSetDeDatos1(object sender, EventArgs e) {
  47:         DataSet ds = new DataSet();
  48:         /*...*/
  49:         this.dsGrillaPrincipal = ds;
  50:     }
  51:
  52:     private void RecuperarSetDeDatos2(object sender, EventArgs e) {
  53:         DataSet ds = new DataSet();
  54:         /*...*/
  55:         this.dsDropDownList = ds;
  56:     }
  57: }

Debo aclarar que en el ejemplo hago uso del delegado EventHandler utilizado en la primera parte de esta serie debido a que estoy mostrando la forma más compleja de recuperar datos (normalmente es la única que usaremos). Las consultas de datos pueden ser realizadas dentro de los métodos RecuperarDatosDeGrillaPrincipal() y RecuperarDatosDeDropDownList(), pero es necesario devolver un objeto que implemente IAsyncResult, y como no tenemos ninguno en nuestro caso, lo creamos mediante el uso del delegado que mencioné. En la primera parte de esta serie se puede ver una de las maneras que hay para recuperar los datos directamente desde estos métodos.

Dos puntos a tener en cuenta. El primero es el tercer parámetro del constructor de PageAsyncTask, un EndEventHandler (igual que el segundo parámetro) que podemos usar en caso de que la tarea que ejecutemos lance un timeout (no se ve en el ejemplo). Este parámetro se configura en la directiva Page con la propiedad AsyncTimeOut (expresada en segundos) de la siguiente manera:

   1: <%@ Page Async="true" AsyncTimeout="20" ... %>

Por defecto este valor viene configurado a 45 segundos.

El otro punto a tener en cuenta es el último parámetro del mismo método, el cual nos permite especificar si deseamos que las tareas se ejecuten de forma secuencial o paralela. En el ejemplo, como las consultas son independientes, decidí ejecutarlas en paralelo.

Ahora veamos otro caso. Imaginemos que agregamos una nueva grilla a nuestra página y que los datos de la misma depende del valor seleccionado en el DropDownList. Esta tarea no podemos cargarla en el método Page_Load ya que necesitamos asegurarnos que la carga del DropDownList ha finalizado. Es por ello que podemos cargar esta tarea justamente en el momento de cargar la lista.

El siguiente ejemplo muestra como hacerlo y además muestra el uso de un EventHander propio para facilitar nuestra tarea:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:
   3:     private EventHandler ehRecuperarDatosDeGrillaPrincipal = null;
   4:     private EventHandler ehRecuperarDatosDeDropDownList = null;
   5:     private RecuperarDatosDeGrillaSecundariaEventHandler ehRecuperarDatosDeGrillaSecundaria = null;
   6:
   7:     private DataSet dsGrillaPrincipal;
   8:     private DataSet dsDropDownList;
   9:
  10:     protected void Page_Load(object sender, EventArgs e) {
  11:
  12:         // Creamos 2 tareas
  13:         PageAsyncTask pat1 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeGrillaPrincipal), new EndEventHandler(this.CargarGrillaPrincipal), null, null, true);
  14:         PageAsyncTask pat2 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeDropDownList), new EndEventHandler(this.CargarDropDownList), null, null, true);
  15:
  16:         // Registramos las 2 tareas para que se ejecuten asincronicamente
  17:         Page.RegisterAsyncTask(pat1);
  18:         Page.RegisterAsyncTask(pat2);
  19:     }
  20:
  21:     IAsyncResult RecuperarDatosDeGrillaPrincipal(object sender, EventArgs e, AsyncCallback cb, object state) {
  22:         this.ehRecuperarDatosDeGrillaPrincipal = new EventHandler(this.RecuperarSetDeDatos1);
  23:         return this.ehRecuperarDatosDeGrillaPrincipal.BeginInvoke(this, EventArgs.Empty, cb, null);
  24:     }
  25:
  26:     void CargarGrillaPrincipal(IAsyncResult ar) {
  27:         this.ehRecuperarDatosDeGrillaPrincipal.EndInvoke(ar);
  28:         this.GridView1.DataSource = this.dsGrillaPrincipal;
  29:         this.GridView1.DataBind();
  30:     }
  31:
  32:     IAsyncResult RecuperarDatosDeDropDownList(object sender, EventArgs e, AsyncCallback cb, object state) {
  33:         this.ehRecuperarDatosDeDropDownList = new EventHandler(this.RecuperarSetDeDatos2);
  34:         return this.ehRecuperarDatosDeDropDownList.BeginInvoke(this, EventArgs.Empty, cb, null);
  35:     }
  36:
  37:     void CargarDropDownList(IAsyncResult ar) {
  38:         this.ehRecuperarDatosDeDropDownList.EndInvoke(ar);
  39:         this.DropDownList1.DataSource = this.dsDropDownList;
  40:         this.DropDownList1.DataBind();
  41:
  42:         this.DropDownList1.SelectedIndex = 0;
  43:
  44:         string id = this.DropDownList1.Items[0].Value;
  45:         // Cargamos la tarea y le pasamos el ID como parámetro
  46:         PageAsyncTask pat3 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeGrillaSecundaria),
  47:                             new EndEventHandler(this.CargarGrillaSecundaria), null, id, true);
  48:
  49:         Page.RegisterAsyncTask(pat3);
  50:     }
  51:
  52:     IAsyncResult RecuperarDatosDeGrillaSecundaria(object sender, EventArgs e, AsyncCallback cb, object state) {
  53:         this.ehRecuperarDatosDeGrillaSecundaria = new RecuperarDatosDeGrillaSecundariaEventHandler(this.RecuperarSetDeDatos3);
  54:         return this.ehRecuperarDatosDeGrillaSecundaria.BeginInvoke((string)state, cb, null);
  55:     }
  56:
  57:     void CargarGrillaSecundaria(IAsyncResult ar) {
  58:         // El uso de un EventHandler personalizado nos permite devolver el DataSet
  59:         DataSet ds = this.ehRecuperarDatosDeGrillaSecundaria.EndInvoke(ar);
  60:         this.GridView2.DataSource = ds;
  61:         this.GridView2.DataBind();
  62:     }
  63:
  64:     private void RecuperarSetDeDatos1(object sender, EventArgs e) {
  65:         DataSet ds = new DataSet();
  66:         /*...*/
  67:         this.dsGrillaPrincipal = ds;
  68:     }
  69:
  70:     private void RecuperarSetDeDatos2(object sender, EventArgs e) {
  71:         DataSet ds = new DataSet();
  72:         /*...*/
  73:         this.dsDropDownList = ds;
  74:     }
  75:
  76:     private DataSet RecuperarSetDeDatos3(string id) {
  77:         DataSet ds = new DataSet();
  78:         /*...*/
  79:         return ds;
  80:     }
  81: }

La definición del RecuperarDatosDeGrillaSecundariaEventHandler es la siguiente:

   1: public delegate DataSet RecuperarDatosDeGrillaSecundariaEventHandler(string id);

Lo bueno del método RegisterAsyncTask es que hasta no terminar todas las tareas, no se retoma el procesamiento normal de la página por lo que podemos seguir agregando tareas si así lo quisiésemos. De todos modos, hay que tener en cuenta que al igual que con AddOnPreRenderCompleteAsync, estas tareas se ejecutan antes del evento PreRenderComplete, por lo que si las registramos luego de haberse producido ese evento, serán ignoradas por completo.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0, ASP.NET | Etiquetado: , , , , | 1 Comment »

Ejecutar tareas de forma asincrónica en ASP.NET 2.0 (Parte 1)

Posted by Gerardo Contijoch en enero 20, 2009

Navegando por los links de uno de los posts que me encontré hace un tiempo en mi feed reader, encontré un artículo interesantísimo de Jeff Prosise sobre ejecución asincrónica de páginas en ASP.NET 2.0 que no quería dejar de comentar. Ya hace tiempo que trabajo sobre el Framework 2.0, pero por algún motivo nunca llegué a leer algo sobre el tema (¡era hora que lo hiciera!). El artículo del que hablo es éste, y como no encontré mucho material en español sobre el tema voy a comentarles un poco de que trata el artículo y como lograr que las páginas en ASP.NET 2.0 se procesen (al menos en parte) de manera asincrónica.

Cabe aclarar que con las técnicas que voy a presentar nuestros sites no necesariamente se van a cargar más rápido de una manera perceptible para el usuario, sino que más bien se van a aprovechar los recursos para evitar tiempos muertos durante el procesamiento de las páginas.

Es necesario tener conocimiento sobre el ciclo de vida de las paginas en ASP.NET para poder entender como se logra la asincronicidad, por lo que voy a describir esto brevemente.

Ciclo de vida de una página en ASP.NET

Toda página tiene un ciclo de vida que comienza luego de que desde un cliente (como puede ser un browser) se hace una petición o Request por una página. A lo largo de este ciclo de vida se producen distintos eventos que nos van notificando por las etapas por las cuales esta pasando una página. Por ejemplo, el evento Init se produce luego de cargar los controles y es el momento de inicializarlos (setear sus propiedades). Por otro lado, el evento PreRender se da un instante antes de comenzar el renderizado de la página por lo que puede ser un buen lugar para modificar algún detalle de último momento relacionado con la visualización o renderizado de la página (yo tengo la costumbre de hacer visibles/invisibles o habilitar/deshabilitar ciertos controles en este punto y no antes, de modo que la lógica de presentación no se mezcle con la relacionada al binding de grillas, por ejemplo). Pueden encontrar el detalle del ciclo de vida de una página acá (a los más curiosos les recomiendo investigar el método ProcessRequestMain(bool, bool) de la clase Page con .Net Reflector).

El ciclo completo de una página es el siguiente:

  • PreInit
  • Init
  • InitComplete
  • LoadState (evento privado)
  • ProcessPostData (evento privado)
  • PreLoad
  • Load
  • ProcessPostData (una segunda vez)
  • “ChangedEvents” (eventos lanzados por los controles de la página)
  • PostBackEvents (evento privado)
  • LoadComplete
  • PreRender
  • PreRenderComplete
  • SaveState
  • SaveStateComplete
  • Unload

Cuando una página implementa la interface IHttpHandler, el ciclo de vida completo es ejecutado por un único hilo recuperado del ThreadPool, el cual es devuelto una vez finalizada la ejecución de la página. La mayoría de las paginas muestran datos que son procesados o recuperados de algún lado, ya sea una DB, un archivo de configuración, etc, y esa tarea casi siempre es independiente del procesamiento de la página. Si tardamos 5 segundos en recuperar los datos de una tabla de una DB, esos son 5 segundos de tiempo muerto en el que el hilo que esta ejecutando el procesamiento de la página se queda esperando los datos. Como el ciclo de vida de una página es necesariamente secuencial, no podemos aprovechar este tiempo muerto para procesar otras partes de la página, pero lo que si podemos hacer es liberar el hilo (es decir, devolverlo al ThreadPool) de modo que pueda ser reutilizado para el procesamiento de alguna otra tarea.

Ejecución asincrónica

Veamos como hacerlo. Lo primero es habilitar la ejecución asincrónica seteando el atributo Async en la directiva Page a true:

   1: <%@ Page Async="True" ... %>

Lo que esto produce es que la página implemente la interface IHttpAsyncHandler (a diferencia de las páginas ‘comunes’, que implementan IHttpHandler). No entiendo muy bien en dónde ni cómo ocurre esto ya que la clase Page implementa IHttpHandler y no se pueden cambiar sus interfaces así como así. Evidentemente todo este proceso ocurre antes de la propia compilación de la página (ya que luego de la misma, esta no puede ser modificada), pero no se exactamente en que punto (si alguien sabe, que diga).

En fin, me estoy llendo por las ramas. Al implementar esta interface, vamos a poder introducir dos eventos nuevos en el ciclo de vida de la página. Estos se van a ejecutar inmediatamente después del evento PreRender y antes de PreRenderComplete . El primero de ellos lo que va a hacer es ejecutar una tarea de manera asincrónica devolviendo un objeto que implemente IAsyncResult. El segundo, va a ser lanzado cuando el proceso asincrónico finalice, retomando ahí lo que queda del procesamiento de la página.

Para atrapar estos eventos vamos a hacer uso del método AddOnPreRenderCompleteAsync de la clase Page:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:     protected void Page_Load(object sender, EventArgs e) {
   3:         Page.AddOnPreRenderCompleteAsync(
   4:             new BeginEventHandler(this.BeginAsync),
   5:             new EndEventHandler(this.EndAsync)
   6:         );
   7:     }
   8:
   9:     IAsyncResult BeginAsync(object sender, EventArgs e, AsyncCallback cb, object state) {
  10:         /*...*/
  11:     }
  12:
  13:     void EndAsync(IAsyncResult ar) {
  14:         /*...*/
  15:     }
  16: }

Debido a que los eventos estos son lanzados antes del evento PreRenderComplete, es necesario que este método sea ejecutado antes de llegar a esta fase del procesamiento de la página.

Veamos ahora como recuperar datos para cargar una grilla de manera asincrónica:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:
   3:     SqlCommand cmd;
   4:
   5:     protected void Page_Load(object sender, EventArgs e) {
   6:        Page.AddOnPreRenderCompleteAsync(
   7:            new BeginEventHandler(this.RecuperarDatos),
   8:            new EndEventHandler(this.CargarGrilla)
   9:        );
  10:     }
  11:
  12:     IAsyncResult RecuperarDatos(object sender, EventArgs e, AsyncCallback cb, object state) {
  13:        SqlConnection cnn = new SqlConnection("...");
  14:        cnn.Open();
  15:
  16:        this.cmd = new SqlCommand("SELECT * FROM ...", cnn);
  17:
  18:        // Atencion con el CommandBehavior, si no lo usamos de esta forma, necesitamos mantener una
  19:        // referencia a la conexión a nivel de clase para poder cerrarla despues!
  20:        return cmd.BeginExecuteReader(cb, state, CommandBehavior.CloseConnection);
  21:     }
  22:
  23:     void CargarGrilla(IAsyncResult ar) {
  24:        SqlDataReader reader = cmd.EndExecuteReader(ar);
  25:        this.GridView1.DataSource = reader
  26:        this.GridView1.DataBind();
  27:        reader.Close();
  28:     }
  29: }

Esta técnica puede aplicarse también a la ejecución de WebServices ya que los mismos ofrecen una forma Begin[WebMethod] y End[WebMethod] para cada uno de los métodos que se expongan.

Sin embargo, hay ocasiones en lo que deseamos hacer es algo más complejo que una simple consulta o simplemente el método ExecuteReader no nos sirve. En esos casos podemos hacer algo como lo siguiente:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:
   3:     // Referencia al EventHandler que vamos a usar para llamar al método que recupera los datos
   4:     private EventHandler ehRecuperarDatos = null;
   5:     // Aca vamos a guardar los datos que recuperemos
   6:     private DataSet dsDatos = null;
   7:
   8:     protected void Page_Load(object sender, EventArgs e) {
   9:
  10:         Page.AddOnPreRenderCompleteAsync(
  11:             new BeginEventHandler(this.RecuperarDatos),
  12:             new EndEventHandler(this.CargarGrilla)
  13:             );
  14:     }
  15:
  16:     IAsyncResult RecuperarDatos(object sender, EventArgs e, AsyncCallback cb, object state) {
  17:
  18:         // Esto es necesario porque tenemos que devolver un IAsyncResult
  19:         this.ehRecuperarDatos = new EventHandler(this.RecuperarDataSet);
  20:         return this.ehRecuperarDatos.BeginInvoke(this, EventArgs.Empty, cb, null);
  21:     }
  22:
  23:     private void RecuperarDataSet(object sender, EventArgs e) {
  24:         DataSet ds = new DataSet();
  25:         /*...*/
  26:         this.dsDatos = ds;
  27:     }
  28:
  29:     void CargarGrilla(IAsyncResult ar) {
  30:         this.ehRecuperarDatos.EndInvoke(ar);
  31:         this.GridView1.DataSource = this.dsDatos;
  32:         this.GridView1.DataBind();
  33:     }
  34: }

En el ejemplo estoy usando un EventHandler genérico, pero tranquilamente podríamos crear nuestro propio EventHandler con parámetros propios y usarlo para facilitarnos la tarea de recuperación y/o procesamiento de los datos.

Como se ve, no es muy difícil ejecutar tareas en segundo plano, pero éste método nos limita a sólo una única tarea. En un próximo post voy a mostrar otro método para ejecutar más de una tarea asincrónicamente.

[Update: Pueden encontrar la segunda parte de este artículo acá.]

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0, ASP.NET | Etiquetado: , , , , | 1 Comment »

Sacar acentos, tildes y demás signos diacríticos de un texto

Posted by Gerardo Contijoch en enero 15, 2009

Hace un tiempo me vi en la necesidad de quitarle todos los acentos a un texto para que el mismo pueda ser procesado correctamente. Lo primero que me vino a la mente es hacer uso de una expresión regular para esto, pero como no las manejo muy bien tuve que ponerme a buscar como hacerlo. En medio de esa búsqueda me topé con este post de Michael Kaplan en donde explica una mejor manera de hacerlo y no solo eso, sino que también su método elimina cualquier otro ‘símbolo extraño’ que contengan nuestro texto. Con ‘símbolo extraño’ me refiero a los signos diacríticos (ni sabia que existía esa palabra), como pueden ser el anillo (°), la diéresis (¨) o la virgulilla (~, ¡otra palabra nueva!).

Esta técnica es muy útil para generar URLs como se puede ver acá (versión ligeramente modificada para hacerla apta para URLs).

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0 | Etiquetado: , | 3 Comments »

Error de compilación CS0016 en site corriendo en IIS

Posted by Gerardo Contijoch en diciembre 30, 2008

Hoy por primera vez necesite levantar un site en II7 sobre Windows Vista y me encontré con un viejo problema: el error de compilación CS0016, que se presenta con el siguiente mensaje:

Compiler Error Message: CS0016: Could not write to output file ‘c:\Windows\Microsoft.NET\Framework\[version de framework]\Temporary ASP.NET Files\root\fc8191ba\bb891bec\App_global.asax.pwk2xkjx.dll’ — ‘Access is denied. ‘

El nombre del archivo puede ser diferente ya que es generado dinámicamente al azar.

Este problema no es nuevo, ya que con otras versiones de Windows también sucede lo mismo y se debe a que el usuario con el cual estamos ejecutando el proceso de compilación no tiene permisos de escritura sobre el path indicado ni sobre el directorio temporal de Windows (esto último no lo dice en ningún lado, pero es así). Este usuario es el usuario configurado en el Application Pool asociado a nuestro site. Por defecto es el usuario NETWORK SERVICE, por lo que el problema se soluciona simplemente dándole permisos de modificación y escritura (no es necesario Full Control como figura en muchos lugares) a ese usuario sobre los paths arriba indicados.

Actualización 10/06/2009:

Luego de investigar un poco más sobre este tema, encontré aquí que el problema también puede ser causado el antivirus McAfee. Resulta que este antivirus tiene una característica que bloquea la ejecución de scripts dentro de carpetas temporales y esto puede estar interfiriendo. Para desactivar esta característica sólo tienen que seguir las instrucciones aquí expuestas.

Otra posibilidad es que el directorio de archivos temporales de Windows no este registrado entre las variables de entorno de sistema (algo raro, pero puede pasar si andamos tocando lo que no debemos). Para verificar esto, tenemos que ir a System Properties (o propiedades de Mi PC) y ahi seleccionar el tab Advanced, al final de todo hay un boton llamado Environment variables…, el cual clickeamos y en la sección System variables de la ventana que se abre tendremos que encontrar a TEMP y a TMP. Si no se encuentra alguno de los dos, entonces hay que agregarlos y asociarles como valor el path de los archivos temporales (tipicamente %Systemroot%\Temp). Luego de hacer esto, reinician IIS y listo. Tengan en cuenta que si no estan seguros de lo que hacen es mejor no tocar estos settings porque pueden dejar de funcionar muchas cosas si hacen algo mal.

Referencias:

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in ASP.NET, Desarrollo Web | Etiquetado: , | 16 Comments »

Acceder directamente al test que falló en MSTest

Posted by Gerardo Contijoch en diciembre 16, 2008

Una de las cosas que más me molesta de ejecutar los tests dentro de Visual Studio (MSTest), es que para llegar al código de un test que falló, tengo que acceder a la ventana de resultados del test, y recién ahí hacer click en el link al archivo (que nos lleva directamente a la línea que falló).

Afortundamente, Simone Chiaretta encontró la solución al problema, ¡y la encontró dentro del propio Visual Studio!

Les dejo el link.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in Testing, Visual Studio | Etiquetado: , , , | Leave a Comment »

Inversión de control e inyección de dependencias

Posted by Gerardo Contijoch en diciembre 6, 2008

La inversión de control (IoC -Inversion of control-, de ahora en adelante) es un principio en el cual se basan muchos de los patrones que vemos día a día. Consiste en invertir el flujo de ejecución de modo que nuestro código sea invocado, en vez de nosotros ser los invocadores (lo que implicaría que nosotros dejaríamos de tener control sobre el flujo de ejecución). El ejemplo más famoso (o por lo menos uno de los más usados) de esto es uno presentado por Martin Fowler en su artículo ‘Inversion of control‘. En el artículo él compara como uno interactúa con una aplicación en modo texto y como lo hace con una con ventanas.

Veamos este ejemplo, adaptado a C#:

   1: public static void Main(string[] args){
   2:     string nombre;
   3:     string busqueda;
   4:
   5:     Console.WriteLine("¿Cuál es su nombre?:");
   6:     nombre = Console.ReadLine();
   7:
   8:     ProcesarNombre(nombre);
   9:
  10:     Console.WriteLine("¿Qué busca?:");
  11:     busqueda = Console.ReadLine();
  12:
  13:     ProcesarBusqueda(busqueda);
  14:
  15:     /*...*/
  16: }

Como se puede apreciar, nuestro código es el que tiene el control sobre el flujo de ejecución. Nuestro código decide cuando se realiza el procesamiento y cuando las preguntas.

Veamos ahora como sería la misma aplicación, pero en un sistema de ventanas (código simplificado):

   1: public class Formulario : Form {
   2:
   3:     private System.Windows.Forms.Label lblNombre = new System.Windows.Forms.Label();
   4:     private System.Windows.Forms.Label lblBusqueda = new System.Windows.Forms.Label();
   5:     private System.Windows.Forms.TextBox txtNombre = new System.Windows.Forms.TextBox();
   6:     private System.Windows.Forms.TextBox txtBusqueda = new System.Windows.Forms.TextBox();
   7:
   8:     public Formulario() {
   9:         InitializeComponent();
  10:     }
  11:
  12:     private void InitializeComponent() {
  13:         this.lblNombre.Text = "¿Cuál es su nombre?:";
  14:         this.lblBusqueda.Text = "¿Qué busca?:";
  15:
  16:         this.txtNombre.LostFocus += new System.EventHandler(txtNombre_LostFocus);
  17:         this.txtBusqueda.LostFocus += new System.EventHandler(txtBusqueda_LostFocus);
  18:     }
  19:
  20:     private void txtNombre_LostFocus(object sender, EventArgs e) {
  21:         ProcesarNombre(this.txtNombre.Text);
  22:     }
  23:
  24:     private void txtBusqueda_LostFocus(object sender, System.EventArgs e) {
  25:         ProcesarBusqueda(this.txtBusqueda.Text);
  26:     }
  27:     /*...*/
  28: }

En este caso, nosotros no somos los que tenemos control sobre el flujo de ejecución, sino el propio formulario, y es él el que decide cuando se procesan los datos.

Hay distintas formas de IoC. Una de ellas, como acabamos de ver, es el patrón Listener u Observer, en donde se pasa el control de la ejecución de ciertas tareas a otro componente. Otra forma común de IoC es la aplicación de ciertos patrones de construcción, como pueden ser Factoría, Factoría abstracta y Builder. En estos casos, se pasa el control de la construcción de objetos a otros componentes que nos resuelven la instanciación (o recuperación) de los distintos objetos de los cuales nuestro código/módulo/componente depende. Particularmente quiero hacer hincapié en otra de las formas de IoC, llamada Inyección de dependencias.

Inyección de dependencias

La inyección de dependencias (DI – Dependency Injection-, de ahora en adelante) es un principio de diseño muy utilizado últimamente, debido principalmente a la ‘gran movida’ que esta teniendo el unit testing con frameworks para mocking. El testing con mocks depende mucho del uso de este patrón ya que se basa en la inyección de dependencias fabricadas o personalizadas específicamente para cada test. Pero ese es otro tema, ahora me voy a concentrar en explicar un poco que es esto de DI.

Como dije, DI es básicamente una forma de IoC, en donde lo que se invierte es la lógica de recuperación de las dependencias de modo que los componentes dependientes no tengan ni siquiera que molestarse en buscar las ya que las mismas les son provistas.

Para entender de que trata esto, tomemos como ejemplo un sistema de compras online. Este sistema entre otras cosas tendría un carrito donde se van guardando los productos que se van comprando. Su uso básicamente seria el siguiente:

   1: public class WebSite {
   2:     /* ... */
   3:     public void Comprar() {
   4:
   5:         var p = new Producto() { Descripcion = "Mouse", Precio = 10 };
   6:
   7:         var carrito = new CarritoDeCompras();
   8:
   9:         carrito.AgregarNuevoItem(p);
  10:     }
  11:     /* ... */
  12: }

Siendo el código del carrito el siguiente:

   1: public class CarritoDeCompras {
   2:         /* ... */
   3:     public void AgregarNuevoItem(Producto p) {
   4:
   5:         // Validamos el producto antes de ingresarlo
   6:
   7:         if (p.Descripcion == "") {
   8:             // Notificar de error en la descripcion
   9:         }
  10:
  11:         if (p.Precio <= 0) {
  12:             // Notificar precio inválido
  13:         }
  14:
  15:         // Agregar el producto a la lista de compras
  16:         /* ... */
  17:     }
  18:
  19:     /* ... */
  20: }

Como se puede apreciar, es el propio carrito el que realiza la validación del producto antes de procesarlo. Esto no es muy deseable ya que hace que el sistema de validaciones sea muy poco flexible y toda la responsabilidad de validar los productos caiga en el propio carrito.

Para ayudarnos, vamos a hacer uso de una clase helper para validar productos, quedando el código del nuevo carrito así:

   1: public class ValidadorDeProductos {
   2:
   3:     public bool ValidarProducto(Producto p) {
   4:
   5:         if (p.Descripcion == "") {
   6:             return false;
   7:         }
   8:
   9:         if (p.Precio <= 0) {
  10:             return false;
  11:         }
  12:
  13:         return true;
  14:     }
  15: }
  16:
  17: public class CarritoDeCompras {
  18:     /* ... */
  19:     public void AgregarNuevoItem(Producto p) {
  20:
  21:         ValidadorDeProductos validador = new ValidadorDeProductos();
  22:
  23:         // Validamos el producto antes de ingresarlo
  24:         bool productoValido = validador.ValidarProducto(p);
  25:
  26:         if (productoValido) {
  27:             // Agregar el producto a la lista de compras
  28:             /* ... */
  29:         } else {
  30:             // Notificar producto inválido
  31:         }
  32:
  33:     }
  34:     /* ... */
  35: }

Ahora toda la lógica de validación de productos se encuentra fuera de la clase CarritoDeCompra, y esta se limita simplemente a crear el validador que va a encargarse de liberar al carrito de la responsabilidad de validar productos.

Si bien esta versión es más conveniente (nos da la flexibilidad de poder usar el validador en otro lugar si es que lo precisamos o podemos modificarlo sin necesidad modificar la clase CarritoDeCompra), sigue sin ser una solución ideal debido a que hay demasiado acoplamiento entre el carrito y la clase responsable de la validación. ¿A que me refiero? Imaginemos esta situación: Se agregó un requerimiento del usuario que limita la validación del precio sólo para los productos que no se encuentren dentro de una promoción, es decir, si un producto forma parte de una promoción, su precio no debe ser validado (debido a que posiblemente haya sido modificado). Para responder a este requerimiento podríamos crear otra clase validadora y usar una u otra dependiendo si el producto se encuentra o no dentro de una promoción. Es una solución rápida al problema, pero seguimos limitados a sólo estas únicas dos clases de validaciones y si precisamos personalizarlas en algún momento, vamos a tener que volver a tocar el código del carrito. Es acá donde podemos aplicar DI y pasarle el ‘control’ de la creación de la clase que realice las validaciones a quien consuma los servicios del carrito, liberándonos de la lógica de la instanciación.

Para ello vamos a hacer algunos cambios. Lo primero es hacer que la clase que representa al carrito dependa de una abstracción y no de una implementación en particular de una clase validadora. Para ello, creamos una interface como la siguiente:

   1: public interface IValidadorDeProductos {
   2:     bool ValidarProducto(Producto p);
   3: }
   4:
   5: public class ValidadorDeProductosComunes : IValidadorDeProductos {
   6:
   7:     public virtual bool ValidarProducto(Producto p) {
   8:
   9:         if (p.Descripcion == "") { return false; }
  10:
  11:         if (p.Precio <= 0) { return false; }
  12:
  13:         return true;
  14:     }
  15: }
  16:
  17: public class ValidadorDeProductosEnPromocion : IValidadorDeProductos {
  18:
  19:     public virtual bool ValidarProducto(Producto p) {
  20:
  21:         if (p.Descripcion == "") { return false; }
  22:
  23:         return true;
  24:     }
  25: }
  26:
  27: public class ValidadorDeProductosLimitados : ValidadorDeProductosComunes {
  28:
  29:     public virtual bool ValidarProducto(Producto p) {
  30:
  31:         bool esValido = base.ValidarProducto(p);
  32:
  33:         if (!esValido) { return false; }
  34:
  35:         return HayEnStock(p);
  36:     }
  37:
  38:     private bool HayEnStock(Producto p) {
  39:         /* ... */
  40:     }
  41: }

Como se puede ver, podemos crear una jerarquía de validadores (lo que promueve la reutilización del código) y todos compatibles con nuestro carrito, el cual quedaría así:

   1: public class CarritoDeCompras {
   2:     /* ... */
   3:     public void AgregarNuevoItem(Producto p, IValidadorDeProductos validador) {
   4:
   5:         // Validamos el producto antes de ingresarlo
   6:         bool productoValido = validador.ValidarProducto(p);
   7:
   8:         if (productoValido) {
   9:             // Agregar el producto a la lista de compras
  10:             /* ... */
  11:         } else {
  12:             // Notificar producto inválido
  13:         }
  14:     }
  15:     /* ... */
  16: }

En éste último ejemplo se puede ver porque llamamos a este patrón ‘inyección de dependencias’: estamos ‘inyectando’ una dependencia (el validador) a un objeto (el carrito), liberándolo a este de la tarea de instanciar, fabricar o recuperar la misma (invertimos el control de la creación del validador).

Hay distintos tipos de DI. Los más comunes son: por interface, por propiedad y por constructor.

Inyección de dependencias por interface (Interface Injection)

El ejemplo anterior del carrito muestra como se aplicó DI por interface. Inyectar dependencias por interface significa que se expone una interface que le permite a un objeto proveer las dependencias a otro. En nuestro ejemplo, la clase WebSite inyecta un validador al carrito a través de la interface que expone (el método AgregarNuevoItem()). En realidad, lo que acabo de decir no es del todo cierto. Este tipo de inyección refiere a la implementación de una interface, la cual provea una manera de aceptar dependencias. En nuestro caso no hay ninguna inteface de este tipo, por lo que la interface expuesta es la de la propia clase, pero podría existir una interface que todos los carritos (en caso de existir más de un tipo) que acepten productos deban implementar:

   1: public interface ICarritoDeCompras {
   2:     void AgregarNuevoItem(Producto p, IValidadorDeProductos validador);
   3: }

De este modo, todos los carritos de compras se ven obligados a aceptar un validador, forzando así la inyección de dependencias.

Inyección de dependencias por propiedad (Setter Injection)

Este tipo de DI se da cuando la dependencia se provee con una propiedad. Nuestro código quedaría así si usáramos este tipo de DI:

   1: public class CarritoDeCompras {
   2:    /* ... */
   3:    public IValidadorDeProductos Validador { get; set; }
   4:
   5:    public void AgregarNuevoItem(Producto p) {
   6:
   7:        // Validamos el producto antes de ingresarlo
   8:        bool productoValido = this.Validador.ValidarProducto(p);
   9:
  10:        if (productoValido) {
  11:            // Agregar el producto a la lista de compras
  12:            /* ... */
  13:        } else {
  14:            // Notificar producto inválido
  15:        }
  16:    }
  17:    /* ... */
  18: }

La DI por propiedad no debería usarse cuando la inyección es obligatoria, ya que el hecho que se haga mediante una propiedad nos da la libertad de no setear esa propiedad (intencionalmente o no), teniendo que contemplar ese caso cada vez que hagamos uso de la dependencia. Algo a tener en cuenta, es que la propiedad puede heredarse de una clase o implementarse con una interface, con lo que también estaríamos haciendo DI por interface. Muchas veces esta forma de inyección es la ofrecida por clases cuyos constructores son privados o no tenemos acceso a ellos.

Inyección de dependencias por constructor (Constructor Injection)

Este es uno de los tipos más comunes de DI. La dependencia simplemente se inyecta en el constructor de la clase que hace uso de la misma. Así se vería el ejemplo con este tipo de DI:

   1: public class CarritoDeCompras {
   2:    /* ... */
   3:    private IValidadorDeProductos Validador { get; set; }
   4:
   5:    public CarritoDeCompras(IValidadorDeProductos validador) {
   6:        this.Validador = validador;
   7:    }
   8:
   9:    public void AgregarNuevoItem(Producto p) {
  10:
  11:        // Validamos el producto antes de ingresarlo
  12:        bool productoValido = this.Validador.ValidarProducto(p);
  13:
  14:        if (productoValido) {
  15:            // Agregar el producto a la lista de compras
  16:            /* ... */
  17:        } else {
  18:            // Notificar producto inválido
  19:        }
  20:    }
  21:    /* ... */
  22: }

Algunos ejemplos

Al comienzo de este post vimos un ejemplo real de la aplicación de IoC. Ahora voy a mostrar dos casos en donde se aplica DI.

El primero se da en la clase System.Web.UI.Control. Cuando cargamos una página, todos los controles de la misma se renderizan en HTML para que puedan ser cargados por nuestros browsers. Este renderizado se realiza en el método RenderControl(), el cual acepta como parámetro una instancia de la clase System.Web.UI.HtmlTextWriter, que no es mas ni menos un TextWriter especializado en escribir HTML. Esto es así debido a que cada control se ‘dibuja’ sobre el HtmlTextWriter, quien va a ser el que se encargue mas adelante de devolver el código HTML de la pagina para que sea presentado por el browser. Cada control sabe como representarse a sí mismo. Un control TextBox va a renderizar un <input type=’text’ … />, un control HyperLink va a renderizar <a href=’…’ /> y un GridView, un <table>…</table>, pero ningún control sabe como generar con el código renderizado, simplemente se limitan a darle al HtmlTextWriter instrucciones sobre como renderizarse y nada mas. Por otro lado, el HtmlTextWriter si sabe como armar el código de la pagina y es él el que ‘da forma’ a los controles, es él el que sabe que los tags de cierre siempre tienen la forma </xxxx> y que los atributos se escriben sólo en los tags de apertura. En fin, acá vemos dos objetos diferentes, con responsabilidades diferentes e íntimamente relacionados (un control no sirve de nada si no puede renderizarse), pero el control no sabe como crear un HtmlTextWriter, ni de dónde sacarlo. El writer es inyectado al método RenderControl() aplicando DI por interface (todos los controles web tienen que sobreescribir el método RenderControl() viendose obligados a aceptar la inyección).

El otro ejemplo es la clase SqlCommand. Un comando depende siempre de una conexión a una DB para poder ejecutarse y es por eso que la misma le es provista mediante su constructor o su propiedad Connection. El comando nunca crea, abre o cierra una conexión, este se limita a enviar una solicitud de ejecución de SQL a una instancia de SQLServer usando la conexión provista y nada mas.

Esto no termina acá y hay mucho mas por ver. Les sugiero los siguientes enlaces para seguir con el tema:

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in Diseño, Patrones | Etiquetado: , , | 2 Comments »