22 July 2013 Config, ConfigSection, HowTo Robert Muehsig

Ein (Ur)-altes Thema, aber trotzdem braucht man es immer mal wieder:

Wer eine Applikation mit komplexeren Konfigurationsmöglichkeiten baut stößt mit dem Key/Value Standard-System schnell an die Grenze. Vor 4 Jahren hatte ich bereits über das Thema gebloggt.

In dem Blogpost erweitere ich das Beispiel noch etwas und zeige wie man in einer Collection eine andere Collection hinterlegen kann. So kann man seine Konfiguration beliebig tief verschachteln.

Demo Config:

  <codeInsideConfig webUrl="http://code-inside.de" startedOn="2007">
    <authors>
      <author name="Robert Mühsig">
        <topics>
          <add name="Foobar 1" />
          <add name="Foobar 2" />
          <add name="Foobar 3" />
          <add name="Buzz 1" />
          <add name="Buzz 2" />
          <add name="Buzz 3" />
        </topics>
      </author>
      <author name="Oliver Guhr">
        <topics>
          <add name="Foobar 1" />
          <add name="Foobar 2" />
          <add name="Foobar 3" />
          <add name="Buzz 1" />
          <add name="Buzz 2" />
          <add name="Buzz 3" />
        </topics>
      </author>
    </authors>
  </codeInsideConfig>

Nested ConfigurationElementCollection:

Rein von der Struktur her haben wir “codeInsideConfig” - “authors” - “author” - “topics” - “topic”. In Code gegossen ergibt dies sowas:

    public class CodeInsideConfig : ConfigurationSection
    {
        [ConfigurationProperty("webUrl", DefaultValue = "http://DEFAULT.de", IsRequired = true)]
        public string WebUrl
        {
            get
            {
                return this["webUrl"] as string;
            }
        }

        [ConfigurationProperty("startedOn", IsRequired = false)]
        public int StartedOn
        {
            get
            {
                return (int)this["startedOn"];
            }
        }

        [ConfigurationProperty("authors")]
        public CodeInsideConfigAuthorCollection Authors
        {
            get
            {
                return this["authors"] as CodeInsideConfigAuthorCollection;
            }
        }

        public static CodeInsideConfig GetConfig()
        {
            return ConfigurationSettings.GetConfig("codeInsideConfig") as CodeInsideConfig;
        }
    }

    public class CodeInsideConfigAuthorCollection : ConfigurationElementCollection
    {
        // define "author" as child-element-name (and not add...)
        public CodeInsideConfigAuthorCollection()
        {
            AddElementName = "author";
        }

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

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

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((CodeInsideConfigAuthor)element).Name;
        }
    }

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

        [ConfigurationProperty("topics")]
        public CodeInsideConfigTopicCollection Topics
        {
            get
            {
                return this["topics"] as CodeInsideConfigTopicCollection;
            }
        }

    }

    public class CodeInsideConfigTopicCollection : ConfigurationElementCollection
    {
        public CodeInsideConfigTopic this[int index]
        {
            get
            {
                return base.BaseGet(index) as CodeInsideConfigTopic;
            }
            set
            {
                if (base.BaseGet(index) != null)
                {
                    base.BaseRemoveAt(index);
                }
                this.BaseAdd(index, value);
            }
        }

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

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((CodeInsideConfigTopic)element).Name;
        }
    }

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

    } 

Wichtigster Punkt: Wir müssen dem Configuration-Framework mitteilen was eigentlich der Tag “author” bedeutet. Im Standardfall geht das Framework bei einer Collection von dem Tag “add” aus. Im Konstruktor der Collection können wir aber das AddElementName setzen – dies ist im Grunde schon der gesamte Trick.

LINQ & ConfigurationElements

Auf dem ersten Blick verträgt sich LINQ und die Config-API nicht wirklich:

image

Gibt man aber immer explizit die Typen mit an, kann man auch komplexere LINQ-Abfragen wie gewohnt machen:

 static void Main(string[] args)
        {
            // LINQ & config demo
            var authors = from CodeInsideConfigAuthor author in CodeInsideConfig.GetConfig().Authors
                          select author;

            foreach (var codeInsideConfigAuthor in authors)
            {
                Console.WriteLine(codeInsideConfigAuthor.Name);
                Console.WriteLine("Foobar-Topics:");

                var topicsWithFoobar = from CodeInsideConfigTopic topic in codeInsideConfigAuthor.Topics
                                    where topic.Name.Contains("Foobar")
                                    select topic;


                foreach (CodeInsideConfigTopic topics in topicsWithFoobar)
                {
                    Console.WriteLine(topics.Name);
                }
            }

            Console.ReadLine();
        }

Ergebnis:

image

Quellen waren der LINQ Stackoverflow Thread und über diesen Blogpost bin ich auf die Zauberzutat “AddElementName” gekommen. Natürlich noch mein eigener Blogpost.

Demo-Code auf GitHub


Written by Robert Muehsig

Software Developer - from Dresden, Germany, now living & working in Switzerland. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de

If you like the content and want to support me you could buy me a beer or a coffee via Litecoin or Bitcoin - thanks for reading!