web-dev-qa-db-fra.com

Comment implémenter une ConfigurationSection avec un ConfigurationElementCollection

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.config qui 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 élément 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, c'est ce qu'il faut faire pour le gestionnaire. À l’origine, j’ai essayé de mettre en œuvre une IConfigurationSectionHandler mais j’ai trouvé deux choses:

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

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

156
Chris Holmes

La réponse précédente est correcte, mais je vais également vous donner 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 ServiceConfig et 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 cela devrait faire l'affaire. Pour le consommer, vous pouvez utiliser:

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

ServiceConfig serviceConfig = serviceConfigSection.Services[0];
181
Russell McClure

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 System.Configuration Référence d'assemblage à votre projet

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

Elément d'identification

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 vers 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 éléments suivants:

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 ce post. Laissons de côté 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 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'application Énumération, un System.Configuration.ConfigurationErrorsException sera lancé au démarrage. Ok permet de passer à 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 réglé 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 modifié uniquement pour les besoins de cet article.

Maintenant que tous nos éléments imbriqués ont été implémentés, nous devrions maintenant regrouper tous ceux-ci dans une classe devant 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 portant les noms PrimaryAgent et SecondaryAgent ayant le même type. Vous pouvez donc 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), il vous suffit de dire à votre application que vous avez inventé votre propre section de configuration et de la respecter, 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 le message ci-dessus vous aiderait à démarrer avec un type de configuration personnalisée un peu compliqué.

Bonne codage :)

**** Edit **** Pour activer LINQ sur LaneConfigCollection vous devez implémenter IEnumerable<LaneConfigElement>

Et ajouter après la mise en oeuvre 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 réellement le rendement, lisez cet article de Nice

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. return return met en pause 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 return return. Cela semble un peu déroutant, je pense… (Shay Friedman)

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

78
Mubashar

C'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 avez GenericConfigurationElementCollection, vous pouvez l’utiliser simplement dans la section config (il s’agit d’un exemple de mon répartiteur):

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>

espérons que ça aide!

46
Mzf

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

1) Installez Nerdle.AutoConfig à partir de NuGet

2) Définissez votre type ServiceConfig (soit une classe concrète, soit simplement une interface, cela suffira)

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 suit (notez le nom 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) Carte avec AutoConfig

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

Essayez d’hériter de ConfigurationSection . This blog post par Phil Haack a un exemple.

Confirmé, conformément à la documentation de IConfigurationSectionHandler :

Dans .NET Framework version 2.0 et ultérieure, vous devez plutôt dériver de la classe ConfigurationSection pour implémenter le gestionnaire de section de configuration associé.

4
Jeff Ogata