Cómo implementar una ConfigurationSection con un ConfigurationElementCollection

166

Estoy tratando de implementar una sección de configuración personalizada en un proyecto y sigo corriendo con excepciones que no entiendo. Espero que alguien pueda completar los espacios en blanco aquí.

Tengo App.configque se ve así:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="ServicesSection" type="RT.Core.Config.ServicesConfigurationSectionHandler, RT.Core"/>
    </configSections>
    <ServicesSection type="RT.Core.Config.ServicesSection, RT.Core">
            <Services>
                <AddService Port="6996" ReportType="File" />
                <AddService Port="7001" ReportType="Other" />
            </Services>
        </ServicesSection>
</configuration>

Tengo un ServiceConfigelemento definido así:

public class ServiceConfig : ConfigurationElement
  {
    public ServiceConfig() {}

    public ServiceConfig(int port, string reportType)
    {
      Port = port;
      ReportType = reportType;
    }

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

    [ConfigurationProperty("ReportType", DefaultValue = "File", IsRequired = true, IsKey = false)]
    public string ReportType
    {
      get { return (string) this["ReportType"]; }
      set { this["ReportType"] = value; }
    }
  }

Y tengo un ServiceCollectiondefinido así:

public class ServiceCollection : ConfigurationElementCollection
  {
    public ServiceCollection()
    {
      Console.WriteLine("ServiceCollection Constructor");
    }

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

    public void Add(ServiceConfig serviceConfig)
    {
      BaseAdd(serviceConfig);
    }

    public void Clear()
    {
      BaseClear();
    }

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

    protected override object GetElementKey(ConfigurationElement element)
    {
      return ((ServiceConfig) element).Port;
    }

    public void Remove(ServiceConfig serviceConfig)
    {
      BaseRemove(serviceConfig.Port);
    }

    public void RemoveAt(int index)
    {
      BaseRemoveAt(index);
    }

    public void Remove(string name)
    {
      BaseRemove(name);
    }
  }

La parte que me falta es qué hacer con el controlador. Originalmente, intenté implementar un IConfigurationSectionHandlerpero encontré dos cosas:

  1. no funcionó
  2. Está en desuso.

Ahora estoy completamente perdido sobre qué hacer para poder leer mis datos desde la configuración. Cualquier ayuda por favor!

Chris Holmes
fuente
No puedo hacer que esto funcione. Me encantaría ver RT.Core.Config.ServicesSection. Acabo de recibir el elemento 'AddService' no reconocido a pesar de usar también el código de la respuesta aceptada.
sirdank
También me perdí esto al principio: esta parte: [ConfigurationCollection (typeof (ServiceCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")] AddItemName tiene que coincidir, así que si cambiaste "agregar" a "addService" funcionaría
HeatherD

Respuestas:

188

La respuesta anterior es correcta, pero también te daré todo el código.

Su app.config debería verse así:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <section name="ServicesSection" type="RT.Core.Config.ServiceConfigurationSection, RT.Core"/>
   </configSections>
   <ServicesSection>
      <Services>
         <add Port="6996" ReportType="File" />
         <add Port="7001" ReportType="Other" />
      </Services>
   </ServicesSection>
</configuration>

Tu ServiceConfigy ServiceCollectionclases permanecen sin cambios.

Necesitas una nueva clase:

public class ServiceConfigurationSection : ConfigurationSection
{
   [ConfigurationProperty("Services", IsDefaultCollection = false)]
   [ConfigurationCollection(typeof(ServiceCollection),
       AddItemName = "add",
       ClearItemsName = "clear",
       RemoveItemName = "remove")]
   public ServiceCollection Services
   {
      get
      {
         return (ServiceCollection)base["Services"];
      }
   }
}

Y eso debería hacer el truco. Para consumirlo puedes usar:

ServiceConfigurationSection serviceConfigSection =
   ConfigurationManager.GetSection("ServicesSection") as ServiceConfigurationSection;

ServiceConfig serviceConfig = serviceConfigSection.Services[0];
Russell McClure
fuente
10
Las [Add|Remove|Clear]ItemNamepropiedades en el ConfigurationCollectionatributo no son realmente necesarias en este caso, porque "agregar" / "borrar" / "eliminar" ya son los nombres predeterminados de los elementos XML.
Wim Coenen
2
¿Cómo puedo hacer que funcione para que las etiquetas no se agreguen? Solo parece funcionar si se agregan. No funcionaría si fuera <Service Port = "6996" ReportType = "File" /> o <Service Port = "7001" ReportType = "Other" />
JonathanWolfson
77
@JonathanWolfson: simplemente cambie AddItemName = "add" a AddItemName = "Service"
Mubashar
¿Sigue siendo el enfoque para .NET 4.5?
aplastar
66
@crush: sí, no hay muchos cambios en este rincón polvoriento de .NET.
Russell McClure
84

Si está buscando una sección de configuración personalizada como la siguiente

<CustomApplicationConfig>
        <Credentials Username="itsme" Password="mypassword"/>
        <PrimaryAgent Address="10.5.64.26" Port="3560"/>
        <SecondaryAgent Address="10.5.64.7" Port="3570"/>
        <Site Id="123" />
        <Lanes>
          <Lane Id="1" PointId="north" Direction="Entry"/>
          <Lane Id="2" PointId="south" Direction="Exit"/>
        </Lanes> 
</CustomApplicationConfig>

entonces puedes usar mi implementación de la sección de configuración para comenzar a agregar una System.Configurationreferencia de ensamblaje a tu proyecto

Mire cada uno de los elementos anidados que utilicé, el primero es Credenciales con dos atributos, así que agreguemos primero

Elemento de credenciales

public class CredentialsConfigElement : System.Configuration.ConfigurationElement
    {
        [ConfigurationProperty("Username")]
        public string Username
        {
            get 
            {
                return base["Username"] as string;
            }
        }

        [ConfigurationProperty("Password")]
        public string Password
        {
            get
            {
                return base["Password"] as string;
            }
        }
    }

Agente primario y Agente secundario

Ambos tienen los mismos atributos y parecen una Dirección a un conjunto de servidores para una conmutación por error primaria y, por lo que solo necesita crear una clase de elemento para ambos, como los siguientes

public class ServerInfoConfigElement : ConfigurationElement
    {
        [ConfigurationProperty("Address")]
        public string Address
        {
            get
            {
                return base["Address"] as string;
            }
        }

        [ConfigurationProperty("Port")]
        public int? Port
        {
            get
            {
                return base["Port"] as int?;
            }
        }
    }

Explicaré cómo usar dos elementos diferentes con una clase más adelante en esta publicación, omita el SiteId ya que no hay diferencia en él. Solo tiene que crear una clase igual que la anterior con una sola propiedad. veamos cómo implementar la colección Lanes

se divide en dos partes, primero debe crear una clase de implementación de elemento y luego crear una clase de elemento de colección

LaneConfigElement

public class LaneConfigElement : ConfigurationElement
    {
        [ConfigurationProperty("Id")]
        public string Id
        {
            get
            {
                return base["Id"] as string;
            }
        }

        [ConfigurationProperty("PointId")]
        public string PointId
        {
            get
            {
                return base["PointId"] as string;
            }
        }

        [ConfigurationProperty("Direction")]
        public Direction? Direction
        {
            get
            {
                return base["Direction"] as Direction?;
            }
        }
    }

    public enum Direction
    { 
        Entry,
        Exit
    }

puede notar que un atributo de LanElementes una Enumeración y si intenta usar cualquier otro valor en la configuración que no esté definido en la aplicación Enumeración arrojará un System.Configuration.ConfigurationErrorsExceptioninicio. Ok, pasemos a la definición de colección

[ConfigurationCollection(typeof(LaneConfigElement), AddItemName = "Lane", CollectionType = ConfigurationElementCollectionType.BasicMap)]
    public class LaneConfigCollection : ConfigurationElementCollection
    {
        public LaneConfigElement this[int index]
        {
            get { return (LaneConfigElement)BaseGet(index); }
            set
            {
                if (BaseGet(index) != null)
                {
                    BaseRemoveAt(index);
                }
                BaseAdd(index, value);
            }
        }

        public void Add(LaneConfigElement serviceConfig)
        {
            BaseAdd(serviceConfig);
        }

        public void Clear()
        {
            BaseClear();
        }

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

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((LaneConfigElement)element).Id;
        }

        public void Remove(LaneConfigElement serviceConfig)
        {
            BaseRemove(serviceConfig.Id);
        }

        public void RemoveAt(int index)
        {
            BaseRemoveAt(index);
        }

        public void Remove(String name)
        {
            BaseRemove(name);
        }

    }

puedes notar que he configurado el AddItemName = "Lane"puedes elegir lo que quieras para el elemento de entrada de tu colección, prefiero usar "agregar" el predeterminado pero lo cambié solo por el bien de esta publicación.

Ahora todos nuestros elementos anidados se han implementado, ahora debemos agregar todos los de una clase que tiene que implementar System.Configuration.ConfigurationSection

CustomApplicationConfigSection

public class CustomApplicationConfigSection : System.Configuration.ConfigurationSection
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(CustomApplicationConfigSection));
        public const string SECTION_NAME = "CustomApplicationConfig";

        [ConfigurationProperty("Credentials")]
        public CredentialsConfigElement Credentials
        {
            get
            {
                return base["Credentials"] as CredentialsConfigElement;
            }
        }

        [ConfigurationProperty("PrimaryAgent")]
        public ServerInfoConfigElement PrimaryAgent
        {
            get
            {
                return base["PrimaryAgent"] as ServerInfoConfigElement;
            }
        }

        [ConfigurationProperty("SecondaryAgent")]
        public ServerInfoConfigElement SecondaryAgent
        {
            get
            {
                return base["SecondaryAgent"] as ServerInfoConfigElement;
            }
        }

        [ConfigurationProperty("Site")]
        public SiteConfigElement Site
        {
            get
            {
                return base["Site"] as SiteConfigElement;
            }
        }

        [ConfigurationProperty("Lanes")]
        public LaneConfigCollection Lanes
        {
            get { return base["Lanes"] as LaneConfigCollection; }
        }
    }

Ahora puede ver que tenemos dos propiedades con nombre PrimaryAgenty SecondaryAgentambas tienen el mismo tipo. Ahora puede comprender fácilmente por qué solo teníamos una clase de implementación para estos dos elementos.

Antes de poder utilizar esta sección de configuración recién inventada en su app.config (o web.config) solo necesita decirle a la aplicación que ha inventado su propia sección de configuración y darle un poco de respeto, para ello debe agregar las siguientes líneas en app.config (puede ser justo después del inicio de la etiqueta raíz).

<configSections>
    <section name="CustomApplicationConfig" type="MyNameSpace.CustomApplicationConfigSection, MyAssemblyName" />
  </configSections>

NOTA: MyAssemblyName debe estar sin .dll, por ejemplo, si el nombre del archivo de ensamblado es myDll.dll, use myDll en lugar de myDll.dll

para recuperar esta configuración, use la siguiente línea de código en cualquier lugar de su aplicación

CustomApplicationConfigSection config = System.Configuration.ConfigurationManager.GetSection(CustomApplicationConfigSection.SECTION_NAME) as CustomApplicationConfigSection;

Espero que la publicación anterior lo ayude a comenzar con un tipo un poco complicado de secciones de configuración personalizadas.

Happy Coding :)

**** Editar **** Para habilitar LINQ en LaneConfigCollectiontienes que implementarIEnumerable<LaneConfigElement>

Y agregue la siguiente implementación de GetEnumerator

public new IEnumerator<LaneConfigElement> GetEnumerator()
        {
            int count = base.Count;
            for (int i = 0; i < count; i++)
            {
                yield return base.BaseGet(i) as LaneConfigElement;
            }
        }

para las personas que todavía están confundidas acerca de cómo funciona realmente el rendimiento, lea este bonito artículo

Dos puntos clave tomados del artículo anterior son

en realidad no termina la ejecución del método. return return detiene la ejecución del método y la próxima vez que lo llame (para el siguiente valor de enumeración), el método continuará ejecutándose desde la última llamada de return. Suena un poco confuso, creo ... (Shay Friedman)

El rendimiento no es una característica del tiempo de ejecución .Net. Es solo una característica del lenguaje C # que el compilador C # compila en código IL simple. (Lars Corneliussen)

Mubashar
fuente
3
Gracias por proporcionar un ejemplo completo, ¡esto realmente ayuda mucho!
John Leidegren
46

Este es un código genérico para la colección de configuración:

public class GenericConfigurationElementCollection<T> :   ConfigurationElementCollection, IEnumerable<T> where T : ConfigurationElement, new()
{
    List<T> _elements = new List<T>();

    protected override ConfigurationElement CreateNewElement()
    {
        T newElement = new T();
        _elements.Add(newElement);
        return newElement;
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return _elements.Find(e => e.Equals(element));
    }

    public new IEnumerator<T> GetEnumerator()
    {
        return _elements.GetEnumerator();
    }
}

Después de que lo haya hecho GenericConfigurationElementCollection, puede usarlo simplemente en la sección de configuración (este es un ejemplo de mi Dispatcher):

public class  DispatcherConfigurationSection: ConfigurationSection
{
    [ConfigurationProperty("maxRetry", IsRequired = false, DefaultValue = 5)]
    public int MaxRetry
    {
        get
        {
            return (int)this["maxRetry"];
        }
        set
        {
            this["maxRetry"] = value;
        }
    }

    [ConfigurationProperty("eventsDispatches", IsRequired = true)]
    [ConfigurationCollection(typeof(EventsDispatchConfigurationElement), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
    public GenericConfigurationElementCollection<EventsDispatchConfigurationElement> EventsDispatches
    {
        get { return (GenericConfigurationElementCollection<EventsDispatchConfigurationElement>)this["eventsDispatches"]; }
    }
}

El elemento de configuración es config aquí:

public class EventsDispatchConfigurationElement : ConfigurationElement
{
    [ConfigurationProperty("name", IsRequired = true)]
    public string Name
    {
        get
        {
            return (string) this["name"];
        }
        set
        {
            this["name"] = value;
        }
    }
}

El archivo de configuración se vería así:

<?xml version="1.0" encoding="utf-8" ?>
  <dispatcherConfigurationSection>
    <eventsDispatches>
      <add name="Log" ></add>
      <add name="Notification" ></add>
      <add name="tester" ></add>
    </eventsDispatches>
  </dispatcherConfigurationSection>

Espero que ayude!

Mzf
fuente
¡Frio! Estaba pensando lo mismo y descubrí que no estoy solo. Deseo que MS implemente eso para todas las configuraciones de FCL
abatishchev
¿Alguna sugerencia sobre cómo hacer eso con un BasicMap para los elementos? No quiero implementar Agregar si puedo evitarlo.
SpaceCowboy74
28

Una alternativa más fácil para aquellos que prefieren no escribir toda esa plantilla de configuración manualmente ...

1) Instalar Nerdle.AutoConfig desde NuGet

2) Defina su tipo de ServiceConfig (ya sea una clase concreta o simplemente una interfaz, ya sea)

public interface IServiceConfiguration
{
    int Port { get; }
    ReportType ReportType { get; }
}

3) Necesitará un tipo para guardar la colección, por ejemplo

public interface IServiceCollectionConfiguration
{
    IEnumerable<IServiceConfiguration> Services { get; } 
}

4) Agregue la sección de configuración de esta manera (observe el nombre de camelCase)

<configSections>
  <section name="serviceCollection" type="Nerdle.AutoConfig.Section, Nerdle.AutoConfig"/>
</configSections>

<serviceCollection>
  <services>
    <service port="6996" reportType="File" />
    <service port="7001" reportType="Other" />
  </services>
</serviceCollection>

5) Mapa con AutoConfig

var services = AutoConfig.Map<IServiceCollectionConfiguration>();
Miedo de planetas
fuente
55
Gracias a Dios por esta respuesta
Svend
Para las personas que solo quieren hacerlo y no necesariamente crean todo desde cero, esta es la verdadera respuesta :)
CodeThief
5

Intente heredar de ConfigurationSection . Esta publicación de blog de Phil Haack tiene un ejemplo.

Confirmado, según la documentación de IConfigurationSectionHandler :

En .NET Framework versión 2.0 y superior, en su lugar, debe derivar de la clase ConfigurationSection para implementar el controlador de la sección de configuración relacionada.

Jeff Ogata
fuente