Gerardo Contijoch

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

Archive for the ‘.Net 2.0’ Category

Suspender el dibujado de un control o formulario

Posted by Gerardo Contijoch en septiembre 14, 2009

Hay ocasiones en que nuestros Forms terminan recargados de controles, los cuales cambian de estado o contenido todos a la vez. Si la cantidad de controles es mucha y el contenido de los mismo es modificado varias veces en un breve intervalo de tiempo o simplemente el redibujado de los mismos es muy lento (es decir, el usuario nota como se va llenando cargando el contenido del Form progresivamente), podemos optar por suspender el redibujado del form en su totalidad hasta que los controles estén listos para ser dibujados.

Muy relacionado con mi último post, les dejo el link al código de una clase que se encarga de suspender y reanudar el redibujado de un control (o form en su totalidad si lo deseamos). La técnica se basa en el envío de mensajes al control o ventana en cuestión.

¡Nos vemos en el próximo post!

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

Anuncios

Posted in .Net 2.0, C# | Etiquetado: , | 4 Comments »

Encontrar el valor de un Window Message

Posted by Gerardo Contijoch en septiembre 12, 2009

De vez en cuando, si trabajamos con WinForms, nos puede surgir la necesidad de manejar un evento a muy bajo nivel y para hacerlo tenemos que interceptar los famosos Window Messages, o mensajes de ventana. Estos mensajes son el mecanismo que utiliza Windows para comunicarse con una aplicación. Cuando apretamos una tecla y nuestra aplicación responde, parece que es nuestra aplicación la que ‘atrapó’ el teclazo, pero en realidad es el Sistema Operativo (SO) el que lo atrapó (debido a que el SO es quien controla el puerto donde esta conectado el teclado) y este le envía un mensaje a nuestra aplicación para que responda al mismo, normalmente imprimiendo el caracter representado por la tecla presionada. Aunque no lo parezca, el SO es quien tiene control de todo lo que sucede y cuando nuestra aplicación responde a distintos inputs, es en realidad el SO el que le ordena que haga una cosa u otra.

Si tenemos un Form y el SO determina que es necesario que sea redibujado (porque cambio su contenido por ejemplo), entonces se envía un mensaje conocido como WM_PAINT (en .NET veremos que esto dispara el evento Paint), en cambio si presionamos la tecla ‘Q’ en el teclado, entonces el mensaje recibido por nuestra aplicación es WM_KEYDOWN (también tenemos el evento KeyDown en .NET, el cual es lanzado cuando se recibe un mensaje de este tipo). Cabe aclarar que el nombre de los mensajes es solo una ayuda para nosotros los humanos, ya que los mensajes en realidad no son más que un valor entero, y estos nombres son los nombres de las constantes asociadas a los mismos.

Para interceptar estos mensajes lo que se suele hacer comúnmente es sobrescribir el método WndProc definido en la clase Control de la siguiente manera:

   1: ...
   2: private const int WM_PAINT = 15;
   3:
   4: protected override void WndProc(ref Message m) {
   5:
   6:     if (m.Msg == WM_PAINT) {
   7:         // Atrapamos el mensaje...
   8:     } else{
   9:         // Ignoramos el mensaje y dejamos que siga su curso
  10:         base.WndProc(ref m);
  11:     }
  12:
  13: }
  14: ...

El método WndProc es el que se encarga de procesar todos los mensajes que recibe un Form y es por eso que ese es el punto indicado para atraparlos.

Ahora el problema esta en conocer el valor de los mensajes, ya que muchas veces uno sabe que mensaje procesar, pero no sabe cual es su valor. Estos valores están definidos en la clase System.Windows.Forms.NativeMethods y System.Design.NativeMethods, pero lamentablemente estas clases son internas y no tenemos acceso a ellas, por lo que vamos a tener que usar .NET Reflector para ver su contenido.

La info de este post fue sacada de aquí.

¡Nos vemos en el próximo post!

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

Posted in .Net 2.0, C# | Etiquetado: , , | Leave a Comment »

TraceTool: simplificando el logging en .NET (¡y en otras plataformas también!)

Posted by Gerardo Contijoch en mayo 31, 2009

Les voy a presentar una herramienta que me cambió la forma de hacer logging en mis aplicaciones .NET. Estoy hablando de TraceTool, creado por Thierry Parent.

TraceTool consiste en una librería (disponible para .NET, Java, Delphi, Javascript, entre otros lenguajes) para hacer tracing o logging (como prefieran llamarlo) y un visor (de uso opcional) que es capaz de interpretar tanto los logs o trazas generadas por la librería como así también otros orígenes.

En este post me voy a limitar a hacer una presentación de la herramienta (actualmente en su versión 11), pero para más información pueden consultar el artículo original, el cual contiene muchos más detalles sobre el funcionamiento y configuración de la misma.

Uso de la librería

En la mayoría de los casos la única clase con la que vamos a necesitar interactuar directamente es TTrace. Esta clase expone 3 propiedades, Debug, Warning y Error, las cuales se utilizan para registrar logs de información, advertencia y errores respectivamente. Veamos un ejemplo:

   1: public void Log() {
   2:     TTrace.Debug.Send("Esto es un mensaje.");
   3:     TTrace.Warning.Send("Esto es una advertencia...");
   4:     TTrace.Error.Send("Esto es un mensaje de error!");
   5: }

Ese código produce la siguiente salida (vista en el visor de TraceTool):

TraceTool-log1

Como se ve, lo único que diferencia a cada tipo de mensaje, es el icono que lo precede. Si lo deseamos, también podemos agregar un comentario o texto asociado al mensaje de la siguiente manera:

TTrace.Debug.Send("Esto es un mensaje.", "Texto con info extra sobre el mensaje...");

Que nos quedaría así en el visor:

TraceTool-log2

Una característica por de más de interesante es el hecho de que el método Send() nos devuelve una instancia de un objeto de tipo TraceNode (que es la clase base de la cual derivan los objetos Debug, Warning y Error), la cual nos permite seguir enviando trazas pero anidadas dentro de la traza original (es decir, sub-trazas):

   1: TraceNode nodo = TTrace.Debug.Send("Nodo nivel 0.");
   2: nodo.Send("¡Me encuentro en el nivel 1!");
   3: nodo.Send("¡Yo tambien!");
   4: nodo.Send("¡Y yo!");
   5: TTrace.Debug.Send("Otro nodo nivel 0.");

TraceTool-LogAnidado

Un efecto parecido se puede lograr con los métodos Indent() y UnIndent() que nos permiten indentar todas las trazas sin necesidad de guardar una referencia a un nodo en particular. Esto suele ser muy útil cuando logueamos las entradas, procesamiento y salidas de ciertos métodos aunque para ello TraceTool ya tiene un set de métodos específicamente adaptado para tal fin:

   1: private void UnMetodo() {
   2:     TTrace.Debug.EnterMethod("UnMetodo()");
   3:
   4:     try {
   5:         TTrace.Debug.Send("Procesando...");
   6:         // ...
   7:         throw new Exception();
   8:     } catch {
   9:         // Aca se podría loguear la excepción
  10:         TTrace.Error.Send("Error!");
  11:     } finally {
  12:         TTrace.Debug.ExitMethod("UnMetodo()");
  13:     }
  14: }

Esto produce la siguiente salida:

TraceTool-EnterExit

Loguear más que sólo texto

Con TraceTool también es posible loguear el contenido de objetos enteros con la misma facilidad con la que logueamos sólo texto:

   1: public class Persona {
   2:     public string Nombre { get; set; }
   3:     public string Apellido { get; set; }
   4:     public int Edad { get; set; }
   5: }
   6:
   7: /*...*/
   8:
   9: TTrace.Debug.Send("Logueando solo texto");
  10: var p = new Persona() { Nombre = "Gerardo", Apellido = "Contijoch", Edad = 27 };
  11: TTrace.Debug.SendObject("Logueando un objeto", p);

TraceTool-Objeto

También es posible incluir en la información a loguear sobre un objeto detalles como sus campos, métodos, eventos, miembros privados, etc.

En el caso que quisiéramos registrar el valor de una variable de un tipo primitivo, como puede ser un número, este método no resulta práctico (¿para qué mostrar información de la clase Int32 cuando solo queremos su valor?) y es por eso que la clase TraceNode posee el método SendValue():

   1: TTrace.Debug.SendValue("Logueando un Int32", 50);
   2: TTrace.Debug.SendValue("Logueando un DateTime", new DateTime(2005, 10, 6));
   3: TTrace.Debug.SendValue("Logueando un Boolean", false);

TraceTool-Primitive

(Los logs pueden configurarse para que no se registre la información como la hora y el hilo en el que se ejecutó el logueo)

Como si todo esto fuera poco, también existen métodos similares para registrar Xmls (AddXml()), imágenes (SendBitmap()) y hasta tablas (SendTable()). Pueden ver su uso en el artículo donde se presenta TraceTool.

Configuración

Hay dos puntos donde se puede configurar el logueo. El primero es la propiedad Options de la clase TTrace. Esta propiedad nos va a permitir configurar el formato de los logs que se registren.

Para controlar la verbosidad de los logs se pueden usar las propiedades SendEvents, SendFunctions, SendInherited, SendPrivate, SendProcessName y SendThreadId, las cuales permiten configurar que información se registra sobre los objetos que logueamos.

La otra propiedad importante de esta clase es SendMode, la cual nos permite indicarle a la API si vamos a enviar logs al visor (util cuando se depura, pero no siempre en producción) o no.

El otro punto donde podemos configurar el logueo es la propiedad WinTrace de TTrace. TTrace internamente utiliza a la clase WinTrace para registrar todo y es por ello que algunos parámetros referidos al logueo se especifican acá.

Para setear el path a un archivo de logs vamos a usar el método SetLogFile(). Junto con el path de logs también se puede configurar el particionamiento de los logs, es decir, si se crea un log diferente por día, por máximo de líneas o simplemente un único log ilimitado.

Lamentablemente, los logs son únicamente en formato Xml y esto no se puede cambiar.

Cabe aclarar que TraceTool tiene 2 logs diferentes, uno es el local (el que normalmente usaremos nosotros) y otro es el log del Visor, el cual puede registrar todo lo que se le envíe (usar este log es útil cuando hay mas de una aplicación logueando al mismo lugar). Ambos logs pueden ser configurados independientemente el uno del otro. Para más detalles sobre la configuración pueden ver el artículo original en donde se detalla un poco más el asunto.

Soporte Unicode

Desafortunadamente, si bien la API tiene soporte para logs encodeados como Unicode, el visor aún no los soporta y no es posible abrir logs con caracteres especiales. Es por ello que vamos a tener que cuidarnos de lo que guardamos en los logs porque puede que no podamos abrirlos con el visor. Para solucionar esto, yo opté por modificar la API (el código fuente esta disponible para bajar) y filtrar todos los caracteres especiales con una versión ligeramente modificada del método que presenté en este post (ojo con las tablas, que necesitan los caracteres ‘\t’ para ser procesadas correctamente).

Bien, como dije al comienzo, esto fue sólo una breve presentación de la herramienta, no quise entrar en muchos detalles porque iba a terminar siendo una reescritura completa del artículo original de Thierry Parent. Espero que les resulte interesante esta herramienta y por sobretodo, útil.

¡Nos vemos en el próximo post!

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

Posted in .Net 2.0, C# | Etiquetado: | 2 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 »

Como resolver el error ‘It is an error to use a section registered as allowDefinition=’MachineToApplication’ beyond application level.’

Posted by Gerardo Contijoch en febrero 22, 2009

Hoy mientras estaba configurando la autenticación de un site me encontré con este error. Es un tanto críptico, pero no tan difícil de entender si pensamos un poco (y conocemos otro poco). El mensaje básicamente nos esta diciendo que hay una sección de la configuración (en mi caso la sección <authentication>, pero puede ocurrir lo mismo con otras secciones) que esta registrada con un atributo allowDefinition=’MachineToApplication’ y por eso utilizarla más allá del nivel de aplicación no esta permitido.

El archivo web.config de mi aplicación se ve algo así (resumido para mayor claridad):

   1: <?xml version="1.0"?>
   2: <configuration>
   3:     ...
   4:     <location path="Admin">
   5:         <system.web>
   6:             <authentication mode="Forms">
   7:                 <forms loginUrl="Login.aspx" name=".ASPXFORMSAUTH" defaultUrl="~/Inicio.aspx" protection="Validation"/>
   8:             </authentication>
   9:
  10:             <authorization>
  11:                 <deny users="?" />
  12:             </authorization>
  13:         </system.web>
  14:     </location>
  15:     <system.web>
  16:         <authorization>
  17:             <allow users="*" />
  18:         </authorization>
  19:         ...
  20:     </system.web>
  21: </configuration>

Aparente no tiene nada de raro, se configura la autenticación y autorización del site, así como una de las secciones del site (la rama Admin). El error me decía que la linea 6 era la del problema.

Ahora veamos de entender el problema para poder resolverlo.

Si buscamos en nuestro web.config veremos que la sección <authorization> no esta definida en ningún lugar y eso se debe a que la misma esta definida en el archivo machine.config. El mismo se encuentra en el path ‘C:\Windows\Microsoft.NET\Framework\<versión del framework>\CONFIG\machine.config’.

La sección <authentication> se encuentra definida de la siguiente manera:

   1: <configuration>
   2:     <configSections>
   3:         ...
   4:         <sectionGroup name="system.web" type="System.Web.Configuration.SystemWebSectionGroup, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
   5:             ...
   6:             <section name="authentication" type="System.Web.Configuration.AuthenticationSection, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" allowDefinition="MachineToApplication"/>
   7:             ...
   8:         </sectionGroup>
   9:     </configSections>
  10: </configuration>

Como se puede ver, efectivamente esta sección esta ‘configurada’ para que solo pueda ser utilizada en el machine.config, en el web.config que se encuentra junto con el machine.config o en el web.config que se encuentra en el root de nuestra aplicación (para ver los posibles valores de este atributo y sus significados pueden ir a esta página). Y justamente ese era el problema. En mi web.config la seccion <authentication> estaba siendo utilizada dentro de la sección <location>. Esto es lo mismo que colocarla dentro de un web.config en el directorio especificado en el atributo path de la sección <location> y por eso ASP.NET se quejaba.

Todo se solucionó cuando moví la sección problemática fuera de <location> quedando el web.config de la siguiente manera:

   1: <?xml version="1.0"?>
   2: <configuration>
   3:     <location path="Admin">
   4:         <system.web>
   5:             <authorization>
   6:                 <deny users="?" />
   7:             </authorization>
   8:         </system.web>
   9:     </location>
  10:
  11:     <system.web>
  12:         <authentication mode="Forms">
  13:             <forms loginUrl="Login.aspx" name=".ASPXFORMSAUTH" defaultUrl="~/Inicio.aspx" protection="Validation"/>
  14:         </authentication>
  15:
  16:         <authorization>
  17:             <allow users="*" />
  18:         </authorization>
  19:     </system.web>
  20: </configuration>

La confusión mía estaba en que quería configurar la autenticación dentro de un subdirectorio, cuando en realidad solo tenia que configurar la autorización para ese subdirectorio. La configuración de autenticación se tiene que aplicar a nivel de site y no de subdirectorio, en cambio, la configuración de autenticación si puede ser diferente para distintos subdirectorios.

En esta página pueden encontrar algo mas de información sobre el tema.

Espero que les sea útil.

¡Nos vemos en el próximo post!

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

Posted in .Net 2.0, ASP.NET, Desarrollo Web | Etiquetado: , , , | 10 Comments »

Keyword default en C#

Posted by Gerardo Contijoch en febrero 15, 2009

Desde la versión 2.0 de C# hay un nuevo uso para keyword default que no todos conocen y resulta de muchísima utilidad en ciertos casos. Típicamente a este keyword lo vemos en bloques de tipo switch, pero también puede ser utilizado para la inicialización de variables. En este último caso, la función del keyword es devolver el valor por defecto de un tipo dado. Veamos un ejemplo:

   1:
   2: public class UnaClase { ... }
   3:
   4: public struct UnaEstructura { ... }
   5:
   6: public void Test(){
   7:
   8:     int i = default(int);
   9:     string s = default(string);
  10:     UnaClase c = default(UnaClase);
  11:     UnaEstructura est = default(UnaEstructura);
  12:
  13:     /* ... */
  14: }
  15:

Acá la variable i se va a inicializar en 0 (que es el valor por defecto para los tipos numéricos), s en null (que es el valor por defecto para strings), c en null (valor por defecto para tipos por referencia) y est va inicializarse con una nueva estructura cuyos campos toman el valor por defecto de sus tipos.

En un código como el anterior no tiene mucha utilidad este keyword ya que sabemos de antemano el valor por defecto de los tipos dados y podemos asignarlos directamente. La verdadera utilidad se descubre cuando trabajamos con tipos genéricos:

   1: public class GenericList<T> {
   2:
   3:     private class Node {
   4:         public Node Next;
   5:         public T Data;
   6:     }
   7:
   8:     private Node head;
   9:
  10:     //...
  11:
  12:     public T GetNext() {
  13:         T temp = default(T);
  14:         Node current = head;
  15:         if (current != null) {
  16:             temp = current.Data;
  17:             current = current.Next;
  18:         }
  19:         return temp;
  20:     }
  21: }

En este ejemplo (sacado de acá) es imposible saber que valor por defecto hay que asignarle a la variable temp (dentro del método GetNext()) ya que su tipo es genérico. Por supuesto también puede utilizarse para determinar si una variable esta o no inicializada sin necesidad de conocer su tipo.

¡Nos vemos en el próximo post!

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

Posted in .Net 2.0, C# | Etiquetado: , | Leave a 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 »