Comment implémenter une ConfigurationSection avec une ConfigurationElementCollection

166

J'essaie d'implémenter une section de configuration personnalisée dans un projet et je continue à me heurter à des exceptions que je ne comprends pas. J'espère que quelqu'un pourra remplir les blancs ici.

J'ai App.configqui ressemble à ceci:

<?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élément défini comme ceci:

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 ServiceCollectiondéfini comme ceci:

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. À l'origine, j'ai essayé d'implémenter un IConfigurationSectionHandlermais j'ai trouvé deux choses:

  1. ça n'a pas marché
  2. c'est obsolète.

Je ne sais plus trop quoi faire pour pouvoir lire mes données depuis la configuration. Toute aide s'il vous plaît!

Chris Holmes
la source
Je ne peux pas faire fonctionner ça. J'aimerais voir RT.Core.Config.ServicesSection. J'obtiens juste l'élément non reconnu 'AddService' malgré l'utilisation du code de la réponse acceptée également.
sirdank
J'ai manqué cela au début aussi - cette partie: [ConfigurationCollection (typeof (ServiceCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")] Le AddItemName doit correspondre donc si vous avez changé "ajouter" à "addService" ça marcherait
HeatherD

Réponses:

188

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

Votre app.config devrait 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>

Vos classes ServiceConfiget vos ServiceCollectionclasses 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 cela devrait faire l'affaire. Pour le consommer, vous pouvez utiliser:

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

ServiceConfig serviceConfig = serviceConfigSection.Services[0];
Russell McClure
la source
10
Les [Add|Remove|Clear]ItemNamepropriétés de l' ConfigurationCollectionattribut ne sont pas vraiment nécessaires dans ce cas, car "ajouter" / "effacer" / "supprimer" sont déjà les noms par défaut des éléments XML.
Wim Coenen
2
Comment puis-je le faire fonctionner pour que les balises ne soient pas ajoutées? Cela ne semble fonctionner que s'ils sont ajoutés. Cela ne fonctionnerait pas si c'était <Service Port = "6996" ReportType = "File" /> ou <Service Port = "7001" ReportType = "Other" />
JonathanWolfson
7
@JonathanWolfson: remplacez simplement AddItemName = "add" par AddItemName = "Service"
Mubashar
Est-ce toujours l'approche pour .NET 4.5?
écraser
6
@crush: oui, pas beaucoup de changements dans ce coin poussiéreux de .NET.
Russell McClure
84

Si vous recherchez une section de configuration personnalisée comme suit

<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 de configuration afin de commencer, ajoutez une System.Configurationréférence d'assemblage à votre projet

Regardez les éléments imbriqués que j'ai utilisés, le premier est les informations d'identification avec deux attributs, alors ajoutons-le d'abord

Élément Credentials

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 ressemblent à une adresse à un ensemble de serveurs pour un serveur principal et un basculement, il vous suffit donc 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 classe plus tard dans cet article, laissez-nous sauter le SiteId car il n'y a aucune différence. Il vous suffit de créer une classe identique à celle 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 LanElementest une énumération et si vous essayez d'utiliser une autre valeur dans la configuration qui n'est pas définie dans l'application d'énumération lèvera un System.Configuration.ConfigurationErrorsExceptionau démarrage. Ok passons à la définition de 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 défini le AddItemName = "Lane"vous pouvez choisir ce que vous voulez pour votre élément d'entrée de collection, je préfère utiliser "ajouter" celui par défaut mais je l'ai changé juste pour le bien de ce message.

Maintenant, tous nos éléments imbriqués ont été implémentés maintenant, nous devons regrouper tous ceux 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; }
        }
    }

Vous pouvez maintenant voir que nous avons deux propriétés avec un nom PrimaryAgentet que les SecondaryAgentdeux ont le même type. Vous pouvez maintenant facilement comprendre pourquoi nous n'avions qu'une seule classe d'implémentation par rapport à ces deux éléments.

Avant de pouvoir utiliser cette section de configuration nouvellement inventée dans votre app.config (ou web.config), il vous suffit de dire à votre application que vous avez inventé votre propre section de configuration et de lui donner du respect, pour ce faire, vous devez ajouter les lignes suivantes dans app.config (peut-être juste après le début de la balise racine).

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

REMARQUE: MyAssemblyName doit être sans .dll, par exemple, si votre nom de 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 l'article ci-dessus vous aidera à démarrer avec des sections de configuration personnalisées un peu compliquées.

Bon codage :)

**** Edit **** Pour activer LINQ sur LaneConfigCollectionvous devez implémenterIEnumerable<LaneConfigElement>

Et ajouter la mise en œuvre 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 fonctionne vraiment le rendement, lisez ce bel article

Deux points clés tirés de l'article ci-dessus sont

cela ne met pas vraiment fin à l'exécution de la méthode. yield return interrompt l'exécution de la méthode et la prochaine fois que vous l'appelez (pour la valeur d'énumération suivante), la méthode continuera à s'exécuter à partir du dernier appel de yield return. Cela semble un peu déroutant je pense ... (Shay Friedman)

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

Mubashar
la source
3
Merci de fournir un exemple complet, cela aide vraiment beaucoup!
John Leidegren
46

Ceci est un code générique pour la collecte de 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();
    }
}

Une fois que vous l'avez GenericConfigurationElementCollection, vous pouvez simplement l'utiliser dans la section config (c'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 configuré 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!

Mzf
la source
Cool! Je pensais à la même chose et j'ai constaté que je ne suis pas seul. Wish MS implémente cela pour toutes les
configurations
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.
SpaceCowboy74
28

Une alternative plus simple pour ceux qui préféreraient ne pas écrire manuellement toute cette configuration passe-partout ...

1) Installez Nerdle.AutoConfig depuis NuGet

2) Définissez votre type ServiceConfig (soit une classe concrète ou juste une interface, l'un 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 exemple

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

4) Ajoutez la section de configuration comme ceci (notez la dénomination 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>();
peur de la planète
la source
5
Merci à Dieu pour cette réponse
Svend
Pour les personnes qui veulent juste le faire et ne pas nécessairement tout créer à partir de zéro, c'est la vraie réponse :)
CodeThief
5

Essayez d'hériter de ConfigurationSection . Ce billet de blog de Phil Haack a 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 implémenter le gestionnaire de section de configuration associé.

Jeff Ogata
la source