sábado, 28 de julio de 2007
[Video] ¿Cómo crear un Custom Trace Listener?
viernes, 27 de julio de 2007
¿Cómo crear un Custom Validator para VAB de EntLib 3.1?
- Que nuestra clase herede de Validator
- Que nuestra clase esté adornado con el atributo CustomValidatorData
- DoValidate
- DefaultMessageTemplate
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: }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: }jueves, 26 de julio de 2007
EntLib Contrib Release
viernes, 20 de julio de 2007
Validaton Application Block desde Web Config
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: }
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: }jueves, 19 de julio de 2007
Introducción al Validation Application Block
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: }
- 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: }
miércoles, 18 de julio de 2007
Implementado un Custom Trace Listener
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: }
martes, 17 de julio de 2007
Checkey UnChecked
private void Test()
{
byte b = new byte();
Console.WriteLine(byte.MaxValue);
for (int i = 0; i <300;i++
{
Console.WriteLine(b++);
}
}
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++);
}
}
}
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?
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?
sábado, 14 de julio de 2007
viernes, 13 de julio de 2007
Heredar Enums
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
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.