úterý 9. srpna 2016

Načtení objektu z konfiguračního souboru - 2

Nedávno jsem zde popsal způsob, jak snadno  načíst hodnoty z konfiguračního souboru do objektu v případě, že se použije appSettings a název klíče odpovídá názvu vlastnosti třídy. Někdy ale existuje požadavek vyjádřit konfiguraci více klasicky, tedy pomocí xml. I když :NET framework poskytuje způsob, jak xml z konfiguračního souboru převést na objekt, je tato možnost dle mého názoru krkolomná a lze to i jednodušeji.

Pokud máme v konfiguračním souboru tento zápis:
  <idnes>
    <Uri>http://idnes.cz</Uri>
    <Name>idnes</Name>
    <RetryAttempts>5</RetryAttempts>
    <ConnectionPoint>
      <Limit>10</Limit>
      <UseAlgorithm>true</UseAlgorithm>
    </ConnectionPoint>
  </idnes>
Je jednou z možností vytvořit si nového potomka třídy ConfigurationSection, který tento zápis převede na "flat" podobu - tedy tak, aby bylo možné zpracování stejjným způsobme, jaký je popsán v předchozím článku. Takový potomek je překvapivě jednoduchý:
public class GetNameValueSection : ConfigurationSection
{
    private Func<string, string> getValue = null;
 
    protected override void DeserializeSection(XmlReader reader)
    {
        var nameValueDictionary = new Dictionary<string, string>();
        List<string> path = new List<string>();
 
        while (reader.Read())
        {
            switch (reader.NodeType)
            {
                case XmlNodeType.Element:
                    path.Add(reader.Name);
                    break;
 
                case XmlNodeType.EndElement:
                    if (path.Count > 0)
                    {
                        path.RemoveAt(path.Count - 1);
                    }
                    break;
 
                case XmlNodeType.Text:
                    var key = string.Join(".", path.ToArray());
                    var value = reader.Value;
                    
                    nameValueDictionary.Add(key, value);
                    break;
            }
        }
 
        getValue = (name) => nameValueDictionary[name]; 
    }
 
    protected override object GetRuntimeObject()
    {
        return getValue;
    }
}
Samozřejmě je nutné do konfiguračního souboru přidat sekci configurationSection a správně nastavit typ, který má být použit pro zpracování nově přidané sekce. V případě, že je třída GetNameValueSection umístněna v namespace ConfigurationManagerHelper v assembly ConfigurationManagerHelper bude použit tento zápis:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="idnes" type="ConfigurationManagerHelper.GetNameValueSection, ConfigurationManagerHelper" />
  </configSections>
A celé to lze použít takto snadno:
getValue = ConfigurationManager.GetSection("idnes") as Func<string, string>;
var idnesConfiguration = FlatConfigurationManager.CreateAndSetInstance<EndPoint>("idnes", getValue);
Jistě by šlo použít xmlReader a provést prostou xml deserializaci, ale oproti xml deserializaci nemusí být cílová třída veřejná - tedy třída EndPoint použitá v příkladu může být privátní.

Navíc, pokud raději napřed vytváříme konfigurační prezentaci a teprve pak třidu, můžeme postupovat takto:
1. Nejrpve do konfiguračního elementu zapíšeme data, například:
    <library>
        <books>10</books>
        <magazines>34</magazines>
    </library>
2. Poté tento text označíme a zkopírujeme do schárnky.
3. Přejdeme do kódu a z nabídky Viual Studio Edit vybereme Paste special a následně možnost  Paste XML as classes. Vygeneruje se tento kód:
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class library
{
 
    private byte booksField;
 
    private byte magazinesField;
 
    /// <remarks/>
    public byte books
    {
        get
        {
            return this.booksField;
        }
        set
        {
            this.booksField = value;
        }
    }
 
    /// <remarks/>
    public byte magazines
    {
        get
        {
            return this.magazinesField;
        }
        set
        {
            this.magazinesField = value;
        }
    }
}
4. Z vygenerovaného kódu lze odstranit atributy a třídy změnit z public na private, tedy:
class library
{
    private byte booksField;
 
    private byte magazinesField;
 
    public byte books
    {
        get
        {
            return this.booksField;
        }
        set
        {
            this.booksField = value;
        }
    }
 
    public byte magazines
    {
        get
        {
            return this.magazinesField;
        }
        set
        {
            this.magazinesField = value;
        }
    }
}
5. Do configSection v konfiguráku je nutné přidat správnou definici:
      <section name="library" type="ConfigurationManagerHelper.GetNameValueSection, ConfigurationManagerHelper" />
6. A nyní je už možné získat v kódu objekt:
getValue = ConfigurationManager.GetSection("library") as Func<string, string>;
var library = FlatConfigurationManager.CreateAndSetInstance<library>("library", getValue);

2 komentáře:

  1. Pokud bys netrval na C#, lze v F# velmi jednoduše použít tzv. Type provider - http://fsharp.github.io/FSharp.Data/library/XmlProvider.html :)

    OdpovědětVymazat
    Odpovědi
    1. Je fakt, že z toho zápisu
      var nameValueDictionary = new Dictionary();

      a později
      getValue = (name) => nameValueDictionary[name];

      je cítit funkcionální přístup, vyzkoušet si F# by se mohlo nabízet. Jinak dík za článek

      Vymazat