[Ww.Text] CustomConfig2 - czyli jak zrobic aby sie nie narobic

Wczoraj przeczytaLem post Darka omawiający hierarchiczne pliki konfiguracyjne. Podczas czytania naszły mnie 3 mysli.

  1. Kropkowana notacja przypomina mi Javowe properties (czy to jescze tak sie nazywa?). Mialem z tym doczynnienia daawno temu jak jeszcze cos robilem przy Java. Srednio mi sie to posdobalo i do dzis uwazam ze reprezentacja hierarchii wartosci za pomoca notacji kropkowej a nie skorzystanie z gotowego, znanego, popularnego, sprawdzonego rozwiazania nie jest najszczesliwszym rozwiazaniem (do typowych zastosowan, jako ze na pewno znajda sie zastosowanie gdzie take java-ini-like rozwiazanie sprawdzi sie duzo lepiej niz XML).
  2. Po moich ostatnich "zawodowych" walkach z przetwarzaniem plikow tekstowych i problemami stron kodowych, unicode, kompatybilnosci wstecz i wprzod (na szczescie bez kompatybilnosci gora-dol czy prawo-lewo ;-) ) ustawiem regionalnych, jezyka systemu i non-win systemow - cisnienie mi skoczylo na mysl o sledzeniu problemow i ich rozwiazywaniu. Po pierwszym tekscie nie widac na razie jak tego typu problemy moga byc rozwiazane. Prawdopodobnie dlatego ze Darek nie wymaga podobnej funkcjonalnosci od tego rozwiazania i jemu to wystarcza to jest calkowicie w porzadku.
  3. Pozazdroscilem checi pisania, testowania, dokumentowania, wdrazania tego rozwiazania w zespole, jakoze jestem leniwa osoba. Z tego tez powodu powstal ten blog, poniewaz postanowilem zrobic podobna funkcjonalnosc korzystajac z wbudowanej funkcjonalnosci w .NET Frameworka.

Configuration w .NET Frameworku wspiera chyba od poczatku hierarchiczne pliki konfiguracyjne. Przeciez po instalacji mamy machine.config i "app.config". Ten drugi moze zmienic "odziedziczone" wlasnosci z machine.config lub dodac nowe a nawet usunac - oczywiscie wszystko "lokalnie" w ramach aplikacji. Wiec wykorzystajmy ta infrastrukture do "naszych" celow - czyli rozwiazania ktore posiada glowny plik konfiguracyjny oraz opcjonalne per user nadpisane ustawienia.

Nie stadardowy plik konfiguracyjny

Najpierw pokaze jak zaladowac konfiguracje z prawie dowolnego pliku. korzystajac z metody ConfigurationManager.OpenExeConfiguration(string exePath) mozemy zaladowac plik konfiguracyjny dla wskazanego pliku "exe" (a tak naprawde dowolnego pliku) gdzie plik konfiguracyjny to exePath z dodanym sufiksem ".config".



const string cfg = @"<?xml version='1.0' encoding='utf-8' ?>
    <configuration>
      <configSections>
        <section name='mySection' type='ZineNet.CustomCfg2.MySection, ZineNet.CustomCfg2'/>
      </configSections>
      <mySection>
        <myProperties>
          <add name='p1' value='v1' />
          <add name='p2' value='v2' />
          <add name='p4' value='v4' />
        </myProperties>
      </mySection>
    </configuration>"
;
File.WriteAllText(@"CfgDir\Demo1.tmp", "");
File.WriteAllText(@"CfgDir\Demo1.tmp.config", cfg, Encoding.UTF8);

var exeCfg = ConfigurationManager.OpenExeConfiguration(Path.GetFullPath(@"CfgDir\Demo1.tmp"));
var myCfg = (MySection)exeCfg.GetSection("mySection");

foreach (MyPropertyElement p in myCfg.MyProperties) {
    Console.WriteLine("{0}:{1}", p.Name, p.Value);
}

W tym przykladzie tworze plik na dysku, gdze zapisuje przykladowa konfiguracje. Nastepnie otwieram plik konfiguracyjny wskazujac na tymczasowy/sztuczny plik CfgDir\Demo1.tmp. Metoda OpenExeConfiguration sprawdza istnienie pliku przekazanego w argumencie exePath, pomimo iz z niech nie korzysta. Dlatego wytwarzam rowniez pusty plik o takiej nazwie.

Dowolne hierarchiczne pliki konfiguracyjne

Teraz przyklad blizszy temu co zaprezentowal Darek w swoim tekscie. Utworze 2 pliki konfiguracyjne, zaladuje oba uzyskujac ten sam typ sekcji konfiguracyjnej jak w przykladzie pierwszym.



const string cfg = @"<?xml version='1.0' encoding='utf-8' ?>
    <configuration>
      <configSections>
        <section name='mySection' type='ZineNet.CustomCfg2.MySection, ZineNet.CustomCfg2' allowExeDefinition='MachineToRoamingUser'/>
      </configSections>
      <mySection>
        <myProperties>
          <add name='p1' value='v1' />
          <add name='p2' value='v2' />
          <add name='p4' value='v4' />
        </myProperties>
      </mySection>
    </configuration>"
;
const string cfgLocal = @"<?xml version='1.0' encoding='utf-8' ?>
    <configuration>
      <mySection>
        <myProperties>
          <remove name='p2' />
          <add name='p5' value='v5' />
        </myProperties>
      </mySection>
    </configuration>"
;
File.WriteAllText(@"CfgDir\Demo2.config", cfg, Encoding.UTF8);
File.WriteAllText(@"CfgDir\Demo2.Local.config", cfgLocal, Encoding.UTF8);

var exeCfg = ConfigurationManager.OpenMappedExeConfiguration(
    new ExeConfigurationFileMap {
        ExeConfigFilename = Path.GetFullPath(@"CfgDir\Demo2.config"),
        RoamingUserConfigFilename = Path.GetFullPath(@"CfgDir\Demo2.Local.config")
    },
    ConfigurationUserLevel.PerUserRoaming
);
var myCfg = (MySection)exeCfg.GetSection("mySection");

foreach (MyPropertyElement p in myCfg.MyProperties) {
    Console.WriteLine("{0}:{1}", p.Name, p.Value);
}

Tym razem uzywam innej metody OpenMappedExeConfiguration gdzie przekazuje obkekt ExeConfigurationFileMap. Obiekt file map zawiera sciezki do konkretnych plikow konfiguracyjnych a nie zaleznych plikow "exe". Za pomoca drugiego argumentu ConfigurationUserLevel specyfikujemy ktore pliki musza byc zaladowane, a ktore zignorowane, jako ze w typie ExeConfigurationFileMap mozeny wyspecyfikowac 4 pliki konfiguracyjne roznego poziomu. Musimy zezwolic na ta operacje przy definicji sekcji w "pierwszym" pliku konfiguracyjnym za pomoca atrybutu "allowExeDefinition".

Jeszcze wiecej funkcjonalnosci we wlasnych plikach

Powyzszy przyklad dostarcza z grubsza funkcjonalnosci zaprezentowanej przez Darka. Lecz mozemy skorzystac bez zmiany kodu przetwarzajacego, z pozostalej funkcjonalnosci. Dla przykladu pokaze mozliwosc blokowania redefiniowania fragmentow konfiguracji.

const string cfg = @"<?xml version='1.0' encoding='utf-8' ?>
    <configuration>
      <configSections>
        <section name='mySection' type='ZineNet.CustomCfg2.MySection, ZineNet.CustomCfg2' allowExeDefinition='MachineToRoamingUser'/>
      </configSections>
      <mySection>
        <myProperties>
          <add name='p1' value='v1' />
          <add name='p2' value='v2' lockItem='true' />
          <add name='p4' value='v4' />
        </myProperties>
      </mySection>
    </configuration>"
;
const string cfgLocal = @"<?xml version='1.0' encoding='utf-8' ?>
    <configuration>
      <mySection>
        <myProperties>
          <remove name='p2' />
        </myProperties>
      </mySection>
    </configuration>"
;
File.WriteAllText(@"CfgDir\Demo3.config", cfg, Encoding.UTF8);
File.WriteAllText(@"CfgDir\Demo3.Local.config", cfgLocal, Encoding.UTF8);

var exeCfg = ConfigurationManager.OpenMappedExeConfiguration(
    new ExeConfigurationFileMap {
        ExeConfigFilename = Path.GetFullPath(@"CfgDir\Demo3.config"),
        RoamingUserConfigFilename = Path.GetFullPath(@"CfgDir\Demo3.Local.config")
    },
    ConfigurationUserLevel.PerUserRoaming
);
var myCfg = (MySection)exeCfg.GetSection("mySection");

foreach (MyPropertyElement p in myCfg.MyProperties) {
    Console.WriteLine("{0}:{1}", p.Name, p.Value);
}

Ustawiajac atrybut lockItem='true' na jednej z wlasnych wlasciwosci, nie pozwalamy na redefiniowanie go w "potomnym" pliku konfiguracyjnym. Podczas uruchamiania przykladu trzeciego dostaniemy wyjatek o podobnej tresci: "The attribute 'p2' has been locked in a higher level configuration. (K:\ZineNet\ZineNet.CustomCfg2\bin\Debug\CfgDir\Demo3.Local.config line 5)".

Inne uwagi

  1. Zamiast hierarchii zagniezdzania elementow, mozna ja "rozplaszczyc" i zastosowac notacje kropkowa podobnie jak to uczynil Darek. Poprostu trzeba nadpisac w definicji sekcji sposob deserializacji/serializacji
  2. Podejscie z zagniezdzaniem elementow XML zamiast notacji kropkowej wymusza na nas stworzenie odpowiednich klas konfiguracji. Zyskujemy na tym walidacje pliku konfiguracyjnegom lecz tracimy na tym ze mamy dodatkowa prace rzemieslnicza zwiazana z ich napisaniem.

 

Opublikowane 12 czerwca 08 10:56 przez Wojciech Gebczyk

Komentarze:

Brak komentarzy
Komentarze anonimowe wyłączone

About Wojciech Gebczyk

Code Sculptor.