IDisposable Thoughts

Honey… where is my coding t-shirt?

Hey there! Thanks for dropping by Theme Preview! Take a look around
and grab the RSS feed to stay updated. See you around!

Posts Tagged ‘configuration’

.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.

.NET y Configuraciones – Parte 5

Aunque hemos creado colecciones de elementos en las secciones de configuración personalizadas no nos dejemos engañar, hay mucho más acerca de las colecciones de elementos de configuración que simplemente conocer como agregar elementos nuevos.

Tipos de colecciones de configuración

Existe un enumerador especial, ConfigurationElementCollectionType, que nos sirve como indicador de “cómo” debe ser el comportamiento de la colección. Al principio uno piensa “¿cómo así el comportamiento? una colección simplemente es un número finito secuencial de elementos”. Bueno, veamos en que difiere cada tipo:

AddRemoveClearMap El tipo por defecto, los elementos de la colección pueden unirse o fusionarse dentro de una jerarquía de archivos de configuración (por ejemplo, la jerarquía en Web.config). Existen operaciones de Clear, Remove, Add que permiten modificar las propiedades heredadas dentro de la jerarquía
AddRemoveClearMapAlternate Igual que AddRemoveClearMap, con la diferencia en que sus elementos ordenan sus elementos en la definición actual antes que la de la jerarquía. Estando los elementos locales antes que los elementos heredados (en la colección)
BasicMap Los elementos definidos en un nivel son compartidos con los de los niveles hijos, pero los niveles hijos no pueden modificar la colección definida por su padre
BasicMapAlternate Funciona igual al BasicMap, pero los elementos heredados son mostrados después que los elementos locales

Las colecciones de tipo BasicMap no poseen operaciones de add, remove, clear. Los tipos Alternate incluyen los elementos padres después de la definición de elementos locales. Lo más común es mantenerse en colecciones de tipo AddRemoveClearMap y utilizar BasicMap cuando realmente queremos asegurarnos que no se debe modificar la colección.

Definir nodos como elementos

Otra opción poco común pero sencilla de hacer es evitar utilizar la palabra add para agregar el nodo a la colección y en vez de eso utilizar el elemento como nodo de la colección. Esto es realmente sencillo y muchas veces nos puede mejorar la claridad del código de configuración. El secreto es simplemente hacer override de las propiedades de la colección que se encarga de retornar los nombres de los elementos en la colección, ElementName y CollectionType, y opcional a ello indicar el tipo de colección que utilizaremos marcando la colección con una propiedad de un atributo, ConfigurationCollectionAttribute.

using System.Configuration;

namespace Samples {
    [ConfigurationCollection(typeof (ServerConfigurationElement),
        CollectionType = ConfigurationElementCollectionType.BasicMap)]
    public class ServerConfigurationCollection : ConfigurationElementCollection {
        public ServerConfigurationElement this[int index] {
            get { return BaseGet(index) as ServerConfigurationElement; }
            set {
                if (BaseGet(index) != null)
                    BaseRemoveAt(index);
                BaseAdd(index, value);
            }
        }

        public new ServerConfigurationElement this[string name] {
            get { return BaseGet(name) as ServerConfigurationElement; }
        }

        public override ConfigurationElementCollectionType CollectionType {
            get { return ConfigurationElementCollectionType.BasicMap; }
        }

        protected override string ElementName {
            get { return "server"; }
        }

        protected override ConfigurationElement CreateNewElement() {
            return new ServerConfigurationElement();
        }

        protected override object GetElementKey(ConfigurationElement element) {
            var value = element as ServerConfigurationElement;
            return value != null ? value.Name : null;
        }
    }
}

Y nuestro archivo de configuración ahora tendría la siguiente forma

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="sample"
             type="Samples.SampleConfigurationSection, Samples.Cfg.AdvancedCollections"/>
  </configSections>
  <sample default="default">
    <servers>
      <server name="default" host="localhost" />
      <server name="another" port="8080" host="192.168.1.1" />
    </servers>
  </sample>
</configuration>

Algunos quizás encontraran esto ligeramente más fácil de leer que la versión original publicada en la parte 4 de esta serie.

Rayos, sin darnos cuenta llevamos ya cinco artículos sobre configuración personalizada en .Net (no se preocupen, aún faltan muchos más), el tiempo vuela cuando uno se divierte :P , como siempre agradezco sus comentarios, tags, pings, whatever… siempre son bienvenidos….

Y bueno, como dice Porky…

.NET y Configuraciones – Parte 4

Hasta el momento hemos tocado la creación de configuraciones personalizadas, elementos de configuración personalizados y la creación de grupos de secciones de configuración personalizados. Es interesante pero eso simplemente es la punta del iceberg de todo lo que dentro del concepto de configuración que nos permite manejar o mover la .NET Framework, hoy profundizaremos un poco más acerca de agregar nuevas características a nuestra configuración personalizada.

Colecciones y listas

Se que muchos hemos visto secciones de la configuración de ASP.NET y notado el uso de colecciones en él web.config, por ejemplo, el segmento de appSettings:

<appSettings>
    <clear />
    <remove name="port" />
    <add name="port" value="81" />
</appSettings>

Luego acceder a tal segmento de la configuración es "relativamente" sencillo, basta con algo como esto:

int port = int.Parse(ConfigurationManager.AppSettings["port"]);

Como habrán notado esto nos permite tener secciones que se comportan como colecciones de valores, que luego pueden ser accedidas por un indexer usando el nombre (en este caso "port") o el índice (en este caso, 0). Estas colecciones también soportan operaciones básicas (remove, clear, add). Crear colecciones de valores para la configuración no es para nada difícil simplemente hay que heredar de una clase abstracta especial, ConfigurationElementCollection

Comencemos por la unidad mínima, digamos que son elementos para datos de servidores

using System.Configuration;

namespace Samples {
    public class ServerConfigurationElement : ConfigurationElement {
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name {
            get { return (string) this["name"]; }
        }

        [ConfigurationProperty("port", DefaultValue = 80)]
        public int Port {
            get { return (int) this["port"]; }
        }

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

Hasta el momento nada diferente de lo que conocemos. Ahora agreguemos el soporte para la colección de elementos de configuración tipo ServerConfigurationElement

using System.Configuration;

namespace Samples {
    [ConfigurationCollection(typeof(ServerConfigurationElement))]
    public class ServerConfigurationCollection : ConfigurationElementCollection {
        protected override ConfigurationElement CreateNewElement() {
            return new ServerConfigurationElement();
        }

        protected override object GetElementKey(ConfigurationElement element) {
            var value = element as ServerConfigurationElement;
            return value != null ? value.Name : null;
        }

        public ServerConfigurationElement this[int index] {
            get {
                return BaseGet(index) as ServerConfigurationElement;
            }
            set {
                if (BaseGet(index) != null)
                    BaseRemoveAt(index);
                BaseAdd(index, value);
            }
        }

        public new ServerConfigurationElement this[string name] {
            get {
                return BaseGet(name) as ServerConfigurationElement;
            }
        }
    }
}

Obligatoriamente tenemos que implementar los métodos CreateNewElement (el cual creo su nombre e implementación son suficientemente descriptivos) y el método GetElementKey, este nos retorna el valor que tendrá la key del diccionario de elementos. Observen como implementamos los indexers que luego necesitaremos para acceder a los elementos de la colección. Nótese también que nuestra colección esta adornada con el atributo ConfigurationCollectionAttribute, el cual no es estrictamente necesario.

Bien, nos queda simplemente implementar la sección de configuración general, no muy diferente

using System.Configuration;

namespace Samples {
    public class SampleConfigurationSection : ConfigurationSection {
        [ConfigurationProperty("default", IsRequired = true)]
        public string DefaultServer {
            get { return (string) this["default"]; }
        }

        [ConfigurationProperty("servers", IsRequired = true)]
        public ServerConfigurationCollection Servers {
            get {
                return this["servers"]
                   as ServerConfigurationCollection;
            }
        }
    }
}

Nuestra configuración personalizada lucirá algo así:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section
        name="sample"
        type="Samples.SampleConfigurationSection, Samples.Cfg"/>
  </configSections>
  <sample default="default">
    <servers>
      <clear />
      <add name="default" host="localhost" />
      <add name="another" port="8080" host="192.168.1.1" />
    </servers>
  </sample>
</configuration>

Y en nuestro programa podemos acceder a ella de forma sencilla

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("Default item: {0}", cfg.DefaultServer);

            foreach (ServerConfigurationElement server in cfg.Servers) {
                Console.WriteLine("server: {0}, host: {1}, port: {2}",
                    server.Name, server.Host, server.Port);
            }

            ServerConfigurationElement defaultServer = cfg.Servers[cfg.DefaultServer];

            Console.WriteLine("Default host: {0}", defaultServer.Host);
            Console.ReadLine();
        }
    }
}

Como les advertí desde el principio, nada complicado y sumamente útil. Creo que vale la pena mencionar que cualquier comentario, sugerencia, duda u observación soy todo ojos para leer sus comentarios.

Cómo dijo Terminator, volveré…

.NET y Configuraciones – Parte 3

Ok, hasta el momento ya vimos como crear nuestras propias secciones y valores de configuración, además de darnos una vuelta por las opciones de crear y agregar elementos a nuestras secciones personalizadas. Hoy continuaremos indagando un poco más en el mundo de las configuraciones personalizadas en .NET.

Grupos de Secciones

Imagínense que la configuración de nuestra aplicación es mucho más compleja de lo usual y que además de elementos personalizados necesitamos agregar “subsecciones” de configuración. Dentro de la .Net Framework es algo común en ASP.NET. Crear secciones de configuración no puede ser más fácil, basta con crear las secciones como lo hemos estado haciendo hasta el momento. Tomemos por ejemplo las siguientes dos secciones:

public class SampleSection : ConfigurationSection {
    [ConfigurationProperty("name", IsRequired = true)]
    public string Name {
        get { return (string) base["name"]; }
        set { base["name"] = value; }
    }

    [ConfigurationProperty("port", DefaultValue = 80)]
    public int Port {
        get { return (int) base["port"]; }
        set { base["port"] = value; }
    }
}

public class AnotherSection : ConfigurationSection {
    [ConfigurationProperty("times", IsRequired = true)]
    public int TimesPerDay {
        get { return (int) base["times"]; }
        set { base["times"] = value; }
    }
}

Basta ahora con crear un ConfigurationSectionGroup para obtener ambas secciones dentro de un mismo grupo

public class SampleSectionGroup : ConfigurationSectionGroup {
    [ConfigurationProperty("sample")]
    public SampleSection Sample {
        get { return (SampleSection) Sections["sample"]; }
    }

    [ConfigurationProperty("another")]
    public AnotherSection Another {
        get { return (AnotherSection) Sections["another"]; }
    }
}

Agregarla en el web.config es un poco más laborioso pero continúa siendo sencillo

<configuration>
  <configSections>
    <sectionGroup name="sampleGroup" type="WebApplication2.SampleSectionGroup">
      <section name="sample" type="WebApplication2.SampleSection"/>
      <section name="another" type="WebApplication2.AnotherSection"/>
    </sectionGroup>
  </configSections>
  <sampleGroup>
    <sample name="sample" port="31" />
    <another times="5" />
  </sampleGroup>
</configuration>

Creo que para estas alturas el usarla en el código debe ser algo sencillo y "straighforward"

.NET y Configuraciones – Parte 2

Anteriormente habíamos conversado acerca de como trabajar nuestras propias secciones “customizadas” de configuración usando las facilidades de configuración que nos da la .Net Framework. Como vemos es sumamente fácil crear nuestras secciones “personalizadas”. Conversaremos hoy un poco más de como sacarle mayor provecho y extender nuestras secciones de configuración y así sacar mucho mayor provecho.

Elementos personalizados anidados

Observemos el siguiente snippet de código

public class HostInfo : ConfigurationElement {
    [ConfigurationProperty("server", DefaultValue = "localhost")]
    public string Server {
        get { return (string) base["server"]; }
        set { base["server"] = value; }
    }

    [ConfigurationProperty("port", DefaultValue = 80)]
    public int Port {
        get { return (int) base["port"]; }
        set { base["port"] = value; }
    }

    [ConfigurationProperty("ssl", DefaultValue = false)]
    public bool IsSecure {
        get { return (bool) base["ssl"]; }
        set { base["ssl"] = value; }
    }
}

Aunque nos recuerda a la forma en que creamos una sección de configuración, realmente estamos creando un elemento de la configuración (ConfigurationElement). En otras palabras, podemos tener elementos anidados y de esa manera lograr que nuestros elementos de configuración obedezcan a una más clara semántica. Utilizarlo en la sección de configuración no puede ser más fácil

public class ServerConfigurationSection : ConfigurationSection {
    [ConfigurationProperty("name", IsRequired = true)]
    public string Name {
        get { return (string)base["name"]; }
        set { base["name"] = value; }
    }

    [ConfigurationProperty("host")]
    public HostInfo Host {
        get { return (HostInfo)base["host"]; }
        set { base["host"] = value; }
    }
}

Usarlo no puede ser más fácil que esto

<configuration>
  <configSections>
    <section name="server" type="WebApplication2.ServerConfigurationSection"/>
  </configSections>
  <server name="myconfiguration">
    <host server="192.168.1.1" port="81"/>
  </server>
</configuration>

.NET y configuraciones – Parte 1

Hace unos días 0Gis0 publicó un buen artículo sobre el archivo de configuración en .Net y como crear nuestras propias secciones personalizadas. Bien, he decidido ahondar un “poco” más en el asunto aprovechando el hecho que tengo prometido a un par de amigos desde hace ratos que publicaría acerca de configuración en .Net, es buena idea que antes le den una rápida leída al artículo que en cuestión.

Recapitulando lo que nos menciona nuestra compañera, es sencillo crear una sección personalizada de configuración en .Net, basta solamente de heredar de la clase ConfigurationSection y listo. Bueno, no tan rápido, hay un par de cosas de hacer notar, la sección personalizada de configuración debe exponer propiedades que se mapean a las opciones de configuración, estas propiedades deben estar “adornados” con los atributos ConfigurationProperty y obligatoriamente debemos indicar a que atributo de la sección de configuración a la cual se mapea. A partir de esto usamos el indexer de la clase base y la casteamos al tipo que necesitamos.

public class SimpleConfigurationSection : ConfigurationSection {

    [ConfigurationProperty("port")]
    public int Port {
       get {
           return (int) this["port"];
       }
       set {
           this["port"] = value;
       }
    }
}

Esto correspondería a algo así en el archivo de configuración

<configSections>
    <section name="smtp" type="SimpleConfigurationSection, MyAssembly" />
</configSections>
<smtp port="25">

Simple y sencillo, de igual manera podemos indicar si el elemento es requerido y también el valor por defecto de la propiedad con simplemente modificar un poco el atributo

public class SimpleConfigurationSection : ConfigurationSection {

    [ConfigurationProperty("port", DefaultValue = 80, IsRequired = true)]
    public int Port {
       get {
           return (int) this["port"];
       }
       set {
           this["port"] = value;
       }
    }
}

Simple y sencillo, como lo menciona nuestra amiga. Con esto como base podemos acceder la configuración de forma simple

var config = (SimpleConfigurationSection) ConfigurationManager.GetSection("smtp");
var port = config.Port;

Con esto como base estamos listos para profundizar un poco más en el asunto. En la próxima entrega veremos como definir jerarquías de configuración en nuestras secciones personalizadas.

Saludos!

UPDATE: otros post de la serie han sido publicados

Lo nuevo en ASP.NET 4.0: El reducido Web.config

¿Recuerdan a nuestro viejo amigo el Web.config? si, el archivo de configuración de nuestra aplicación web. En este archivo solemos configurar distintas cosas como: El modo de autenticación/autorización de nuestra aplicación, los handlers de request ASP.NET, los módulos de request/response de ASP.NET, los settings de la aplicación y los assemblies y modo de ejecución de nuestra app. Tradicionalmente el Web.config es uno de esos archivos grandes e inmutables, solemos cambiarle una cosa o agregale una que otra, pero en cuanto a porcentaje de cambio, casi que se diría que el web.config no suele cambiar. Un típico web.config en Visual Studio 2008/ASP.NET 3.5 SP1 suele lucir así (comentarios removidos por “claridad”):

<?xml version="1.0"?>
<configuration>
    <configSections>
      <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
        <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
          <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
          <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
            <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere" />
            <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
            <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
            <section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
          </sectionGroup>
        </sectionGroup>
      </sectionGroup>
    </configSections>
    <appSettings/>
    <connectionStrings/>
    <system.web>
        <compilation debug="false">
          <assemblies>
            <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
            <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
            <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
            <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
          </assemblies>
        </compilation>
        <authentication mode="Windows" />
      <pages>
        <controls>
          <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
          <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        </controls>
      </pages>
      <httpHandlers>
        <remove verb="*" path="*.asmx"/>
        <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>
      </httpHandlers>
      <httpModules>
        <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      </httpModules>
    </system.web>
    <system.codedom>
      <compilers>
        <compiler language="c#;cs;csharp" extension=".cs" warningLevel="4"
                  type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
          <providerOption name="CompilerVersion" value="v3.5"/>
          <providerOption name="WarnAsError" value="false"/>
        </compiler>
     </compilers>
    </system.codedom>
    <system.webServer>
      <validation validateIntegratedModeConfiguration="false"/>
      <modules>
        <remove name="ScriptModule" />
        <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      </modules>
      <handlers>
        <remove name="WebServiceHandlerFactory-Integrated"/>
        <remove name="ScriptHandlerFactory" />
        <remove name="ScriptHandlerFactoryAppServices" />
        <remove name="ScriptResource" />
        <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode"
             type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode"
             type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </handlers>
    </system.webServer>
    <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <dependentAssembly>
          <assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/>
          <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/>
          <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
        </dependentAssembly>
      </assemblyBinding>
    </runtime>
</configuration>

Uff! y eso que sin comentarios! creo que bastante verbose para no ser un archivo que cambia constantemente. En Visual Studio 2010 con ASP.NET 4.0 el web.config fue sometido a una fuerte “dieta” (web.config de un “empty” web application, luego hablaremos de que implica/significa eso):

<?xml version="1.0"?>
<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
</configuration>

Si, no hay error, son solamente 6 líneas… ¿drástico el cambio verdad? ¿Cómo lograron esto? ¿Qué hicieron exactamente? simple, los desarrolladores de ASP.NET se dieron cuenta de las opciones y configuraciones más empleadas en aplicaciones ASP.NET webforms y movieron las partes que no suelen cambiar a la jerarquía principal de configuración de .Net, estos archivos de jerarquía se pueden encontrar en el directorio C:\Windows\Microsoft.NET\Framework\v4.0.x donde encontraran archivos como machine.config, web.config y otros según el nivel de "confianza" del servidor (web_mediumtrust, web_hightrust, web_lowtrust). ¿Se puede volver a configurar los settings como queremos? claro que si, simplemente basta que agreguemos las secciones que nos interesan, y para eso también hay intellisense.

Más acerca de la jerarquía de configuración en ASP.NET y la .Net Framework en MSDN, también podemos aprender un poco sobre los archivos de poliza y niveles de confianza personalizados en Channel 9.

¡Saludos y hasta la próxima entrega!

Entre las cosas interesantes que muchos hacemos a diario (o bueno, configuramos a diario) y que muy pocas veces nos preguntamos ¿cómo lo hicieron? esta el uso de handlers o providers en la .Net Framework. Por ejemplo, muchas veces vemos algo como:

<viewStatePersisters default="cachePersister">
    <persisters>
        <add name="pagePersister" type="Rioshu.Web.ViewStateHandlers.TinyPagePersister, Rioshu.Web.ViewStateHandlers" />
        <add name="cachePersister" type="Rioshu.Web.ViewStateHandlers.WebCachePersister, Rioshu.Web.ViewStateHandlers" />
    </persisters>
</viewStatePersisters>

Y asumimos correctamente que la framework tomará esa información e instanciará el manejador o clase correspondiente que hemos indicado por su Full Qualified Name. ¿Cómo hace la framework para cargar el tipo correcto en el assembly correcto y luego instanciarlo?. La respuesta es simple, una mezcla de propiedades del tipo del objeto y a un amigo llamado “Activator”.

Cuando me refiero al “tipo del objeto” no me refiero a si es Integer, String u Object. En la .Net Framework existe un “objeto especial” cuya responsabilidad es brindar información acerca del “tipo”, este objeto es System.Type, bien, Type tiene un método especial llamado GetType, al cual podemos pasarle el Full Qualified Name de la clase y el .Net Assembly and Type Loader intentará cargarlo.

public class TypeLoaderFacts
{
    private const string ExternalTypeName = "ExternalLibrary.ExternalServiceImplementation, ExternalLibrary";

    [Fact]
    public void It_can_load_a_type_by_his_fqn()
    {
        var type = Type.GetType(ExternalTypeName);
        Assert.Equal(typeof(ExternalServiceImplementation), type);
    }
}

Bien, con cargar el tipo no hacemos mucho, es hora de inicializarlo.

public class TypeLoaderFacts
{
    private const string ExternalTypeName = "ExternalLibrary.ExternalServiceImplementation, ExternalLibrary";

    private Type ExternalType;

    public TypeLoaderFacts()
    {
        ExternalType = Type.GetType(ExternalTypeName);
    }

    [Fact]
    public void It_can_load_a_type_by_his_fqn()
    {
        Assert.Equal(typeof(ExternalServiceImplementation), ExternalType);
    }

    [Fact]
    public void It_can_ctor_a_type()
    {
        var instance = Activator.CreateInstance(ExternalType);
        Assert.NotNull(instance);
    }
}

Pero como ustedes siguen también buenas prácticas, recordaran que es buena idea programar en base a interface y no a una clase concreta, así que podemos preguntarle al tipo si implementa la clase (algo que vamos a hacer usando un pequeño método de extensión).

public class TypeLoaderFacts
{
    private const string ExternalTypeName = "ExternalLibrary.ExternalServiceImplementation, ExternalLibrary";

    private Type ExternalType;

    public TypeLoaderFacts()
    {
        ExternalType = Type.GetType(ExternalTypeName);
    }

    [Fact]
    public void It_can_load_a_type_by_his_fqn()
    {
        Assert.Equal(typeof(ExternalServiceImplementation), ExternalType);
    }

    [Fact]
    public void It_can_ctor_a_type()
    {
        var instance = Activator.CreateInstance(ExternalType);
        Assert.NotNull(instance);
    }

    [Fact]
    public void It_implements_our_interface()
    {
        Assert.True(ExternalType.IsImplementationOf<IExternalService>());
    }
}

Bien, les queda de tarea el averiguar como crear el extension method en cuestion :)

Como ven es sumamente fácil llegar a implementar dynamic loaders en base a un archivo de configuración de esta manera (‘ala’ web.config de asp.net o NHibernate).

Como siempre cualquier comentario estamos a un mensaje de distancia!