ú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);

3 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
  2. How to get to MGM Grand Casino Las Vegas by Bus or Train
    Directions to MGM Grand Casino Las Vegas (MGM Grand Casino) with public transportation. The following transit lines have 김제 출장마사지 routes that 안성 출장안마 pass 부천 출장마사지 near 순천 출장샵 MGM Grand 파주 출장마사지

    OdpovědětVymazat