sábado, 28 de julio de 2007

[Video] ¿Cómo crear un Custom Trace Listener?

Les dejo un video de cómo crear un Custom Trace Listener para EntLib 3.1. Código del ejemplo

viernes, 27 de julio de 2007

¿Cómo crear un Custom Validator para VAB de EntLib 3.1?

Introducción Ya vimos como se utiliza el VAP de EntLib 3.1, más adelante vamos a ver la integración con Winforms y ASP.NET (dos cosas muy interesantes) pero ahora es momento de aprender a crear nuestros propios validadores. Esto es muy útil para realizar validaciones que no se pueden con los elementos que trae VAP por defecto, en este caso vamos a ver un ejemplo de cómo realizar la validación de un número de CUIT/ CUIL de Argentina. Teoría Para poder crear nuestros Validators y que EntLib los acepte son necesarias dos cosas.
  • Que nuestra clase herede de Validator
  • Que nuestra clase esté adornado con el atributo CustomValidatorData
Con esto es suficiente, obviamente al heredar de Validator (que es una clase abstracta) tenemos que implementar dos métodos
  • DoValidate
  • DefaultMessageTemplate
Pero, alto, hace falta algo más que no es tan fácil de deducir, nuestra clase tiene que tener un constructor que acepte un parámetro del tipo NameValueCollection esto es para que EntLib le pase los atributos de la configuración, pero repito, no lo dice por ningún lado así que cuidado porque sino nos va a dar un error al querer realizar la validación no antes. Veamos el código.
   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using System.Configuration;
   5:  using Microsoft.Practices.EnterpriseLibrary.Validation;
   6:  using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
   7:  using System.Collections.Specialized;
   8:   
   9:  namespace Validadores
  10:  {
  11:      [ConfigurationElementType(typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.CustomValidatorData))]
  12:      public class CuitValidator: Validator
  13:      {
  14:          public CuitValidator():base(null,null) { }
  15:          public CuitValidator(NameValueCollection values): base(null,null)
  16:          {
  17:          }
  18:          protected override string DefaultMessageTemplate
  19:          {
  20:              get { return "El CUIT ingresado no es válido"; }
  21:          }
  22:   
  23:          protected override void DoValidate(object objectToValidate, object currentTarget, string key, ValidationResults validationResults)
  24:          {
  25:              if (objectToValidate != null)
  26:              {
  27:                  bool valido = CUIT.Validar((string)objectToValidate);
  28:                  if(!valido)
  29:                      base.LogValidationResult(validationResults, this.DefaultMessageTemplate, currentTarget, key);
  30:              }
  31:          }
  32:      }
  33:  }
con esto es suficiente, el método DefaultMessageTemplate permite recuperar el mensaje de error por defecto, el método DoValidate realiza la validación, como se puede ver tenemos una clase CUIT con un método estático Validar que utilizamos para validar y es todo, si la validación es incorrecta llamamos a base.LogValidationResult que agrega nuestro mensaje a ValidationResults la colección que contendrá los errores en la validación, no es necesario hacer nada más que esto para poder utilizar nuestro validador configurando el application block desde el archivo de configuración, ahora, si queremos poder agregar como atributo en alguna propiedad nuestro validador hay que hacer trabajo adicional, pero no se asusten, es simple. Validar con atributos
   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
   5:   
   6:  namespace Validadores
   7:  {
   8:      public class CuitValidatorAttribute: ValidatorAttribute
   9:      {
  10:          protected override Microsoft.Practices.EnterpriseLibrary.Validation.Validator DoCreateValidator(Type targetType)
  11:          {
  12:              return new CuitValidator();
  13:          }
  14:      }
  15:  }
sencillo, sólo hay que heredar la clase abstracta ValidatorAttribute que tiene un sólo método, y lo que tenemos que hacer es retornar una instacia de nuestro validador y listo. Namespace utilizados: System.Configuration; Microsoft.Practices.EnterpriseLibrary.Validation; Microsoft.Practices.EnterpriseLibrary.Common.Configuration; System.Collections.Specialized; Microsoft.Practices.EnterpriseLibrary.Validation.Validators; PD: El código para realizar la validación se los dejo de tarea :) .

jueves, 26 de julio de 2007

EntLib Contrib Release

Ya tenemos entre nosotros la primera versión de EntLib Contrib un proyecto que agrupa extensiones creadas por la comunidad para Enterprise Library

viernes, 20 de julio de 2007

Validaton Application Block desde Web Config

Introducción En la entrada anterior vimos como crear validaciones agregando atributos a nuestras clases, ahora vamos a ver como podemos hacer el mismo trabajo sin tocar para nada las clases, incluso podemos aplicar reglas de validación a clases a las cuales no tenemos acceso al código, todo gracias al VAP. Comencemos Tomemos nuestra clase y quitemos todos los atributos.
1:     public class Persona
2:     {
3:         private int _edad;
4:         private string _nombre;
5:         private string _apellido;
6:         private string _email;
7:  
8:         public string Email
9:         {
10:             get { return _email; }
11:             set { _email = value; }
12:         }
13:         public string Apellido
14:         {
15:             get { return _apellido; }
16:             set { _apellido = value; }
17:         }
18:         public string Nombre
19:         {
20:             get { return _nombre; }
21:             set { _nombre = value; }
22:         }
23:         public int Edad
24:         {
25:             get { return _edad; }
26:             set { _edad = value; }
27:         }
28:     }
Ahora, agregamos un App.Config y lo abrimos con el editor de EntLib. Acto seguimos hacemos click derecho y seleccionamos Add Validation Application Block Acto seguido seleccionado Add New Rule Set y le ponemos de nombre Default Luego agregamos un nuevo tipo, con esto vamos a agregar nuestra clase Persona. Buscamos el tipo en el cuadro que aparece y luego hacemos botón derecho y selecionamos Choose Members Y seleccionamos las propiedades que queremos validar Hacemos botón derecho sobre el Apellido (por ejemplo) y seleccionado New -> Not Null Validator Presionamos F4 y modificamos sus propiedades para que nos muestre un mensaje en caso de no superar la validación desde la propiedad Message Template Y seguimos agregando los validadores, utilizamos el código de validación que vimos antes
   1:              Persona p = new Persona();
   2:   
   3:              Console.WriteLine("Ingrese nombre");
   4:              p.Nombre = Console.ReadLine();
   5:   
   6:              Console.WriteLine("Ingrese apellido");
   7:              p.Apellido = Console.ReadLine();
   8:   
   9:              Console.WriteLine("Ingrese edad");
  10:              p.Edad = Convert.ToInt32(Console.ReadLine());
  11:   
  12:              Console.WriteLine("Ingrese email");
  13:              p.Email = Console.ReadLine();
  14:   
  15:              ValidationResults results = ValidationFactory.CreateValidator("Default").Validate(p);
  16:   
  17:              if (!results.IsValid)
  18:              {
  19:   
  20:                  StringBuilder sb = new StringBuilder("Errores de validación");
  21:                  sb.AppendLine();
  22:   
  23:                  foreach (ValidationResult result in results)
  24:                  {
  25:                      sb.AppendLine(result.Message);
  26:                  }
  27:   
  28:                  Console.WriteLine(sb.ToString());
  29:              }
  30:              else
  31:              {
  32:                  Console.WriteLine("todo bien");
  33:              }

Lo corremos y eureka!! funciona, buenísimo y muy flexible. En la próxima vemos como hacer nuestros propios validadores.

jueves, 19 de julio de 2007

Introducción al Validation Application Block

Introducción Para los que no lo conocen el Validation Application Block es parte de la Enterprise Library desde hace tiempo, si bien, creo que ahora con la 3.1 con los adaptadores para winforms, asp.net y wcf se puede decir que está "completo", pero eso lo vamos a ver más adelante, comencemos por el principio. Proceso de validación Pocas cosas son más molestas que las validaciones (y trabajosas) sobre todo porque enmarañan el código y si no están correctas cambiarlas suele ser un problema. Bien el VAB (validation app block) viene en nuestra ayuda. VAP nos va a permitir escribir un pequeño código para realizar el proceso de validación y, en todo caso dejar para más tarde las validaciones, o al menos, que se encuentren en un lugar qu las haga flexible. Veamos un ejemplo. Ejemplo 1
   1:  public class Persona
   2:  {
   3:      private int _edad;
   4:      private string _nombre;
   5:      private string _apellido;
   6:      private string _email;
   7:   
   8:      public string Email
   9:      {
  10:          get { return _email; }
  11:          set { _email = value; }
  12:      }
  13:      public string Apellido
  14:      {
  15:          get { return _apellido; }
  16:          set { _apellido = value; }
  17:      }
  18:      public string Nombre
  19:      {
  20:          get { return _nombre; }
  21:          set { _nombre = value; }
  22:      }
  23:      public int Edad
  24:      {
  25:          get { return _edad; }
  26:          set { _edad = value; }
  27:      }
  28:  }
Ok, supongan que queremos validar lo siguiente:

  • Que el nombre no sea nulo
  • Que tenga entre 4 y 20 caracteres
  • El apellido igual
  • La edad que sea mayor de 18
  • Y el mail correctamente formado

Si, no es muy difícil, pero lo hacemos con algunos método o qué se yo y después las reglas cambian, hay que buscar por todo el código, es más, si llegamos a utilizar esta entidad en varios proyectos (desde varios assembies quiero decir) se pone medio feo, porque tenemos que tener las reglas de validación públicas, en fin.

Cómo se hace con VAP, bueno hay tres modos, por atributos, por configuración o ambos.

Validacion por atributos

Existe una serie de atributos para realizar las validaciones con VAP se encuentran en Microsoft.Practices.EnterpriseLibrary.Validation.Validators, hay muchos (me da un poco de fiaca escribirlos) pero les aseguro que hay para todos los gusto, y es más, podemos escribir los propios, eso lo vamos a ver más adelante. Veamos como decoramos nuestra clase Persona con los atributos.

   1:          public class Persona
   2:          {
   3:              [RegexValidator(@"^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$", MessageTemplate = "El email no es válido")]
   4:              public string Email
   5:              {
   6:                  get { return _email; }
   7:                  set { _email = value; }
   8:              }
   9:   
  10:              [NotNullValidator(MessageTemplate = "El apellido no puedo estar vacío")]
  11:              [StringLengthValidator(4, 20, MessageTemplate = "El largo del mensaje debe estar en 4 y 20 caracteres")]
  12:              public string Apellido
  13:              {
  14:                  get { return _apellido; }
  15:                  set { _apellido = value; }
  16:              }
  17:   
  18:              [NotNullValidator(MessageTemplate = "El nombre no puedo estar vacío")]
  19:              [StringLengthValidator(4, 20, MessageTemplate = "El largo del mensaje debe estar en 4 y 20 caracteres")]
  20:              public string Nombre
  21:              {
  22:                  get { return _nombre; }
  23:                  set { _nombre = value; }
  24:              }
  25:   
  26:              [RangeValidator(18, RangeBoundaryType.Inclusive, 90, RangeBoundaryType.Inclusive)]
  27:              public int Edad
  28:              {
  29:                  get { return _edad; }
  30:                  set { _edad = value; }
  31:              }
  32:          }
  33:          public void Validar()
  34:          {
  35:              ValidationResults results = ValidationFactory.CreateValidator().Validate(p);
  36:              if (!results.IsValid)
  37:              {
  38:                  StringBuilder sb = new StringBuilder("Errores de validación");
  39:                  sb.AppendLine();
  40:   
  41:                  foreach (ValidationResult result in results)
  42:                  {
  43:                      sb.AppendLine(result.Message);
  44:                  }
  45:                  Console.WriteLine(sb.ToString());
  46:              }
  47:              else
  48:              {
  49:                  Console.WriteLine("todo bien");
  50:              }
  51:   
  52:          }

Listo, si lo corremos vemos que funciona de maravillas, una belleza. Pero esperen, no me digan "tengo que poner el código en el programa igual", esto es cierto, si bien ahora las validaciones son propias de la clase, no andan desperdigadas por ahí, pero esto no es lo mejor, lo mejor es que podemos prescindir de escribir ni un sólo atributo en las clases y poner las reglas de validación en el archivo de configuración de la aplicación. Pero eso lo vemos mañana, hasta entonces.

miércoles, 18 de julio de 2007

Implementado un Custom Trace Listener

Finalmente tengo el código del trace listener terminado, no es gran cosa pero es un recurso para quien quiera hacer el propio ya que no hay mucha documentación en castellano sobre esto.
   1:  [ConfigurationElementType(typeof(CustomTraceListenerData))]
   2:  public class ErrorDataBaseTraceListener : CustomTraceListener
   3:  {
   4:      private const string connectionStringAttributeName = "ConnectionString";
   5:      private const string storedProcedureAttributeName = "StoredProcedureName";
   6:      private static string connectionString = "";
   7:      private static string storedProcedureName = "";
   8:   
   9:      /// Método que se encarga de escribir el error
  10:      public override void TraceData(System.Diagnostics.TraceEventCache eventCache, string source, System.Diagnostics.TraceEventType eventType, int id, object data)
  11:      {
  12:          connectionString = Attributes[connectionStringAttributeName];
  13:          storedProcedureName = Attributes[storedProcedureAttributeName];
  14:   
  15:          if (connectionString == null || connectionString.Length == 0)
  16:              throw new ApplicationException("No se ha especificado el " + connectionStringAttributeName);
  17:   
  18:          if (storedProcedureName == null || storedProcedureName.Length == 0)
  19:              throw new ApplicationException("No se ha especificado el " + storedProcedureAttributeName);
  20:   
  21:          if (data is LogEntry)
  22:          {
  23:              LogEntry logEntry = data as LogEntry;
  24:              if (logEntry.ExtendedProperties.ContainsKey("ErrorObject"))
  25:              {
  26:                  if (logEntry.ExtendedProperties["ErrorObject"] is ErrorObject)
  27:                  {
  28:                      ErrorObject errorObject = logEntry.ExtendedProperties["ErrorObject"] as ErrorObject;
  29:                      WriteErrorEntry(errorObject);
  30:                  }
  31:              }
  32:              else if (this.Formatter != null)
  33:              {
  34:                  this.WriteLine(Formatter.Format(logEntry));
  35:              }
  36:              else
  37:              {
  38:                  this.WriteLine(data.ToString());
  39:              }
  40:          }
  41:      }
  42:   
  43:      /// Escribe el error en la base de datos
  44:      private int WriteErrorEntry(ErrorObject errorObject)
  45:      {
  46:          int retVal = 0;
  47:   
  48:          Database db = DatabaseFactory.CreateDatabase(connectionString);
  49:          DbCommand sp = db.GetStoredProcCommand(storedProcedureName);
  50:   
  51:          db.AddInParameter(sp, "ArchivoAdjunto", DbType.AnsiString, errorObject.ArchivoAdjunto);
  52:          db.AddInParameter(sp, "Asunto", DbType.AnsiString, errorObject.Asunto);
  53:          db.AddInParameter(sp, "Categoria", DbType.AnsiString, errorObject.Categoria);
  54:          db.AddInParameter(sp, "Destino", DbType.AnsiString, errorObject.Destino);
  55:          db.AddInParameter(sp, "Enviado", DbType.Boolean, errorObject.Enviado);
  56:          db.AddInParameter(sp, "FechaEnvio", DbType.DateTime, errorObject.FechaEnvio);
  57:          db.AddInParameter(sp, "FechaGeneracion", DbType.DateTime, DateTime.Now);
  58:          db.AddInParameter(sp, "MailTo", DbType.AnsiString, errorObject.MailTo);
  59:          db.AddInParameter(sp, "Mensaje", DbType.AnsiString, errorObject.Mensaje);
  60:          db.AddInParameter(sp, "Origen", DbType.AnsiString, errorObject.Origen);
  61:          db.AddInParameter(sp, "Tipo", DbType.AnsiString, errorObject.Tipo);
  62:   
  63:          retVal = db.ExecuteNonQuery(sp);
  64:   
  65:          return retVal;
  66:   
  67:      }
  68:   
  69:      protected override string[] GetSupportedAttributes()
  70:      {
  71:          return new string[] { connectionStringAttributeName, storedProcedureAttributeName };
  72:      }
  73:   
  74:      public override void Write(string message)
  75:      {
  76:      }
  77:   
  78:      public override void WriteLine(string message)
  79:      {
  80:      }
  81:   
  82:  }
Para usarlo hay que agregarlo como un Custom Trace Listener, y pasarle dos atriguros ConnectionString y StoredProcedureName. Al hacer Logger.Write es necesario agregar al objeto LogEntry un ExtentdedProperty de nombre ErrorObject de tipo ErrorObject, con los datos para guardar en la base. Dejó un diagrama de ErrorObject, lo que falta es hacer el store procedure para que inserte en la tabla, hasta la próxima.

martes, 17 de julio de 2007

Checkey UnChecked

¿Qué pasa si definimos una variable, por ejemplo, byte y la desbordamos? No contesten...la hecatombe? no, no pasa nada, funciona como un buffer circular, o sea vuelve a contar desde cero. Bueno, está bien, pero si queremos evitar esto y en su lugar lanzar un linda excepción porque ese comportamiento perjudica nuestra lógica de negocios, bueno, para eso existe checked. Probemos este código

private void Test()

{

byte b = new byte();

Console.WriteLine(byte.MaxValue);

for (int i = 0; i <300;i++

{

Console.WriteLine(b++);

}

}

Lo ejecutamos y.... Nada. No lanza ninguna excepción, es más, vuelve a comenzar la cuenta, esto es así y es correcto, es decir, sigue la especificación. Entonces si queremos que se lance una excepción grotesca cuando se desborde algún tipo entero cómo hacemos. Así.

private void Test()

{

byteb = new byte();

Console.WriteLine(byte.MaxValue);

checked

{

for (int i = 0; i < 300; i++)

{

Console.WriteLine(b++);

}

}

}

Y nos aparece una excepción. En resumen, si queremos que se realice la comprobación de desbordamiento y se lance una excepción utilizamos checked sino unchecked.

lunes, 16 de julio de 2007

¿Cómo crear un TraceListener?

Surgió la necesidad de crear un trace listener especial, con algunas características, además de escribir en una base de datos los datos que se necesitaban eran muy particulares. Vamos a ver cómo lo resolvemos.

Creando un trace listener

Primero, para poder utilizar un trace listener hecho por nosotros es necesario que nuestra clase herede de Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.CustomTraceListener y tenga el atributo [ConfigurationElementType(typeof(CustomTraceListenerData))], de este modo

   1:  [ConfigurationElementType(typeof(CustomTraceListenerData))] 
   2:  public class MyCustomTraceListener: Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.CustomTraceListener 

Bueno, lo primero es obviamente para respetar los métodos a los cuales responde la clase, y para que la EntLib lo pueda recuperar con el Object Builder. Lo segundo es para que se pueda utilizar el configurador de EntLib.
Lo primero que vemos es que Visual Studio nos sugiere implementar la clase abstracta CustomTraceListener, si seguimos su sugerencia nos escribe dos métodos Write y WriteLine, vamos a verlos.

   1:  public override void Write(string message) 
   2:  { 
   3:    throw new Exception("The method or operation is not implemented."); 
   4:  } 
   5:  public override void WriteLine(string message) 
   6:  { 
   7:    throw new Exception("The method or operation is not implemented."); 
   8:  } 

Viéndolos sospechamos que no nos van a ayudar en nuestra tarea, qué es lo que tenemos que hace, veamos la definición de CustomTraceListener.

   1:  public abstract class CustomTraceListener : TraceListener 
   2:  { 
   3:    protected CustomTraceListener(); 
   4:    public ILogFormatter Formatter { get; set; } 
   5:  } 

No vemos nada interesante, excepto que hereda de TraceListener, entonces veamos su definición

   1:  public abstract class TraceListener : MarshalByRefObject, IDisposable 
   2:  { 
   3:  protected TraceListener(); 
   4:  public virtual void Close(); 
   5:  public void Dispose(); 
   6:  protected virtual void Dispose(bool disposing); 
   7:  public virtual void Fail(string message); 
   8:  public virtual void Fail(string message, string detailMessage); 
   9:  protected internal virtual string[] GetSupportedAttributes(); 
  10:  [ComVisible(false)] 
  11:  public virtual void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data); [ComVisible(false)] 
  12:  public virtual void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data); 
  13:  [ComVisible(false)] 
  14:  public virtual void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id); 

Nota: El código está incompleto.

Aquí vemos el método que nos va a ayudar, se trata de TraceData, veámoslo con más detenimiento.

   1:  [ComVisible(false)] 
   2:  public virtual void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data); 
   3:  [ComVisible(false)] 
   4:  public virtual void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data); 

Concentrémonos en la primera sobrecarga, vemos que recibe un parámetro object llamado data, este parámetro es el que va a contener el objeto LogEntry con los datos del evento. Hagamos una primera implementación.

   1:  [ConfigurationElementType(typeof(CustomTraceListenerData))] 
   2:  public class MyCustomTraceListener: Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.CustomTraceListener
   3:  { 
   4:   public override void Write(string message)
   5:   { 
   6:     Console.WriteLine(message);
   7:   }
   8:   
   9:   public override void WriteLine(string message)
  10:   { 
  11:     Console.WriteLine(message); 
  12:   }
  13:   
  14:   public override void TraceData(System.Diagnostics.TraceEventCache eventCache,  string source, System.Diagnostics.TraceEventType eventType, int id, object data) 
  15:   { 
  16:     if (data is LogEntry && this.Formatter != null) 
  17:     { 
  18:       this.WriteLine(this.Formatter.Format(data as LogEntry)); 
  19:     } 
  20:     else 
  21:     { 
  22:       this.WriteLine(data.ToString()); 
  23:     }
  24:    }
  25:   }
  26:   

En la línea 16 vemos que se pregunta si data es del tipo LogEntry y el Formatter no es nulo, este es un ejemplo típico que en realidad no nos es de mucha ayuda, primero porque no nos interesa tener un Formatter, segundo porque no queremos escribir los datos del LogEntry sino que nuestro escenario es más complejo, sin embargo si lo pensamos un poco y observamos la definición de LogEntry tal vez podamos hacer uso de este modo de implementación. Bueno, es el momento de pensar cómo lo implementamos, vamos a ver la implementación de LogEntry a ver si con eso y la ayuda del Formatter podemos hacer lo que queremos.

   1:  public LogEntry(); 
   2:  public LogEntry(object message, ICollection categories, int priority, int eventId, TraceEventType severity, string title, IDictionary properties); 
   3:  public LogEntry(object message, string category, int priority, int eventId, TraceEventType severity, string title, IDictionary properties); 
   4:  public Guid ActivityId { get; set; } 
   5:  public string ActivityIdString { get; } 
   6:  public string AppDomainName { get; set; } 
   7:  public ICollection Categories { get; set; } 
   8:  public string[] CategoriesStrings { get; } 
   9:  public string ErrorMessages { get; } 
  10:  public IDictionary ExtendedProperties { get; set; } 
  11:  public string LoggedSeverity { get; } 
  12:  public string MachineName { get; set; } 
  13:  public string ProcessId { get; set; } 

Evidentemente la propiedad ExtendedProperties es una excelente candidata para colocar dentro un objeto con nuestros datos.
Manos a la obra

Comenzado con la implementación

Primero creamos un entidad de datos para contener lo que necesitamos grabar en la base de datos.



Es evidente que vamos a necesitar indicarle un par de cosas a nuestro trace listener, como el connection string de la base de datos y el stored procedure que se encargará de insertar los datos. Vamos a ver cómo se hace esto. Según leí en Codeplex el método GetAttributes le indica a EnLib los atributos que están permitidos para declarar en el archivo de configuración para el trace listener, la firma es esta.

protected internal virtual string[] GetSupportedAttributes();

Entonces si lo sobreescribimos y le decimos que tenemos dos atributos, ConnectionString y StoredProcedureName vamos a poder agregarlos al app.config

   1:  protected override string[] GetSupportedAttributes() 
   2:  { 
   3:    return new string[] {"ConnectionString","StoredProcedureName"}; 
   4:  } 

Ok, ahora en el app.config podemos agregar los atributos, ¿de qué modo?, a manopla o con el editor de EntLib
.


Ahora, para leerlo no hacemos más que

string connectionString = Attributes["ConnectionString"];

Hay que tener cuidado porque si el usuario no definió el atributo (EntLib no hace ningún chequeo de esto) nos va a devolver un string vacío, lo mismo si escribimos mal el nombre.

¿Qué tenemos hasta ahora?

Hasta ahora sabemos como hacer para crear nuestro TraceListener, sabemos qué métodos sobreescribir, sabemos cómo vamos a pasarle nuestro datos (por extended properies) y sabemos cómo pasarle parámetro a través de la configuración. Estamos listo, en el próximo post lo escribimos.

¿Dónde hay programadores?

Luego de un largo análisis sobre noticias que van desde ¿qué motiva a los programadores? , ¿alguién ha visto un programador?, de la escazés de programadores, también en Barrapunto y en Menéame en otros. En definitiva, la discusión es si faltan programadores. Podemos leer muchos puntos de vista, por ejemplo, que ya nadie estudia este tipo de carreras porque están mal pagadas. Esto tiene mucho que ver con que hoy por hoy un programador es más que una prostitura picacódigo, no es algo mecánico como creen muchos que simplemente traduce las especificaciones de un analista en código (java, c#, php, lo que sea), el problema es que las empresas (no todas) tienen dirigentes que lo creen así, son los mismo a los que les decís "me va a llevar 2 días escribir los test de unidad" y creen que es una pérdida de tiempo, o se le hace ver la necesidad de capacitación en nuevas tecnologías y contestan que en esta empresa se llevan echas muchas aplicaciones sin esas nuevas tecnologías, en fin, lo que falta es un cambio de mentalidad en la dirigencia. Un programador es un profesional creativo, y como todo creativo necesitas de ciertas libertades y características del entorno, para desarrollar su actividad. Alguien que tiene que desplegar sus recursos creativos necesita encontrarse contento con el lugar donde trabaja, con lo que gana y con la forma en que es tratado. Veo muchos amigos y colegas que se cambian de trabajo cada un año o menos porque no están contentos con lo que cobran, o peor aún, no están motivados.

sábado, 14 de julio de 2007

Video Linq To SQL

Dejo un video con una intro a Linq To SQL.

viernes, 13 de julio de 2007

Heredar Enums

Surgió la pregunta en una lista del MUG, si se podían heredar los Enum, la pregunta concreta era si es posible hacer esto:

   1:  public enum coloresBasicos:byte 
   2:  { 
   3:    ROJO, VERDE, AZUL 
   4:  } 
   5:   
   6:  public enum coloresPersonalizados1:coloresBasicos 
   7:  { 
   8:    VIOLETA FLUOR 
   9:  } 
  10:   
  11:  public enum coloresPersonalizados2:coloresBasicos 
  12:  {
  13:     VIOLETA2 FLUOR2 
  14:  } 

Este código lo escribió Gustavo de las listas del MUG, lo tomo prestado para el ejemplo.

La verdad es que nunca lo había intentado pero me propuse comprobarlo, entonces hice un enum así:

   1:  public enum Colores 
   2:  { 
   3:    ROJO, AZUL, AMARILLO 
   4:  } 

Acto seguido intente heredarlo, y ó sorpresa, no se puede, la pregunta es por qué, ya que como digo siempre es mejor comprender que recordar, entonces volví a echar mano del Reflector y vi esto:

   1:  public enum Colores 
   2:  { 
   3:    ROJO, AZUL, AMARILLO 
   4:  } 

Es idéntico a lo que escribí, vamos como se ve en MSIL

   1:  .class public auto ansi sealed Colores extends [mscorlib]System.Enum 
   2:  { 
   3:   .field public static literal valuetype pruebaEnum.Colores AMARILLO = int32(2) 
   4:   .field public static literal valuetype pruebaEnum.Colores AZUL = int32(1) 
   5:   .field public static literal valuetype pruebaEnum.Colores ROJO = int32(0) 
   6:   .field public specialname rtspecialname int32 value__ 
   7:  } 

Y aquí está la respuesta, como vemos en la declaración la clase tiene el modificador sealed por lo tanto no se puede heredar, en resumen No se pueden heredar los enum. Hasta la próxima.

martes, 10 de julio de 2007

Int32.Parse vs Convert.ToInt32

Tengo que convertir un valor, proveniente de una caja de texto o una base de datos, en un entero, la pregunta es ¿utilizo Int32.Parse ó Convert.ToInt32?, sé que muchas personas desconocen Int32.Parse, la verdad es que yo la utilizaba de mis días de Java (que fueron pocos pero ese método lo usé), y no tenía claro que diferencia podía haber con la buena y nunca bien ponderada clase Convert más precisamente el método Int32, bueno vamos a develar el misterio.

Imaginemos que lo que vamos a convertir en Int32 es un dato proveniente de una caja de texto, supongamos también que limitamos a que sólo se ingresen números, ¿qué puede pasar?, puede pasar que el usuario escriba un número (un texto en realidad) por lo tanto no vamos a tener problemas con Int32.Parse, ahora, si el valor viene de una base de datos y puede ser nulo (olvidemos los tipos Nullables) ¿qué puede pasar?, una fea excepción por lo tanto vamos a tener que hacer try/catch con lo cual se nos puede volver poco claro el código, es más, si estamos asignando un grupo de valores se va a poner realmente feo, en este punto es donde llega la clase Convert a nuestra ayuda, vemos la implementación

   1:  public static int ToInt32(string value) 
   2:  {
   3:    if (value == null) 
   4:    { 
   5:      return 0; 
   6:    }
   7:    return int.Parse(value, CultureInfo.CurrentCulture); 
   8:  } 

Lo que podemos ver es que si pasamos un null al método ToInt32 vamos a recibir un "0" y no una excepción. Esto es más que nada para comprender esas pequeñas diferencias en las cosas que usamos todos los días para programar y que, en ocasiones como esta, en realidad hace la diferencia. Una vez más es mejor comprender que recordar, hasta la próxima.