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:
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:
Quellen waren der LINQ Stackoverflow Thread und über diesen Blogpost bin ich auf die Zauberzutat “AddElementName” gekommen. Natürlich noch mein eigener Blogpost.