Creación de secciones complejas en el archivo de configuración

En muchas ocasiones es necesario almacenar una serie de valores que nos permitan tanto reutilizar nuestro código como reconfigurar nuestra aplicación sin que ello suponga un nuevo despliegue. Para ello, lo ideal sería que todos aquellos valores susceptibles al cambio sean trasladados al archivo de configuración. Hace tiempo, mostré una forma sencilla de hacerlo, pero en ocasiones queda mucho más elegante cuando las secciones pueden llegar a ser algo más complejas. Siguiendo el post anterior relacionado, imaginemos que en vez de tener una única configuración smtp queremos almacenar varias. Por ejemplo, algo como esto:

<SmtpConfiguration>
  <SmtpSetting  name="Development"
                url="development.smtp.com"
                port="24" />
  <SmtpSetting name="Staging"
               url="staging.smtp.com"
               port="23" />
  <SmtpSetting name="Production"
               url="production.smtp.com"
               port="25" />
</SmtpConfiguration>

Para poder crear este tipo de estructura es necesario involucrar alguna clase más que en el caso expuesto meses atrás, pero creo que es mucho más claro, a la par que elegante 😀 ¡Vamos a verlo!

Elemento SmtpSetting

El primer punto que vamos a tratar es la estructura que deberán tener los elementos que contiene la sección. Para ello, creamos una nueva clase llamada SmtpSetting con el siguiente código:

using System.Configuration;
namespace CustomWebConfigurationSection
{
    public class SmtpSetting : ConfigurationElement
    {
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get
            {
                return (string)this["name"];
            }
            set
            {
                this["name"] = value;
            }
        }
        [ConfigurationProperty("url", IsRequired = true)]
        public string Url
        {
            get
            {
                return (string)this["url"];
            }
            set
            {
                this["url"] = value;
            }
        }
        [ConfigurationProperty("port", IsRequired = true)]
        public int Port
        {
            get
            {
                return (int)this["port"];
            }
            set
            {
                this["port"] = value;
            }
        }
    }
}

En esta clase estamos heredando de ConfigurationElement para indicar que representa la estructura de un elemento de la configuración que vamos a crear.

SmtpSettingCollection

Cuando tenemos elementos del mismo tipo, debemos crear una clase intermedia que herede de ConfigurationElementCollection que agrupará los mismos como colección.

using System;
using System.Configuration;
namespace CustomWebConfigurationSection
{
    public class SmtpSettingCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new SmtpSetting();
        }
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((SmtpSetting)element).Name;
        }
        protected override bool IsElementName(string elementName)
        {
            return !String.IsNullOrEmpty(elementName) && elementName == "SmtpSetting";
        }
        public override ConfigurationElementCollectionType CollectionType
        {
            get
            {
                return ConfigurationElementCollectionType.BasicMap;
            }
        }
    }
}

Sección SmtpConfiguration

Para finalizar es necesario declara la clase que engloba nuestra colección de configuraciones Smtps. Para ello, heredamos de ConfigurationSection.

using System.Configuration;
using System.Linq;
namespace CustomWebConfigurationSection
{
    public class SmtpConfiguration : ConfigurationSection
    {
        [ConfigurationProperty("", IsDefaultCollection = true, IsKey = false, IsRequired = true)]
        public SmtpSettingCollection Smtps
        {
            get { return base[""] as SmtpSettingCollection; }
            set { base[""] = value; }
        }
        public bool Contains(string name)
        {
            return Smtps.Cast<SmtpSetting>().Any(smtp => smtp.Name == name);
        }
    }
}

Si quisiéramos comprobar que todo funciona correctamente, podríamos utilizar una aplicación de tipo consola con la siguiente configuración:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="SmtpConfiguration" type="CustomWebConfigurationSection.SmtpConfiguration, CustomWebConfigurationSection"
             allowLocation="true"
             allowDefinition="Everywhere"/>
  </configSections>
  <SmtpConfiguration>
    <SmtpSetting  name="Development"
                  url="development.smtp.com"
                  port="24" />
    <SmtpSetting name="Staging"
                 url="staging.smtp.com"
                 port="23" />
    <SmtpSetting name="Production"
                 url="production.smtp.com"
                 port="25" />
  </SmtpConfiguration>
</configuration>

Y dentro del método Main las siguientes líneas para recuperarlo a través de SmtpConfiguration:

using System;
using System.Configuration;
namespace CustomWebConfigurationSection
{
    class Program
    {
        static void Main()
        {
            var smtpConfiguration = ConfigurationManager.GetSection("SmtpConfiguration") as SmtpConfiguration;
            foreach (SmtpSetting smtp in smtpConfiguration.Smtps)
            {
                Console.WriteLine("Name: {0}", smtp.Name);
                Console.WriteLine("Url: {0}", smtp.Url);
                Console.WriteLine("Port: {0}", smtp.Port);
            }
            Console.ReadLine();
        }
    }
}

Espero que sea de utilidad 😀

¡Saludos!