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:
- Bill Gates
- Mickey Mouse
- 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 :-)
- Bill Gates
- Steve Ballmer
- 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