Gerardo Contijoch

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

Archive for 31 mayo 2009

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 »

Utilidad de la propiedad IsReusable de la interface IHttpHandler

Posted by Gerardo Contijoch en mayo 20, 2009

Una de las propiedades, a mi parecer, menos documentadas del .NET Framework es la propiedad IsReusable de la interface IHttpHandler. La documentación nos indica que el valor de esta propiedad determina si el handler en cuestión puede ser reutilizado por más de un request en una aplicación web, lo cual es correcto, pero en ningún lugar dice porque o porque no deberíamos reutilizar un handler, ni cuales son las consecuencias de hacerlo. En realidad no es muy difícil imaginar una interpretación el valor de esta propiedad. Puede indicarnos si nuestro handler es thread safe o no, es decir, puede ser ejecutado concurrentemente en más de un thread sin que ello afecte su funcionamiento. Otra interpretación (ver comentarios de este post) igual de válida para mi, es que esta propiedad determina si se hace pooling del handler o no. Esto significa que si el handler el reutilizable, el mismo no es destruido una vez finalizado su uso, sino que es guardado en algún lado a la espera de otro request, evitando de ese modo, la reinstanciación innecesaria de handlers de uso muy frecuente.

Yo me inclino por la primera (aunque ello no quita que la segunda pueda ser válida también).

Como regla general, si nuestro handler guarda información (en una propiedad o un campo) referente a un request en particular, entonces la propiedad debería devolver false ya que otro request podría estar siendo procesado con la información incorrecta. Acá se puede ver un ejemplo del problema este.

¡Nos vemos en el próximo post!

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

Posted in Desarrollo Web | Etiquetado: , , | 2 Comments »

Habilitar (y hacer funcionar) Code Coverage en Visual Studio

Posted by Gerardo Contijoch en mayo 12, 2009

Hace cerca de un mes, luego de escribir unos cuantos test sobre un código con el que estaba trabajando, se me ocurrió probar la capacidad de hacer Code Coverage (CC de ahora en adelante) desde Visual Studio. Lamentablemente lo que parecía sencillo de lograr, no lo fue. Lo primero a tener en cuenta es que el CC se configura a través de la pantalla de configuración de los Test Runs y no a nivel de Test Run. Esto implica que vamos a necesitar 2 sets de configuraciones diferentes para correr los tests con y sin CC. Para acceder a esta pantalla tenemos que ir a Test/Edit Test Run configurations/<configuracion_a_editar> y allí seleccionar la opción Code Coverage. Solo nos resta seleccionar los assemblies o proyectos a instrumentar (es decir, sobre los cuales hacer el CC) y aplicar (o guardar) los cambios.

Lo que hay que tener en cuenta ahora, es que si corremos los tests en modo debug el CC no se realiza a pesar de que lo hayamos habilitado y configurado correctamente. El CC solo tendrá efecto si los tests son corridos sin debug. Lamentablemente el warning de ésta situación (correr test en debug con CC habilitado) esta deshabilitado por defecto, lo que hace casi imposible determinar porque nuestro CC no se ejecuta. Para habilitar este warning, vamos a Tools/Options…/Tests Tools/Default Dialog Box Action y habilitamos el warning que dice ‘When starting a remote test run or a run with code coverage under the debugger:’ (en la versión en inglés, por supuesto). A partir de ahora si tenemos CC habilitado y queremos correr los tests en modo debug, una advertencia nos recordará sobre la deshabilitación temporal del CC.

Es increíble como algo tan sencillo es tan difícil de lograr en Visual Studio. No hay ningún menú dedicado al CC y en el menú Test no figura ninguna opción sobre esto (existe la posibilidad de abrir la ventana Code Coverage, pero nada mas). La ventana Test Runs tampoco posee ningún botón que nos permita habilitar/deshabilitar el CC (solo uno que nos permite ver los resultados). Tampoco hay ninguna opción en la ventana Code Coverage (yo esperaría habilitarlo o deshabilitarlo desde ahí mismo). En el toolbar Test Tools hay dos opciones relacionadas a CC, pero están deshabilitadas si no hubo CC (solo nos permiten navegar el código analizado). En la ventana Test Results hay 3 opciones para correr los tests (6 si contamos también las opciones de modo debug), pero ninguna que contemple el CC. Lo peor de todo es que una vez que encontramos como habilitarlo, la decisión de ocultar el warning hace que sea muy frustrante tratar de hacer funcionar el CC (yo estuve cerca de una hora sin ningún resultado). ¿Acaso tan difícil era agregar un botón a la ventana Test Runs que habilite o deshabilite el CC? ¿Porqué se tiene que habilitar a nivel de configuración general (lo cual en realidad no esta mal) y no dentro de cada Test Run?

Por cierto… ¿Para cuándo un CoverageExcludeAttribute como en NCover? (soluciones como el uso de DebuggerNonUserCodeAttribute y DebuggerHiddenAttribute no son aceptables)

La verdad, a mi parecer, es un desastre la manera en que esta implementado el acceso al CC en Visual Studio.

¡Nos vemos en el próximo post!

Referencias:

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

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

Restaurar ViewData luego de hacer un Redirect en ASP.NET MVC

Posted by Gerardo Contijoch en mayo 9, 2009

Una de las consecuencias de hacer un redirect desde una acción en ASP.NET MVC es que se pierde toda la información que tenemos guardada en el ViewData así como también en el ModelState. Esto se debe a que esos dos diccionario se utilizan para pasarle información desde los controladores a las vistas y si hacemos un redirect, lógicamente no vamos a estar devolviendo ninguna vista, por lo que sus valores son descartados cuando comienza la ejecución de la acción a la que redirigimos. Para evitar esto, se pueden guardar estos valores en la propiedad TempData, la cual es persistida entre requests. Como esto se puede volver un poco tedioso y repetitivo (por no decir poco elegante), decidí crear un par de ActionFilters que se encarguen de persistir valores del ViewData y de recuperarlos cuando sea necesario. El siguiente código esta basado en este post, el cual contiene la solución para persistir el ModelState.

Les presento un ejemplo de como se utilizan estos atributos y a continuación el código de los mismos.

   1: public class TestController : Controller {
   2:
   3:     [ExportarViewDataItem("Nombre")]
   4:     public RedirectToRouteResult AccionConRedirect() {
   5:         // ...
   6:         ViewData["Nombre"] = "Gerardo";
   7:         // ...
   8:         return RedirectToAction("Index");
   9:     }
  10:
  11:     [ImportarViewDataItem("Nombre")]
  12:     public ViewResult Index() {
  13:         string nombre = ViewData["nombre"].ToString();
  14:         // ...
  15:         return View();
  16:     }
  17:
  18: }

La implementación actual de los atributos requiere que se especifique un key a exportar e importar, pero los atributos son muy fácilmente modificables para que exporte e importe el ViewData completo sin necesidad de especificar cada uno de los keys.

Aca esta el código:

   1: /// <summary>
   2: /// Clase base para los atributos que exportan e importan valores de ViewData.
   3: /// </summary>
   4: public abstract class ViewDataTransferAttribute : ActionFilterAttribute {
   5:
   6:     public string Key { get; set; }
   7:
   8:     protected ViewDataTransferAttribute(string key) {
   9:         if (string.IsNullOrEmpty(key)) {
  10:             throw new ArgumentException("El parámetro key no puede ser null o vacío.", "key");
  11:         }
  12:
  13:         Key = key;
  14:     }
  15:
  16:     /// <summary>
  17:     /// Key interna para referenciar al valor guardado.
  18:     /// </summary>
  19:     /// <remarks>Se usa una key intena para asegurarnos de que no hay conflictos de keys con valores ya existentes.</remarks>
  20:     protected string InternalKey { get { return "_viewDataItem_" + Key; } }
  21: }
  22:
  23: [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
  24: public class ExportarViewDataItemAttribute : ViewDataTransferAttribute {
  25:
  26:     public ExportarViewDataItemAttribute(string key) : base(key){}
  27:
  28:     public override void OnActionExecuted(ActionExecutedContext filterContext) {
  29:
  30:         // Si no hacemos un redirect, entonces no tiene sentido respaldar los valores
  31:         if (filterContext.Result is RedirectResult || filterContext.Result is RedirectToRouteResult) {
  32:
  33:             var valor = filterContext.Controller.ViewData[Key];
  34:
  35:             if (valor != null) {
  36:                 var tempData = filterContext.Controller.TempData;
  37:                 if (tempData.ContainsKey(InternalKey)) {
  38:                     tempData.Remove(InternalKey);
  39:                 }
  40:                 tempData.Add(InternalKey, valor);
  41:             }
  42:         }
  43:
  44:         base.OnActionExecuted(filterContext);
  45:     }
  46: }
  47:
  48: [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
  49: public class ImportarViewDataItemAttribute : ViewDataTransferAttribute {
  50:
  51:     public ImportarViewDataItemAttribute(string key) : base(key) { }
  52:
  53:     public override void OnActionExecuting(ActionExecutingContext filterContext) {
  54:
  55:         var valor = filterContext.Controller.TempData[InternalKey];
  56:
  57:         if (valor != null) {
  58:             var viewData = filterContext.Controller.ViewData;
  59:             if (viewData.ContainsKey(Key)) {
  60:                 viewData.Remove(Key);
  61:             }
  62:             viewData.Add(Key, valor);
  63:             // Limpiamos TempData
  64:             filterContext.Controller.TempData.Remove(InternalKey);
  65:         }
  66:
  67:         base.OnActionExecuting(filterContext);
  68:     }
  69: }

Se puede bajar el proyecto con los atributos y los tests asociados desde aquí.

¡Nos vemos en el próximo post!

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

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

Tooltip simple con jQuery

Posted by Gerardo Contijoch en mayo 5, 2009

Hace unos días precisé agregar un tooltip a una tabla HTML y debido a que ya estaba usando jQuery, comencé a buscar plugins y scritps para esta librería. Lo que necesitaba era un tooltip simple, sencillo, como los clásicos de Windows y lo encontré en cssglobe.com. Éste es el post donde se presenta el script. El problema que le vi a ese script es que solo funcionaba con elementos <a> y se limita a leer el atributo title de los mismos, lo cual no me servía. Es por ello que lo modifiqué para hacerlo más genérico y poder aplicarlo a cualquier elemento.

Su uso es sencillísimo:

var unElemento = $("#unDiv");
AddTooltip(unElemento, "Este es el contenido del tooltip!");

En el ejemplo anterior, el tooltip se aplicaría sobre un elemento cuyo Id es unDiv y se ve de la siguiente manera:

tooltip-con-jquery-tooltip

De hecho, se puede poner cualquier fragmento de HTML, como una imagen, una tabla o lo que queramos.

Pueden bajar el script modificado junto con código de ejemplo desde aquí.

¡Nos vemos en el próximo post!

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

Posted in Desarrollo Web, Javascript, jQuery | Etiquetado: , | 1 Comment »