As ‘custom sections’ dos ficheiros de configuração permitem-nos configurar as configurações dos nossos programas, adicionando ao ficheiro de configuração (ex app.config) elementos feitos à medida da nossa aplicação.

Numa aplicação que estou a desenvolver, quero que seja possível mudar os forms sem ter de recompilar a aplicação, para que, num outro passo, possa usar o mesmo código para correr o programa em mobile/linux/mac/etc. Assim desenhei o seguinte ficheiro de configuração:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="applicationForms" type="UI.Factory.Configuration.FormsSection, UI.Factory" />
  </configSections>
  <appSettings>
  </appSettings>
  <applicationForms>
    <forms>
      <add name="ISearchingForm" type="UI.WinForms.SearchingForm, UI.WinForms" />
      <add name="IWizardWelcomeForm" type="UI.WinForms.FirstRun.StartForm, UI.WinForms" />
      <add name="IWizardChooseDirectoryForm" type="UI.WinForms.FirstRun.ChooseDirectoryForm, UI.WinForms" />
      <add name="IWizardThankYouForm" type="UI.WinForms.FirstRun.ThankYouForm, UI.WinForms" />
    </forms>
  </applicationForms>
</configuration>

Este ficheiro contém dois elementos de interesse. O primeiro define um nome de uma secção nova, configurando também a classe que vai conseguir ler essa nova secção:

<configSections>
    <section name="applicationForms" type="UI.Factory.Configuration.FormsSection, UI.Factory" />
</configSections>

Depois, o elemento ‘applicationForms’, que é definido pela classe ‘FormsSection’:

public class FormsSection : ConfigurationSection
{
    [ConfigurationProperty("forms")]
    public FormsElementCollection Forms
    {
        get { return (FormsElementCollection)base["forms"]; }
        set { base["forms"] = value; }
    }
}
 
public class FormsElementCollection : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new FormElement();
    }
 
    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((FormElement)element).Name;
    }
 
    public void Add(FormElement element)
    {
        BaseAdd(element);
    }
}
 
public class FormElement : ConfigurationElement
{
    [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
    public string Name
    {
        get { return (string)base["name"]; }
        set { base["name"] = value; }
    }
 
    [ConfigurationProperty("type", IsRequired = true)]
    public string Type
    {
        get { return (string)base["type"]; }
        set { base["type"] = value; }
    }
}

A secção pode ser acedida no código assim:

FormsSection section = (FormsSection)ConfigurationManager.GetSection("applicationForms");

Qualquer um dos atributos ou sub-elementos do elemento ‘applicationsForms’ pode ser configurado de acordo com a especificidade do problema, podendo mesmo ser possível ter elementos recursivos ou, um pouco mais interessante, com aptidão para lerem atributos não esperados:

public class FormElement : ConfigurationElement
{
    ...
 
    protected override bool OnDeserializeUnrecognizedAttribute(string name, string value)
    {
        ConfigurationProperty property = new ConfigurationProperty(name, typeof(string));
        Properties.Add(property);
        SetPropertyValue(property, value, false);
        return true;
    }
}