Tag Archive for configuration

.NET y Configuraciones – Parte 10

.NET y Configuraciones – Parte 9

Luego de varias semanas de ausencia creo que llego la hora de continuar con nuestra serie (no se preocupen, ya estamos llegando al final).

Converters

Es muy comun encontrarnos con momentos en que necesitamos transformar de un tipo a otro en un archivo de configuracion, por ejemplo, necesitamos transformar de un texto a un enumerador dado sin necesidad de usar la funcion “Enum.Parse”. Bien, para esos menesteres existen lo que en el namespace de Configuration se le llama “converters”, o clases especiales que se encargan de transformar de una representacion a un tipo dado.

System.Configuration viene con varios converters dentro de la cajita, algunos bastante utiles:

CommaDelimitedStringCollectionConverter Transforma de una cadena separada por comas a una coleccion de tipo CommaDelimitedStringCollection
GenericEnumConverter Transforma de una representacion de cadena a un Enum
InfiniteIntConverter Tranforma de un entero de tipo entero ilimitado
InfiniteTimeSpanConverter Un tipo TimeSpan con soporte para representacion al infinito
TimeSpanMinutesConverter Tipo TimeSpan usando minutos
TimeSpanMinutesOrInfiniteConverter Tipo TimeSpan usando minutos con representacion al infinito
TimeSpanSecondsConverter Tipo TimeSpan usando segundos
TimeSpanSecondsOrInfiniteConverter Tipo TimeSpan usando segundos con representacion al infinito
TypeNameConverter Retorna un “Tipo” a partir del nombre del tipo
WhiteSpaceTrimStringConverter Retorna una cadena sin los espacios

Una forma facil de enteder como funciona los converters es facil, simplemente veamos un ejemplo:

using System;
using System.ComponentModel;
using System.Configuration;

namespace ConfigurationSample {
    internal struct ConfigurationConsts {
        public const string Name = "type";
    }

    public class SampleConfigurationSection : ConfigurationSection {
        [ConfigurationProperty(ConfigurationConsts.Name, IsRequired = true)]
        [TypeConverter(typeof(TypeNameConverter))]
        public Type Type {
            get { return base[ConfigurationConsts.Name] as Type; }
            set { base[ConfigurationConsts.Name] = value; }
        }
    }
}

Y bueno, veamos el archivo de configuracion, observen como se usa el nombre del tipo, mientras que en la seccion de configuracion retornamos el tipo (no la cadena que representa el tipo, ven? type safety de "gratis").

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="sample" type="ConfigurationSample.SampleConfigurationSection, ConfigurationSample" />
  </configSections>
  <sample type="ConfigurationSample.SampleTypeStuff, ConfigurationSample" />
</configuration>

Y para probar que todo funciona bien, nada mejor que nuestro "hola mundo"

using System;
using System.Configuration;

namespace ConfigurationSample {
    class Program {
        static void Main(string[] args) {
            var configuration = ConfigurationManager.GetSection(&quot;sample&quot;) as SampleConfigurationSection;
            if (configuration != null) {
                var type = configuration.Type;
                Console.WriteLine("The type name is {0}", type.Name);
                Console.WriteLine("The type's assembly is {0}", type.Assembly);
            }
            Console.ReadLine();
        }
    }
}

Ven? bastante simple :) los leo en la siguiente entrega

.NET y Configuraciones – Parte 8

La última vez que conversamos acerca de configuraciones sacamos a relucir lo sencillo que es agregar soporte para funciones de validación callback, y antes de eso comentábamos lo sencillo que era agregar soporte para validación de forma declarativa usando los atributos de validación incluídos en la Configuration Framework de la .Net Framework.

Hoy seguiremos caminando en el soporte de validación customizada de secciones y elementos de validación, pero esta vez creando nuestros propios atributos de validación customizados.

Atributos de Validación

Un atributo de validación nos permite validar los elementos o secciones de nuestra configuración de .Net, simplemente tenemos que adornar ese elemento con el atributo en cuestión. La .Net framework nos incluye algunos atributos ya dentro de la caja, pero podemos agregar los nuestros propios de forma sencilla.

Realmente un atributo de validación costa de dos clases: La clase que marca el atributo (un atributo marcador) y la clase que ejecuta la acción de validar (el validador). Como podremos notar en este lado de la framework se utilizan los atributos de una manera muy particular (y que en lo personal me gusta) de mezclar el atributo como marcador pero no como ejecutor.

Usemos un simple ejemplo, imaginemos que necesitamos validar la entrada de correos electrónicos, como todos sabemos, podemos seguir un par de reglas expuestas en una serie de RFC’s acerca de direcciones de email. Bueno, comencemos por la clase que hace realmente la validación, esta debe heredar de la clase ConfigurationValidatorBase y luego el override de los métodos CanValidate (que nos dice si podemos o no validar el tipo en cuestión) y Validate (quien realmente realiza la validación).

using System;
using System.Configuration;
using System.Text.RegularExpressions;

namespace Cprieto.Samples {
    public class EmailValidator : ConfigurationValidatorBase {
        private const string Word = "[^x00-x1F^(^)^<^>^@^,^;^:^\^"^.^[^]^s]";
        private const string IpEntry = "[[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}]";
        private static readonly string Domain = string.Format("({0}+(.{0}+)*", Word);

        private static readonly Regex EmailRegex =
            new Regex(string.Format("^{0}+(.+)*@{1}|{2})$", Word, Domain, IpEntry), RegexOptions.Compiled);

        public override bool CanValidate(Type type) {
            return type == typeof (string);
        }

        public override void Validate(object value) {
            var item = (string) value;
            if (!string.IsNullOrEmpty(item) && !EmailRegex.IsMatch(item))
                throw new ArgumentException("value is not a valid email");
        }
    }
}

Bien, ahora simplemente creamos el atributo marcador, este debe heredar de ConfigurationValidatorAttribute y la parte importante de este es el override de la propiedad ValidatorInstance que retorna una nueva instancia de nuestra clase validadora.

using System.Configuration;

namespace Cprieto.Samples {
    public class EmailValidatorAttribute : ConfigurationValidatorAttribute {
        public override ConfigurationValidatorBase ValidatorInstance {
            get { return new EmailValidator(); }
        }
    }
}

Ahora simplemente lo aplicamos a nuestra sección de configuración:

using System.Configuration;

namespace Cprieto.Samples {
    public class SampleConfigurationSection : ConfigurationSection {
        [CallbackValidator(Type = typeof(PortValidator),
            CallbackMethodName = "Validate")]
        [ConfigurationProperty("port", DefaultValue = 80)]
        public int Port {
            get { return (int) this["port"]; }
        }

        [ConfigurationProperty("host", IsRequired = true)]
        public string Host {
            get { return (string) this["host"]; }
        }

        [EmailValidator]
        [ConfigurationProperty("email")]
        public string Email {
            get { return (string) this["email"]; }
        }
    }
}

Y claro, nunca cae de más un pequeño archivo de validación que debe marcar como inválido

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="sample"
             type="Cprieto.Samples.SampleConfigurationSection, Cprieto.Samples.ValidationCallback"/>
  </configSections>
  <sample port="80" host="localhost" email="invalid@@email" />
</configuration>

Bueno, y ahora me queda pensar que escribo para el siguiente post de la serie :) Hasta la próxima!

.NET y Configuraciones – Parte 7

Bueno, luego de un largo tiempo de ausencia creo que es hora de continuar con la serie acerca de configuraciones en la .NET Framework. La última vez que conversamos mencionamos la capacidad que tenía una sección de configuración de validar los elementos de configuración, esta capacidad es netamente declarativa a partir de atributos que indican el tipo de validación que debe llevarse a cabo. Como ustedes podrán imaginarse existirán situaciones donde es necesario realizar validaciones afuera de las que ya nos trae la configuration framework.

Existen dos formas de especificar o crear nuestras propias rutinas de validación de la configuración: mediante validation callback y utilizando custom validation attributes. Veremos ambas en esta serie y comenzaremos con la primera: validation callbacks

Validation Callbacks

Imaginemos el siguiente caso hipotético, nuestra sección de validación requiere que se ingrese el nombre o host de la aplicación y el puerto para ese host, la sección en cuestión se vería algo así:

using System.Configuration;

namespace Cprieto.Samples {
    public class SampleConfigurationSection : ConfigurationSection {
        [ConfigurationProperty("port", DefaultValue = 80)]
        public int Port {
            get { return (int) this["port"]; }
        }

        [ConfigurationProperty("host", IsRequired = true)]
        public string Host {
            get { return (string) this["host"]; }
        }
    }
}

Bien, ahora imaginemos que por alguna razón especial el puerto no debe ser ni el 110 ni el 23 (o podría ser cualquier otra regla que a uno se le ocurra). Una forma sencilla de realizar esta validación es usando un método callback, la idea es sencilla, cuando la framework obtenga el valor ejecutará el método o callback de validación pasando ese valor (o el por defecto) como parámetro, dentro del método (o callback en este caso) si no se cumple el requerimiento o validación, se arroja una ArgumentException. Los requerimientos del callback method son sencillos: Debe ser público, estático, ser de tipo void (o una Sub en Visual Basic) y tener un parámetro tipo Object (que recibe el valor a validar). Nuestra simple callback de ejemplo sería algo así:

using System;

namespace Cprieto.Samples {
    public class PortValidator {
        public static void Validate(object value) {
            var num = Convert.ToInt32(value);
            if (num == 110 || num == 23)
                throw new ArgumentException("port must not be 110 or 23");
        }
    }
}

Ahora simplemente le decimos declarativamente a la sección de configuración que use ese callback basta con adornar el elemento con el atributo CallbackValidatorAttribute

using System.Configuration;

namespace Cprieto.Samples {
    public class SampleConfigurationSection : ConfigurationSection {
        [CallbackValidator(Type = typeof(PortValidator), CallbackMethodName = "Validate")]
        [ConfigurationProperty("port", DefaultValue = 80)]
        public int Port {
            get { return (int) this["port"]; }
        }

        [ConfigurationProperty("host", IsRequired = true)]
        public string Host {
            get { return (string) this["host"]; }
        }
    }
}

Es importante que recordemos pasar el tipo del validador (o sea, la clase que lo contiene) y el nombre de la función de callback

Para probar nuestro validador basta con usar el siguiente app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="sample"
             type="Cprieto.Samples.SampleConfigurationSection, Cprieto.Samples.ValidationCallback"/>
  </configSections>
  <sample port="110" host="localhost" />
</configuration>

Bien, como dijo Porky… los dejo hasta la siguiente entrega donde continuaremos hablando de validadores en las configuraciones de la .Net Framework, Saludos miles a todos :)

.NET y Configuraciones – Parte 6

Bueno, miren que lejos hemos llegado con el asunto de las configuraciones :) pero no se preocupen, aún falta mucho más que aprender sobre nuestros amigos los archivos app/web.config y sobre nuestras configuraciones personalizadas, imagínense un mundo con menos y con configuraciones más claras y con mayor significado para el desarrollador. Hasta el momento hemos explorado secciones, grupos de secciones, elementos, colecciones todas personalizadas, hoy aprenderemos un poco acerca de validadores.

Element Validators

Imagínense que en su sistema de configuración necesitan validar que el número ingresado por el usuario en la configuración se encuentre dentro de un rango, o que el texto ingresado cumpla con una expresión regular o cumpla una longitud mínima o máxima (si, ya se, ambos se pueden lograr con un regex, pero para efectos de ejemplo digamos que son dos diferentes). Muchos quizás nos veremos tentados a obtener el valor de la configuración y posteriormente validarlo, bien, en el caso de elementos de configuración es buena idea abstenerse de hacerlo. La buena noticia es que tenemos a nuestra disposición toda una infraestructura de validación de valores de configuración y esta se lleva a cabo en el momento en que el ConfigurationManager lee los valores.

Los Validators son atributos especiales que acompañan a nuestros elementos de configuración, podemos definir nuestros propios validadores (ese será el tema de un post futuro) pero por defecto la framework nos empaca un par de validadores simples y sencillos:

IntegerValidator Valida un entero dentro de un rango
LongValidator Valida un número dentro de un rango
PositiveTimeSpanValidator Valida un timespan dentro de un rango positivo
RegexStringValidator Valida que una cadena cumple con una expresión regular
StringValidator Valida que una cadena debe cumplir una longitud máxima/mínima
TimeSpanValidator Valida un timespan dentro de un rango

Usar los validadores no puede ser más sencillo, simplemente adornamos nuestras propiedades o elementos con el atributo del validador que nos interesa y la framework de configuración se encarga del resto

using System;
using System.Configuration;

namespace Samples {
    public class SampleConfigurationSection : ConfigurationSection {
        [ConfigurationProperty("port", DefaultValue = 80)]
        [IntegerValidator(MaxValue = 100, MinValue = 20)]
        public int Port {
            get { return (int) this["port"]; }
        }

        [ConfigurationProperty("host", IsRequired = true)]
        public string Host {
            get { return (string) this["host"]; }
        }

        [ConfigurationProperty("timeout")]
        [TimeSpanValidator(MinValueString = "00:00:00",
            MaxValueString = "00:01:00")]
        public TimeSpan Timeout {
            get { return (TimeSpan) this["timeout"]; }
        }
    }
}

Para probar la configuración podemos usar este simple app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="sample" type="Samples.SampleConfigurationSection, ValidatorSample"/>
  </configSections>
  <sample host="localhost" port="80" timeout="00:00:30" />
</configuration>

Y este pequeño programa de consola puede servirnos como simple prueba

using System;
using System.Configuration;

namespace Samples {
    internal class Program {
        private static void Main(string[] args) {
            var cfg = ConfigurationManager.GetSection("sample")
                as SampleConfigurationSection;
            if (cfg == null)
                return;

            Console.WriteLine("Host: {0}, Port: {1}, Timeout: {2}",
                cfg.Host, cfg.Port, cfg.Timeout);

            Console.ReadLine();
        }
    }
}

Si algún elemento no cumple con lo esperado por el validador el ConfigurationManager tirará una excepción de tipo ConfigurationErrorsException que a su vez en la propiedad Errors contendrá los ConfigurationException en forma de arreglo.