Vytvorme si vlastnú sekciu v konfiguračnom súbore (II.) - Kolekcie

Doteraz bola práca s configuračnými sekciami a elementami veľmi jednoduchá. V prvom diely sme sa naučili vytvoriť handler pre konfiguračnú sekciu a zaregistrovať ho, vytvoriť konfiguračný element a zoskupovať jednotlivé sekcie. Všetko toto využijeme aj pri kolekciách.

Čaká nás však trochu viac programovania a zasuplovanie MSDN, v ktorej sú veci týkajúce kolekcií zdokumentované dosť lajdácky ;-). Začnime tou prvou z nich - máme k dispozícii štyry typy kolekcií, ktoré definuje čísleník ConfigurationElementCollectionType.

  • AddRemoveClearMap
  • AddRemoveClearMapAlternate
  • BasicMap
  • BasicMapAlternate

Vysvetlime si najprv rozdiely medzi typmi AddRemoveClearMap a BasicMap.

Do AddRemoveClearMap môžeme jednotlivé prvky pridávať, odobrať, alebo vymazať naraz všetky doteraz pridané záznamy, ktoré boli pridané v rodičovskom konfiguračnom súbore v rámci hierarchie konfiguračných súborov. Deje sa tak prostredníctom elementov <add/>,<remove/>,<clear/>. Existujúcim a dobre známym príkladom takejto konfiguračnej kolekcie je napr. konfiguračný element resp. jemu dozpovedajúca konfiguračná sekcia ConnectionStringSection a ConnectionStringSettingsCollection. Tento typ kolekcie je prednastevený v implementácii ConfigurationElementCollection.

BasicMap kolecia neumožňuje modifikovať záznamy z potomkových konfiguračných súboroch, ktoré boli pridané v rodičovských konfiguračných súboroch. Na druhej strane umožňuje oveľa čitateľnejší zápis, kde názov elementu prvku kolekcie je plne v našich rukách, nie sme odkázaný na <add/> elementy.

AddRemoveClearMapAlternate a BasicMapAlternate sa líšia od svojich základných typov len tým, že zachovávajú poradie prvkov tak, ako boli postupne pridávane v hierarchii konfiguračných súborov. Nedochádza ku ich radeniu podľa kĺúča.

Rodičovský konfiguračný súbor:

<contacts>
    <contact name="Bill Gates" email="gates@microsoft.com"/>
    <contact name="Steve Ballmer" email="ballmer@microsoft.com"/>
</contacts>

Detičkovský :-) konfiguračný súbor:

<contacts>
    <contact name="Mickey Mouse" email="mickey@disneyland.com" />
</contacts>

V prípade použitia BasicMap bude výsledné poradie prvkov v kolekcii zoradené nasledovne: 

  1. Bill Gates
  2. Mickey Mouse
  3. Steve Ballmer

Ak použijeme BasicMapAlternate kolekciu, výsledné poradie zostane nezmenené, Steve si vďaka svojim prudkým hereckým výkonom udrží pozíciu pred Mickey Mouseom :-)

  1. Bill Gates
  2. Steve Ballmer
  3. Mickey Mouse

Prvým cieľom bude vytvoriť wrapper pre nasledujúcu sekciu v našom konfiguračnom súbore.

<ASPNET.Web>
    <TestConfig pageSize="20">
        <contacts>
            <contact 
                name="Bill Gates"
                email="gates@microsoft.com"/>
            <contact 
                name="Steve Ballmer"
                email="ballmer@microsoft.com"/>
        <contacts>
    </TestConfig>
</ASPNET.Web>

Vytvoríme si configuračný element zodpovedajúci contact uzlu z konfiguračného súboru. V ničom zásadnom okrem toho, že musíme označiť jednu z properties za kľúč prostredníctom atribútu IsKey dekoračného atribútu ConfigurationProperty, sa náš element nelýši od toho, ktorý sme si vytvorili v predchádzajúcom diely.

using System;
using System.Configuration;

namespace ASPNET.Config
{
    public sealed class ContactConfigurationElement : 
        ConfigurationElement
    {        
        [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
        public string Name
        {
            get { return (string)base["name"]; }
            set { base["name"] = value; }
        }

        [ConfigurationProperty("email", IsRequired = true)]
        public string Email
        {
            get { return (string)base["email"]; }
            set { base["email"] = value; }
        }
    }
}

Následne vytvoríme kolekciu kontaktov zdedením z ConfigurationElementCollection. Ak si vystačíme s readonly prístupom a nepotrebujeme v runtime meniť konfiguráciu programovo, stačí nám preťažiť dve metódy(na vytvorenie inštancie prvku kolekcie a tú na vrátenie hodnoty kľúča) a preťažiť dve property(vracajúcu názov elementu a tú, ktorá určuje typ kolekcie).

using System;
using System.Configuration;

namespace ASPNET.Config
{
    public class ContactConfigurationElementCollection : 
        ConfigurationElementCollection
    {
        protected override string ElementName 
        {
            get { return "contact"; }
        }

        public override ConfigurationElementCollectionType CollectionType
        {
            get { return ConfigurationElementCollectionType.BasicMap; }
        }

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

        protected override object GetElementKey(ConfigurationElement 

element)
        {
            return ((ContactConfigurationElement)element).Name;
        }            
    }
}

Posledným krokom je vytvorenie wrappera pre samotnú konfiguračnú sekciu. To sme si tiež ukázali v predchádzajúcom diele. Pochiteľne, implementujeme len getter pri kolekcii - nechceme umožniť nahradiť inštanciu, len pračovať s tou, ktorú za nás vytvorí infraštruktúra na pozadí.

using System;
using System.Configuration;

namespace ASPNET.Config
{
    public sealed class TestConfigurationSection : 

ConfigurationSection
    {
        [ConfigurationProperty("contacts", 
            IsRequired = true, IsDefaultCollection=true]
        public ContactConfigurationElementCollection Contacts
        {
            get { return (ContactConfigurationElementCollection)this["contacts"]; }            
        }

        [ConfigurationProperty("pageSize", DefaultValue = 10)]
        public int PageSize
        {
            get { return (int)this["pageSize"]; }
            set { this["pageSize"] = value; }
        }
    }
}

Kód code behind testovacej stránky môže vyzerať nasledovne:

using System;
using System.Text;
using System.Configuration;

using ASPNET.Config;

public partial class _Default : System.Web.UI.Page 
{
    protected override void OnLoad(EventArgs e)
    {
        TestConfigurationSection testConfig = 
(TestConfigurationSection)
ConfigurationManager.GetSection("ASPNET.Web/TestConfig"); StringBuilder contactsStrBldr = new StringBuilder(); foreach (ContactConfigurationElement contact in testConfig.Contacts) contactsStrBldr.AppendFormat("<strong>Name: </strong>{0}" + "<strong>Email</strong>: {1}<br/>", contact.Name, contact.Email); LtrTest.Text = contactsStrBldr.ToString(); base.OnLoad(e); } }

V prípade, že chceme pracovať s collection prostredníctom <add/>,<remove/>,<clear/> tagov, zmodifikujeme len našu ContactConfigurationElementCollection tak, že zmažeme preťažené property ElementName a môžeme aj preťaženie property CollectionType, pretože je bázovou implementáciu nastavená na želanú hodnotu AddRemoveClearMap.

<ASPNET.Web>
    <TestConfig pageSize="20">
      <contacts>
        <add 
        name="Bill Gates" 
        email="gates@microsoft.com"/>        
        <add 
        name="Steve Ballmer"
        email="ballmer@microsoft.com"/>
      </contacts>        
    </TestConfig>
</ASPNET.Web>
public class ContactConfigurationElementCollection 
    : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new ContactConfigurationElement();
    }
    
    protected override object GetElementKey(ConfigurationElement 

element)
    {
        return ((ContactConfigurationElement)element).Name;
    }            
}

Čo v prípade, že nechceme v tomto prípade nadbytočný kontajnerový tag contacts v našom XML súbore a náš konfiguračný element bude obsahovať len našu collection kontaktov a žiaden iný potomkovský ConfigurationElement? Chceme teda dosiahnuť, aby konfiguračný súbor vyzeral nasledovne:

<ASPNET.Web>
    <TestConfig pageSize="20">      
      <contact name="Bill Gates" email="gates@microsoft.com"/>
      <contact name="Steve Ballmer" email="ballmer@microsoft.com"/>      
    </TestConfig>
</ASPNET.Web>

Stačí len zmodifikovať náš wrapper pre sekciu TestConfigurationSection. Stačí zmeniť mapovaný názov elementu na prázny reťazec.

[ConfigurationProperty("", IsRequired = true, IsDefaultCollection = true)]
public ContactConfigurationElementCollection Contacts
{
    get { return (ContactConfigurationElementCollection)this[""]; }           
}

Ak by sme chceli zmeniť názvy tagov <add/>,<remove/>,<clear/>, ktorými pridávame, mažeme prvky, alebo mažeme celú collection pri type kolekcie AddRemoveClearMap máme i takúto možnosť. Stačí keď odekorujujeme našu Contacts property v triede TestConfigurationSection atribútom ConfigurationCollection.

[ConfigurationCollection(typeof(ContactConfigurationElementCollection), 
    AddItemName="addContact", 
    RemoveItemName="RemoveContact",
        ClearItemsName="clearContact")]
[ConfigurationProperty("contacts", IsRequired = true)]
public ContactConfigurationElementCollection Contacts
{
    get { return (ContactConfigurationElementCollection)this["contacts"]; }            
}

Vyššie som spomenul, že pri snahe pracovať s kolekciou programovo budeme mať problém. Chýba nám napr. metóda na pridávanie do kolekcie a možno ešte niečo dalšie. Aby sme si uľahčili život, zoberme si Reflektor a poďme sa pozrieť, čo všetko implementuje Microsoft napr. v už spomínanej kolekcii ConnectionStringSettingsCollection.

[ConfigurationCollection(typeof(ConnectionStringSettings))]
public sealed class ConnectionStringSettingsCollection 
    : ConfigurationElementCollection
{
      // Methods
      static ConnectionStringSettingsCollection();
      public ConnectionStringSettingsCollection();
      public void Add(ConnectionStringSettings settings);
      protected override void BaseAdd(int 

index, ConfigurationElement element);
      public void Clear();
      protected override ConfigurationElement CreateNewElement();
      protected override object GetElementKey(ConfigurationElement 

element);
      public int IndexOf(ConnectionStringSettings settings);
      public void Remove(ConnectionStringSettings settings);
      public void Remove(string name);
      public void RemoveAt(int index);

      // Properties
      public ConnectionStringSettings this[string name] { get; }
      public ConnectionStringSettings this[int index] { get; set; }
      protected internal override ConfigurationPropertyCollection 

Properties { get; }

      // Fields
      private static ConfigurationPropertyCollection _properties;
}

Keď si pozrieme, ako sú metódy implementované, prvé, čo nás asi napadne - "trochu zvrátené od čias, čo existujú generics". Pomožeme si teda vytvorením bázovej ConfigurationElementCollection generic triedy, aby sme nemuseli všetky metódy znova a znova implementovať pri každej podobnej kolekcii.

Naviac však musíme vytvoriť novú bázovu triedu pre naše konfiguračné elementy, ktoré budú ako item v collection. Pridáme abstraktnú property Key, ktorá vráti názov kľúča nášho konfiguračného elementu. Túto musíme naviac implementovať v každom našom konfiguračnom elemente, ktorý bude prvkom kolekcie .

public abstract class ConfigurationElementBase : 

ConfigurationElement
{
    protected internal abstract string Key 

{ get; }
}

public abstract class ConfigurationElementCollection<T> : 
    ConfigurationElementCollection where T : ConfigurationElementBase
{                
    public T this[int index]
    {
        get { return (T) BaseGet(index); }
        set
        {
            if (BaseGet(index) != null)
                BaseRemoveAt(index);
            BaseAdd(index, value);
        }
    }

    new public T this[string key]
    {            
        get { return (T) BaseGet(key); }
    }

    protected override ConfigurationElement CreateNewElement()
    {
        return Activator.CreateInstance<T>();
    }

    protected override object GetElementKey(ConfigurationElement 

element)
    {
        return ((T)element).Key;
    }

    public bool Contains(string key)
    {
        return (BaseGet(key) != null);
    }        

    public void Add(T element)
    {
        BaseAdd(element);
    }        

    public void Remove(T element)
    {
        Remove(element.Key);
    }

    public void Remove(string key)
    {
        BaseRemove(key);
    }

    public void RemoveAt(int index)
    {
        BaseRemoveAt(index);
    }

    public void Clear()
    {
        BaseClear();
    }        
}

Implementácia je potom veľmi jednoduchá a rýchla a hlavne predídeme chybám. Nové verzie tried ContactConfigurationElement a ContactConfigurationElementCollection:

public sealed class ContactConfigurationElement : 
        ConfigurationElementBase
{
    [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
    public string Name
    {
        get { return (string)base["name"]; 

}
        set { base["name"] = value; }
    }

    [ConfigurationProperty("email", IsRequired = true)]
    public string Email
    {
        get { return (string)base["email"]; 

}
        set { base["email"] = value; }
    }

    protected internal override string Key
    {
        get { return Name; }
    }
}

public class ContactConfigurationElementCollection : 
    ConfigurationElementCollection<ContactConfigurationElement> {}
Zdrojové kódy príkladov ku článku
Vytvorme si vlastnú sekciu v konfiguračnom súbore (I.) - Úvod

Komentáre

# Pavel said:

Super clanek, trapim se s vlastnimi typy elementu v custom sekcich v app.config uz skoro dva dny. Perfektni prace, diky.

Wednesday, February 20, 2008 2:29 AM
# T said:

Diky Pavle.

Tuesday, April 08, 2008 5:26 PM
Prihlásiť | Registrovať | Pomoc