Come implementare una ConfigurationSection con ConfigurationElementCollection


166

Sto cercando di implementare una sezione di configurazione personalizzata in un progetto e continuo a correre contro eccezioni che non capisco. Spero che qualcuno possa riempire gli spazi vuoti qui.

Ho App.configche assomiglia a questo:

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

Ho un ServiceConfigelemento definito in questo modo:

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

E ho ServiceCollectiondefinito così:

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 che mi manca è cosa fare per il gestore. Inizialmente, ho cercato di implementare un IConfigurationSectionHandlerma ho trovato due cose:

  1. non ha funzionato
  2. è deprecato.

Sono completamente perso ora su cosa fare in modo da poter leggere i miei dati da Config. Qualsiasi aiuto per favore!


Non riesco a farlo funzionare. Mi piacerebbe vedere RT.Core.Config.ServicesSection. Ottengo solo l'elemento Non riconosciuto 'AddService' nonostante usi anche il codice dalla risposta accettata.
sirdank,

All'inizio ho perso anche questo - questa parte: [ConfigurationCollection (typeof (ServiceCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")] AddItemName deve corrispondere, quindi se hai cambiato "aggiungi" in "addService" funzionerebbe
HeatherD

Risposte:


188

La risposta precedente è corretta ma ti darò anche tutto il codice.

La tua app.config dovrebbe apparire così:

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

Le tue ServiceConfige le ServiceCollectionclassi rimangono invariate.

Hai bisogno di una nuova 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"];
      }
   }
}

E questo dovrebbe fare il trucco. Per consumarlo puoi usare:

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

ServiceConfig serviceConfig = serviceConfigSection.Services[0];

10
Le [Add|Remove|Clear]ItemNameproprietà sull'attributo ConfigurationCollectionnon sono realmente necessarie in questo caso, perché "aggiungi" / "cancella" / "rimuovi" sono già i nomi predefiniti degli elementi XML.
Wim Coenen,

2
Come posso farlo funzionare in modo che i tag non vengano aggiunti? Sembra funzionare solo se vengono aggiunti. Non funzionerebbe se fosse <Porta servizio = "6996" ReportType = "File" /> o <Porta servizio = "7001" ReportType = "Altro" />
JonathanWolfson

7
@JonathanWolfson: basta cambiare AddItemName = "add" in AddItemName = "Service"
Mubashar

È ancora questo l'approccio per .NET 4.5?
schiaccia il

6
@crush: sì, non molti cambiamenti in questo angolo polveroso di .NET.
Russell McClure,

84

Se stai cercando una sezione di configurazione personalizzata come la seguente

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

quindi è possibile utilizzare la mia implementazione della sezione di configurazione in modo da iniziare aggiungere il System.Configurationriferimento dell'assembly al progetto

Guarda tutti gli elementi nidificati che ho usato, il primo è Credenziali con due attributi, quindi aggiungiamolo prima

Elemento credenziali

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

Entrambi hanno gli stessi attributi e sembrano un indirizzo a un set di server per un primario e un failover, quindi devi solo creare una classe di elementi per entrambi come quelli seguenti

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

Spiegherò come utilizzare due diversi elementi con una classe più avanti in questo post, saltiamo il SiteId in quanto non vi è alcuna differenza. Devi solo creare una classe come sopra con una sola proprietà. vediamo come implementare la raccolta Lanes

è diviso in due parti prima devi creare una classe di implementazione dell'elemento, quindi devi creare una classe di elementi della raccolta

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
    }

si può notare che un attributo di LanElementè un'enumerazione e se si tenta di utilizzare qualsiasi altro valore nella configurazione che non è definito nell'applicazione di enumerazione, verrà lanciato System.Configuration.ConfigurationErrorsExceptionall'avvio. Ok consente di passare a Definizione raccolta

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

    }

puoi notare che ho impostato il AddItemName = "Lane"che puoi scegliere quello che ti piace per la voce della tua raccolta, preferisco usare "aggiungi" quello predefinito ma l'ho modificato solo per il bene di questo post.

Ora tutti i nostri elementi nidificati sono stati implementati ora dovremmo aggregare tutti quelli in una classe che deve implementare 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; }
        }
    }

Ora puoi vedere che abbiamo due proprietà con nome PrimaryAgented SecondaryAgententrambi hanno lo stesso tipo ora puoi capire facilmente perché abbiamo avuto solo una classe di implementazione contro questi due elementi.

Prima di poter utilizzare questa sezione di configurazione appena inventata nel tuo app.config (o web.config) devi solo dire all'applicazione che hai inventato la tua sezione di configurazione e dargli un po 'di rispetto, per farlo devi aggiungere le seguenti righe in app.config (potrebbe essere subito dopo l'inizio del tag root).

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

NOTA: MyAssemblyName dovrebbe essere senza .dll, ad esempio se il nome del file dell'assembly è myDll.dll, utilizzare myDll anziché myDll.dll

per recuperare questa configurazione utilizzare la seguente riga di codice in qualsiasi punto dell'applicazione

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

Spero che il post sopra ti possa aiutare a iniziare con un tipo un po 'complicato di sezioni di configurazione personalizzate.

Happy Coding :)

**** Modifica **** Per abilitare LINQ LaneConfigCollectiondevi implementarloIEnumerable<LaneConfigElement>

E aggiungi la seguente implementazione di GetEnumerator

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

per le persone che sono ancora confuse su come funziona davvero la resa leggi questo bell'articolo

Due punti chiave presi dall'articolo precedente sono

in realtà non termina l'esecuzione del metodo. yield return mette in pausa l'esecuzione del metodo e alla successiva chiamata (per il successivo valore di enumerazione), il metodo continuerà a essere eseguito dall'ultima chiamata di return di rendimento. Sembra un po 'confuso, penso ... (Shay Friedman)

La resa non è una funzionalità del runtime .Net. È solo una funzione del linguaggio C # che viene compilata in un semplice codice IL dal compilatore C #. (Lars Corneliussen)


3
Grazie per aver fornito un esempio completo, questo aiuta davvero molto!
John Leidegren,

46

Questo è un codice generico per la raccolta delle configurazioni:

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

Dopo averlo GenericConfigurationElementCollection, puoi semplicemente usarlo nella sezione di configurazione (questo è un esempio dal mio 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'elemento di configurazione è di configurazione qui:

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

Il file di configurazione sarebbe simile al seguente:

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

Spero che sia d'aiuto!


Freddo! Stavo pensando allo stesso e ho scoperto che non sono solo. Vorrei che MS lo implementasse per tutte le
configurazioni

Qualche suggerimento su come farlo con una BasicMap per gli articoli? Non voglio implementare Aggiungi se posso evitarlo.
SpaceCowboy74

28

Un'alternativa più semplice per coloro che preferiscono non scrivere manualmente tutta quella configurazione del boilerplate ...

1) Installa Nerdle.AutoConfig da NuGet

2) Definisci il tuo tipo di ServiceConfig (o una classe concreta o solo un'interfaccia, o lo farà)

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

3) Avrai bisogno di un tipo per contenere la raccolta, ad es

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

4) Aggiungi la sezione di configurazione in questo modo (nota la denominazione di 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) Mappa con AutoConfig

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

5
Grazie a Dio per questa risposta
Svend

Per le persone che vogliono solo farlo e non necessariamente creare tutto da zero, questa è la vera risposta :)
CodeThief

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.