179 votes

Comment mettre en œuvre une ConfigurationSection avec une ConfigurationElementCollection ?

J'essaie de mettre en œuvre une section de configuration personnalisée dans un projet et je me heurte sans cesse à des exceptions que je ne comprends pas. J'espère que quelqu'un pourra m'éclairer sur ce point.

Tengo App.config qui ressemble à ça :

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

J'ai un ServiceConfig défini comme suit :

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; }
    }
  }

Et j'ai un ServiceCollection défini comme suit :

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 partie qui me manque est ce qu'il faut faire pour le gestionnaire. A l'origine, j'ai essayé d'implémenter un IConfigurationSectionHandler mais j'ai trouvé deux choses :

  1. ça n'a pas marché
  2. il est déprécié.

Je suis complètement perdu maintenant sur ce que je dois faire pour pouvoir lire mes données à partir de la configuration. N'hésitez pas à m'aider !

0 votes

Je n'arrive pas à le faire fonctionner. J'aimerais voir RT.Core.Config.ServicesSection. Je n'obtiens que l'élément 'AddService' non reconnu, même si j'utilise également le code de la réponse acceptée.

0 votes

J'ai manqué ça aussi au début - cette partie : [ConfigurationCollection(typeof(ServiceCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")] Le AddItemName doit correspondre, donc si vous changez "add" en "addService", cela fonctionnera.

195voto

Russell McClure Points 2704

La réponse précédente est correcte mais je vous donne également tout le code.

Votre app.config doit ressembler à ceci :

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

Votre ServiceConfig y ServiceCollection restent inchangées.

Vous avez besoin d'une nouvelle classe :

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"];
      }
   }
}

Et ça devrait faire l'affaire. Pour le consommer, vous pouvez utiliser :

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

ServiceConfig serviceConfig = serviceConfigSection.Services[0];

11 votes

El [Add|Remove|Clear]ItemName les propriétés de la ConfigurationCollection ne sont pas vraiment nécessaires dans ce cas, car "add"/"clear"/"remove" sont déjà les noms par défaut des éléments XML.

2 votes

Comment puis-je faire en sorte que les balises ne soient pas ajoutées ? Cela ne semble fonctionner que si elles sont des ajouts. Cela ne fonctionnerait pas si c'était <Service Port="6996" ReportType="File" /> ou <Service Port="7001" ReportType="Other" />.

7 votes

@JonathanWolfson : changez simplement AddItemName = "add" en AddItemName = "Service".

92voto

Mubashar Ahmad Points 2788

Si vous cherchez une section de configuration personnalisée comme la suivante

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

alors vous pouvez utiliser mon implémentation de la section configuration donc pour commencer ajoutez System.Configuration référence d'assemblage à votre projet

Regardez les éléments imbriqués que j'ai utilisés, le premier est Credentials avec deux attributs, ajoutons-le en premier.

Élément d'accréditation

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;
            }
        }
    }

PrimaryAgent et SecondaryAgent

Les deux ont les mêmes attributs et semblent être une adresse pour un ensemble de serveurs pour un primaire et un basculement, donc vous avez juste besoin de créer une classe d'élément pour les deux comme suit

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?;
            }
        }
    }

J'expliquerai comment utiliser deux éléments différents avec une seule classe plus tard dans ce post, sautons le SiteId car il n'y a aucune différence. Il suffit de créer une classe comme ci-dessus avec une seule propriété. Voyons comment implémenter la collection Lanes.

il est divisé en deux parties : vous devez d'abord créer une classe d'implémentation d'élément, puis vous devez créer une classe d'élément de collection.

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
    }

vous pouvez remarquer qu'un attribut de LanElement est une énumération et si vous essayez d'utiliser une autre valeur dans la configuration qui n'est pas définie dans l'énumération, l'application lancera un message d'erreur. System.Configuration.ConfigurationErrorsException au démarrage. Ok, passons à la définition de la collection

[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);
        }

    }

vous pouvez remarquer que j'ai mis le AddItemName = "Lane" vous pouvez choisir ce que vous voulez pour l'élément d'entrée de votre collection, je préfère utiliser "add", l'élément par défaut, mais je l'ai changé juste pour les besoins de cet article.

Maintenant que tous nos éléments imbriqués ont été implémentés, nous devons les regrouper dans une classe qui doit implémenter 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; }
        }
    }

Maintenant vous pouvez voir que nous avons deux propriétés avec le nom PrimaryAgent y SecondaryAgent Les deux ont le même type, maintenant vous pouvez facilement comprendre pourquoi nous n'avions qu'une seule classe d'implémentation pour ces deux éléments.

Avant de pouvoir utiliser cette section de configuration nouvellement inventée dans votre app.config (ou web.config), vous devez simplement indiquer à votre application que vous avez inventé votre propre section de configuration et lui accorder un certain respect. Pour ce faire, vous devez ajouter les lignes suivantes dans app.config (peut-être juste après le début de la balise Root).

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

NOTE : MyAssemblyName doit être sans .dll. Par exemple, si le nom de votre fichier d'assemblage est myDll.dll, utilisez myDll au lieu de myDll.dll.

pour récupérer cette configuration, utilisez la ligne de code suivante n'importe où dans votre application

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

J'espère que cet article vous aidera à démarrer avec des sections de configuration personnalisées un peu compliquées.

Bon codage :)

****Edit**** Pour activer LINQ on LaneConfigCollection vous devez mettre en œuvre IEnumerable<LaneConfigElement>

Et ajouter l'implémentation suivante 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;
            }
        }

pour les personnes qui ne savent toujours pas comment le rendement fonctionne vraiment, lisez ce bel article

Voici deux points essentiels tirés de l'article ci-dessus

il ne termine pas vraiment l'exécution de la méthode. yield return met en pause l'exécution de la l'exécution de la méthode et la prochaine fois que vous l'appelez (pour la prochaine valeur d'énumération), la méthode continuera à s'exécuter à partir du dernier appel de yield return. C'est un peu confus je pense (Shay Friedman)

Yield n'est pas une fonctionnalité du runtime .Net. Il s'agit simplement d'une fonctionnalité du langage C# qui est compilé en code IL simple par le compilateur C#. (Lars Corneliussen)

4 votes

Merci d'avoir fourni un exemple complet, cela m'aide beaucoup !

46voto

Mzf Points 2601

Il s'agit du code générique pour la collecte de la configuration :

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();
    }
}

Après avoir GenericConfigurationElementCollection , vous pouvez simplement l'utiliser dans la section de configuration (ceci est un exemple de mon 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"]; }
    }
}

L'élément de configuration est config ici :

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

Le fichier de configuration ressemblerait à ceci :

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

J'espère que cela vous aidera !

0 votes

Cool ! Je pensais à la même chose et j'ai découvert que je n'étais pas le seul. J'aimerais que MS implémente cela pour toutes les configurations FCL.

0 votes

Une suggestion sur la façon de faire cela avec un BasicMap pour les éléments ? Je ne veux pas implémenter Add si je peux l'éviter.

33voto

fearofawhackplanet Points 12147

Une alternative plus facile pour ceux qui préfèrent ne pas écrire manuellement tous les éléments de configuration...

1) Installer Nerdle.AutoConfig de NuGet

2) Définissez votre type de ServiceConfig (soit une classe concrète, soit une interface, l'une ou l'autre fera l'affaire).

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

3) Vous aurez besoin d'un type pour contenir la collection, par ex.

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

4) Ajoutez la section de configuration comme suit (notez le nom en 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) Carte avec AutoConfig

var services = AutoConfig.Map<IServiceCollectionConfiguration>();

5voto

adrift Points 24386

Essayez d'hériter de ConfigurationSection . Ce site article de blog de Phil Haack en donne un exemple.

Confirmé, selon la documentation pour IConfigurationSectionHandler :

Dans .NET Framework version 2.0 et supérieure, vous devez plutôt dériver de la classe ConfigurationSection pour mettre en œuvre le gestionnaire de section de configuration correspondant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X