Nedávno byl na dotNETportalu dotaz týkající se konfigurace a kromě Settings File byli zmíněné i vlastní konfigurační sekce (Configuration Sections). Přestože jsem je za ta léta použil již hodně krát, uvědomil jsem si, že jsem o nich ještě nepsal. Nyní se to pokusím napravit.
Místo toho, abych ale detailně popisoval, který atribut nastaví ve výsledné XML konfiguraci to nebo ono, zde raději uvedu pár příkladů konfiguračních sekcí z praxe (detaily lze dohledat například v dokumentaci na MSDN).
Vlastní konfigurační sekce
Nejprve ale ještě chvilku obecně. Vlastní konfigurační sekce lze v .NETu psát již od verze 2.0. Každá konfigurační sekce je definována třídou poděděnou ze základní třídy ConfigurationSection z namespace System.Configuration. Pokud konfigurace kromě obyčejných vlastností obsahuje i další vnořené elementy, ty jsou definované dalšími třídami, které jsou poděděné z ConfigurationElement nebo ConfigurationElementCollection, pokud se jedná o kolekci. Konfigurační elementy mohou opět obsahovat obyčejné vlastností nebo další vnořené elementy.
Konfigurační sekci pak odpovídá část XML konfigurace v konfiguračním souboru app.config nebo web.config aplikace. Struktura tohoto XML konfigurace je námi zvolená tj. odpovídá tomu jak je definovaná v implementaci konfigurační sekce. Aby aplikace danou konfigurační sekci mohla použít, musí být přímo v .config souboru sekce zaregistrována. V registraci se uvádí název třídy včetně namespace a plný název assembly obsahující třídu konfigurační sekce. Jedná o tedy buď o assembly nějaké knihovny nebo případně assembly aplikace samotné.
ExchangeRatesConfigurationSection
První příklad ukazuje konfiguraci popisující nastavení, pro které zdrojové/domácí měny a v jaký čas má být prováděn automatický import kurzovních lístků z České národní banky (pro CZK), Národní banky slovenska (EUR) nebo Magyar Nemzeti Bank (HUF).
Příklad konfigurace včetně registrace konfigurační sekce vypadá takto:
<configuration>
<configSections>
<section name="exchangeRates" type="IMP.CNBService.ExchangeRatesConfigurationSection, CNBService" />
</configSections>
<exchangeRates>
<currency code="CZK" time="15:00:00"/>
<currency code="EUR" time="15:30:00"/>
<currency code="HUF" time="13:30:00"/>
</exchangeRates>
</configuration>
Implementace konfigurační sekce ExchangeRatesConfigurationSection je následující:
using System;
using System.Configuration;
using System.Collections.Generic;
namespace IMP.CNBService
{
internal class ExchangeRatesConfigurationSection : ConfigurationSection
{
#region action methods
[ConfigurationProperty("", IsDefaultCollection = true)]
public CurrencyCollection Currency
{
get { return (CurrencyCollection)base[""]; }
}
#endregion
}
internal abstract class ConfigurationElementCollection<TKey, TElement> : ConfigurationElementCollection, IEnumerable<TElement> where TElement : ConfigurationElement, new()
{
#region constructors and destructors
public ConfigurationElementCollection() { }
#endregion
#region action methods
protected abstract TKey GetElementKey(TElement element);
public void Add(TElement element)
{
this.BaseAdd(element);
}
public void Remove(TKey key)
{
this.BaseRemove(key);
}
public new IEnumerator<TElement> GetEnumerator()
{
foreach (TElement element in (ConfigurationElementCollection)this)
{
yield return element;
}
}
#endregion
#region property getters/setters
public abstract override ConfigurationElementCollectionType CollectionType { get; }
protected abstract override string ElementName { get; }
public TElement this[TKey key]
{
get
{
return (TElement)this.BaseGet(key);
}
}
public TElement this[int index]
{
get
{
return (TElement)this.BaseGet(index);
}
}
#endregion
#region private member functions
protected override ConfigurationElement CreateNewElement()
{
return new TElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return this.GetElementKey((TElement)element);
}
#endregion
}
internal class CurrencyCollection : ConfigurationElementCollection<string, CurrencyConfigurationElement>
{
#region property getters/setters
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
protected override string ElementName
{
get { return "currency"; }
}
#endregion
#region private member functions
protected override string GetElementKey(CurrencyConfigurationElement element)
{
return element.Code;
}
#endregion
}
internal class CurrencyConfigurationElement : ConfigurationElement
{
#region property getters/setters
/// <summary>
/// Mezinárodní trojpísmenný kód zdrojové/domácí měny (měna pro kterou se kurzy načítají, CZK, EUR nebo HUF)
/// </summary>
[ConfigurationProperty("code", IsKey = true, IsRequired = true)]
public string Code
{
get
{
return (string)this["code"];
}
private set
{
this["code"] = value;
}
}
/// <summary>
/// Doba spuštění načtení kurzů
/// </summary>
[ConfigurationProperty("time", IsRequired = true)]
public TimeSpan Time
{
get
{
return (TimeSpan)this["time"];
}
private set
{
this["time"] = value;
}
}
#endregion
}
}
A použití této konfigurace interně ve službě provádějící vlastní načítání kurzů vypadá nějak takto:
var configuration = ((ExchangeRatesConfigurationSection)System.Configuration.ConfigurationManager.GetSection("exchangeRates")).Currency;
foreach (IExchangeRateProvider provider in GetAllProviders())
{
var element = configuration[provider.CurrencyCode];
if (element != null)
{
ConfigureProvider(provider, element.Time);
}
}
SignCertificateConfigurationSection
Tato konfigurační sekce popisuje konfiguraci certifikátu používaného například pro podepisování generovaných PDF souborů. Tato konfigurační sekce byla již použita v článku zde.
Příklad této konfigurace je:
<configuration>
<configSections>
<section name="signCertificateConfiguration" type="IMP.Cryptography.SignCertificateConfigurationSection" />
</configSections>
<signCertificateConfiguration>
<signCertificate x509FindType="FindByThumbprint" findValue="862002e527e1ea0e3465d59cc4c49af0d093ad0c" storeLocation="LocalMachine" storeName="My" />
</signCertificateConfiguration>
</configuration>
A implementace konfigurační sekce SignCertificateConfigurationSection i pomocné třídy SignCertificateConfiguration, která konfiguraci zpřístupňuje, je:
using System;
using System.Security.Cryptography.X509Certificates;
namespace IMP.Cryptography
{
internal class SignCertificateConfigurationSection : System.Configuration.ConfigurationSection
{
#region constants
private const string cSignCertificateElementName = "signCertificate";
#endregion
#region property getters/setters
public X509Certificate2 SignCertificate
{
get
{
var element = this.SignCertificateReference;
if (string.IsNullOrWhiteSpace(element.FindValue))
{
throw new System.Configuration.ConfigurationErrorsException("Sign certificate configuration is missing.");
}
return CertificateUtil.GetValidCertificate(element.StoreName, element.StoreLocation, element.X509FindType, element.FindValue);
}
}
#endregion
#region private member functions
[ConfigurationProperty(cSignCertificateElementName, IsRequired = true)]
private System.ServiceModel.Configuration.CertificateReferenceElement SignCertificateReference
{
get { return (System.ServiceModel.Configuration.CertificateReferenceElement)this[cSignCertificateElementName]; }
}
#endregion
}
internal static class SignCertificateConfiguration
{
#region property getters/setters
public static X509Certificate2 SignCertificate
{
get
{
var configuration = (SignCertificateConfigurationSection)System.Configuration.ConfigurationManager.GetSection("signCertificateConfiguration");
if (configuration == null)
{
throw new System.Configuration.ConfigurationErrorsException("Configuration section 'signCertificateConfiguration' not found.");
}
return configuration.SignCertificate;
}
}
#endregion
}
}
Popis použití pomocné třídy CertificateUtil i třídu samotnou naleznete u původního článku.
PasswordPoliciesConfigurationSection
Posledním příkladem je konfigurační sekce PasswordPoliciesConfigurationSection, která může sloužit pro nastavení pravidel pro kontrolu sily hesla. Konfigurace obsahuje dvě pravidla, jedno používané pro běžné uživatele (defaultPasswordPolicy) a druhé používané například ve správě uživatelů pro nastavení hesla administrátorem aplikace (setPasswordPolicy).
Příklad konfigurace vypadá takto:
<configuration>
<configSections>
<section name="passwordPolicies" type="IMP.Security.PasswordPoliciesConfigurationSection, IMP.Security, Version=1.0.0.0, Culture=neutral, PublicKeyToken=21f74bb59b6740fd"/>
</configSections>
<passwordPolicies>
<defaultPasswordPolicy minRequiredPasswordLength="5" minRequiredUppercaseCharacters="1" validateUserName="false"/>
<setPasswordPolicy minRequiredPasswordLength="0" minRequiredUppercaseCharacters="0">
<invalidPasswords>
<remove value="abc123"/>
<remove value="heslo"/>
<add value="1234567890"/>
</invalidPasswords>
</setPasswordPolicy>
</passwordPolicies>
</configuration>
Implementace této konfigurační sekce je:
using System;
using System.Configuration;
using System.Collections.Generic;
namespace IMP.Security
{
internal class PasswordPoliciesConfigurationSection : ConfigurationSection
{
#region property getters/setters
[ConfigurationProperty("defaultPasswordPolicy")]
public PasswordPolicyElement DefaultPasswordPolicy
{
get
{
return (PasswordPolicyElement)this["defaultPasswordPolicy"];
}
}
[ConfigurationProperty("setPasswordPolicy")]
public PasswordPolicyElement SetPasswordPolicy
{
get
{
return (PasswordPolicyElement)this["setPasswordPolicy"];
}
}
#endregion
}
internal class PasswordPolicyElement : ConfigurationElement
{
#region constants
internal static readonly string[] DefaultInvalidPasswords = new[] { "123", "1234", "12345", "123456", "1234567", "12345678", "123456789", "abc123", "heslo" };
#endregion
#region constructors and destructors
public PasswordPolicyElement()
{
foreach (string s in DefaultInvalidPasswords)
{
this.InvalidPasswords.Add(s);
}
}
#endregion
#region property getters/setters
/// <summary>
/// Minimální délka hesla (výchozí hodnota je 6)
/// </summary>
[ConfigurationProperty("minRequiredPasswordLength")]
public int? MinRequiredPasswordLength
{
get
{
return (int?)this["minRequiredPasswordLength"];
}
}
/// <summary>
/// Minimální počet velkých písmen v heslu (výchozí hodnota je 0)
/// </summary>
[ConfigurationProperty("minRequiredUppercaseCharacters")]
public int? MinRequiredUppercaseCharacters
{
get
{
return (int?)this["minRequiredUppercaseCharacters"];
}
}
/// <summary>
/// Minimální počet malých písmen v heslu (výchozí hodnota je 0)
/// </summary>
[ConfigurationProperty("minRequiredLowercaseCharacters")]
public int? MinRequiredLowercaseCharacters
{
get
{
return (int?)this["minRequiredLowercaseCharacters"];
}
}
/// <summary>
/// Minimální počet číslic v heslu (výchozí hodnota je 0)
/// </summary>
[ConfigurationProperty("minRequiredDigits")]
public int? MinRequiredDigits
{
get
{
return (int?)this["minRequiredDigits"];
}
}
/// <summary>
/// Minimální počet nonalfanumerických znaků v heslu (výchozí hodnota je 0)
/// </summary>
[ConfigurationProperty("minRequiredNonAlphanumericCharacters")]
public int? MinRequiredNonAlphanumericCharacters
{
get
{
return (int?)this["minRequiredNonAlphanumericCharacters"];
}
}
/// <summary>
/// PasswordStrengthRegularExpression
/// </summary>
[ConfigurationProperty("passwordStrengthRegularExpression", DefaultValue = null)]
public string PasswordStrengthRegularExpression
{
get
{
return (string)this["passwordStrengthRegularExpression"];
}
}
/// <summary>
/// Kontrola zda heslo není shodné s přihlašovacím jménem nebo neobsahuje jméno či příjmení (výchozí hodnota je true)
/// </summary>
[ConfigurationProperty("validateUserName")]
public bool? ValidateUserName
{
get
{
return (bool?)this["validateUserName"];
}
}
/// <summary>
/// Seznam zakázaných hesel (výchozí hodnota je "123;1234;12345;123456;1234567;12345678;123456789;abc123;heslo")
/// </summary>
[ConfigurationProperty("invalidPasswords")]
[ConfigurationCollection(typeof(ValueConfigurationCollection<string>), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
public ValueConfigurationCollection<string> InvalidPasswords
{
get
{
return (ValueConfigurationCollection<string>)base["invalidPasswords"];
}
}
internal bool HasDefaultInvalidPasswords
{
get { return Enumerable.SequenceEqual(this.InvalidPasswords, DefaultInvalidPasswords); }
}
#endregion
}
internal class ValueConfigurationElement<T> : ConfigurationElement
{
#region property getters/setters
[ConfigurationProperty("value", IsKey = true, IsRequired = true)]
public T Value
{
get { return (T)this["value"]; }
set { this["value"] = value; }
}
#endregion
}
internal class ValueConfigurationCollection<T> : ConfigurationElementCollection, ICollection<T>
{
#region action methods
public void Clear()
{
BaseClear();
}
public bool Contains(T value)
{
return BaseGet(value) != null;
}
public void Add(T value)
{
BaseAdd(new ValueConfigurationElement<T>() { Value = value }, true);
}
public bool Remove(T value)
{
BaseRemove(value);
return true;
}
public void CopyTo(T[] array, int index)
{
if (array == null)
{
throw new ArgumentNullException("array");
}
foreach (ValueConfigurationElement<T> element in (ConfigurationElementCollection)this)
{
array.SetValue(element.Value, index++);
}
}
public new IEnumerator<T> GetEnumerator()
{
foreach (ValueConfigurationElement<T> element in (ConfigurationElementCollection)this)
{
yield return element.Value;
}
}
#endregion
#region property getters/setters
bool ICollection<T>.IsReadOnly
{
get { return IsReadOnly(); }
}
#endregion
#region private member functions
protected override ConfigurationElement CreateNewElement()
{
return new ValueConfigurationElement<T>();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((ValueConfigurationElement<T>)element).Value;
}
#endregion
}
}
Použití této konfigurace je trochu složitější, protože zde budeme chtít, aby element setPasswordPolicy mohl obsahovat pouze rozdíly oproti defaultPasswordPolicy:
internal sealed class PasswordPolicy
{
#region member varible and default property initialization
public int MinRequiredPasswordLength { get; private set; }
public int MinRequiredUppercaseCharacters { get; private set; }
public int MinRequiredLowercaseCharacters { get; private set; }
public int MinRequiredDigits { get; private set; }
public int MinRequiredNonAlphanumericCharacters { get; private set; }
public string PasswordStrengthRegularExpression { get; private set; }
public bool ValidateUserName { get; private set; }
public ISet<string> InvalidPasswords { get; private set; }
#endregion
#region constructors and destructors
internal PasswordPolicy(PasswordPolicyElement passwordPolicyElement)
{
this.MinRequiredPasswordLength = passwordPolicyElement.MinRequiredPasswordLength ?? 6;
this.MinRequiredUppercaseCharacters = passwordPolicyElement.MinRequiredUppercaseCharacters ?? 0;
this.MinRequiredLowercaseCharacters = passwordPolicyElement.MinRequiredLowercaseCharacters ?? 0;
this.MinRequiredDigits = passwordPolicyElement.MinRequiredDigits ?? 0;
this.MinRequiredNonAlphanumericCharacters = passwordPolicyElement.MinRequiredNonAlphanumericCharacters ?? 0;
this.PasswordStrengthRegularExpression = passwordPolicyElement.PasswordStrengthRegularExpression ?? "";
this.ValidateUserName = passwordPolicyElement.ValidateUserName ?? true;
this.InvalidPasswords = new HashSet<string>(passwordPolicyElement.InvalidPasswords);
}
internal PasswordPolicy(PasswordPolicy defautPolicy, PasswordPolicyElement passwordPolicyElement)
{
this.MinRequiredPasswordLength = passwordPolicyElement.MinRequiredPasswordLength ?? defautPolicy.MinRequiredPasswordLength;
this.MinRequiredUppercaseCharacters = passwordPolicyElement.MinRequiredUppercaseCharacters ?? defautPolicy.MinRequiredUppercaseCharacters;
this.MinRequiredLowercaseCharacters = passwordPolicyElement.MinRequiredLowercaseCharacters ?? defautPolicy.MinRequiredLowercaseCharacters;
this.MinRequiredDigits = passwordPolicyElement.MinRequiredDigits ?? defautPolicy.MinRequiredDigits;
this.MinRequiredNonAlphanumericCharacters = passwordPolicyElement.MinRequiredNonAlphanumericCharacters ?? defautPolicy.MinRequiredNonAlphanumericCharacters;
this.PasswordStrengthRegularExpression = !string.IsNullOrEmpty(passwordPolicyElement.PasswordStrengthRegularExpression) ? passwordPolicyElement.PasswordStrengthRegularExpression : defautPolicy.PasswordStrengthRegularExpression;
this.ValidateUserName = passwordPolicyElement.ValidateUserName ?? defautPolicy.ValidateUserName;
this.InvalidPasswords = new HashSet<string>(!passwordPolicyElement.HasDefaultInvalidPasswords ? (IEnumerable<string>)passwordPolicyElement.InvalidPasswords : defautPolicy.InvalidPasswords);
}
#endregion
}
var configuration = (PasswordPoliciesConfigurationSection)System.Configuration.ConfigurationManager.GetSection("passwordPolicies");
var defaultPasswordPolicy = new PasswordPolicy(configuration.DefaultPasswordPolicy);
var setPasswordPolicy = new PasswordPolicy(defaultPasswordPolicy, configuration.SetPasswordPolicy);
Další vlastní konfigurační sekce byla také zde na dotNETportalu implementována v tomto článku.
Implementací vlastních konfiguračních sekcí se dále zabývají například tyto zdroje:
http://msdn.microsoft.com/en-us/library/2tw134k3.aspx
http://www.codeproject.com/Articles/16466/Unraveling-the-Mysteries-of-NET-2-0-Configuration
http://robseder.wordpress.com/articles/the-complete-guide-to-custom-configuration-sections
http://www.codeproject.com/Articles/32490/Custom-Configuration-Sections-for-Lazy-Coders
http://www.codeproject.com/Articles/20548/Creating-a-Custom-Configuration-Section-in-C