В прошлой части я показал, как можно создавать обработчики собственных секций файла конфигурации (app.config или web.config) и вызывать их из кода. Во второй части осталось рассмотреть еще несколько интересных моментов, которые могут пригодиться при работе с конфигурацией.
Унификация настроек
Вспомним, какой вид имеет наша секция настроек в файле конфигурации:
<table fileName="files\import.txt" codePage="utf8"> <fields> <add name="Id" elementType="int" length="10" /> <add name="Name" elementType="string" length="100" /> <add name="UserName" elementType="string" length="50" /> <add name="ObjectId" elementType="int" length="10" /> <add name="ParentObjectId" elementType="int" length="10" /> <add name="SerialNumber" elementType="string" length="20" /> </fields> </table>
В принципе, все хорошо и понятно. Но все-таки посмотрим, какой вид имеют стандартные секции connectionStrings и appSettings:
<connectionStrings> <add name="MyConnectionString1" connectionString="Data Source=sqlexpress;Initial Catalog=Base1;User ID=sa;Password=123" /> <add name="MyConnectionString2" connectionString="Data Source=sqlexpress;Initial Catalog=Base2;User ID=sa;Password=123" /> </connectionStrings> <appSettings> <add key="key1" value="value1"/> <add key="key2" value="value2"/> <add key="key3" value="value3"/> </appSettings>
Видно, что у стандартных секций отстутствует дополнительный элемент: элементы коллекции добавляются напрамую через <add />. Для того чтобы избавиться от лишних элементов <fields />, необходимо перебить некоторые базовые механизмы. Конечно, странно, что такой механизм не предоставляется по умолчанию.
Новые классы TableSection и FieldsCollection будут иметь следующий вид:
////// Обработчик секции, которая хранит описание файла с данными /// public class TableSection : ConfigurationSection { private static ConfigurationPropertyCollection _properties; private static readonly ConfigurationProperty FileNameProperty; private static readonly ConfigurationProperty CodePageProperty; private static readonly ConfigurationProperty FieldsProperty; protected override ConfigurationPropertyCollection Properties { get { return _properties; } } static TableSection() { FileNameProperty = new ConfigurationProperty("fileName", typeof(string), "import.txt"); CodePageProperty = new ConfigurationProperty("codePage", typeof(string), "utf8"); FieldsProperty = new ConfigurationProperty("", typeof(FieldsCollection), null, ConfigurationPropertyOptions.IsDefaultCollection); _properties = new ConfigurationPropertyCollection(); _properties.Add(FileNameProperty); _properties.Add(CodePageProperty); _properties.Add(FieldsProperty); } ////// Путь к файлу с данными /// public string FileName { get { return (string)this["fileName"]; } set { this["fileName"] = value; } } ////// Кодировка файла /// public string CodePage { get { return (string)this["codePage"]; } set { this["codePage"] = value; } } ////// Коллекция столбцов таблицы /// public FieldsCollection Fields { get { return (FieldsCollection)base[FieldsProperty]; } } }
и
////// Обработчик коллекции данных (в нашем случае, коллекции полей) /// public class FieldsCollection : ConfigurationElementCollection { protected override ConfigurationElement CreateNewElement() { return new FieldElement(); } protected override object GetElementKey(ConfigurationElement element) { return ((FieldElement)element).Name; } #region Методы-обертки для привычной работы с коллекцией ////// Получение элемента по его индексу /// /// ///public FieldElement this[int index] { get { return (FieldElement)BaseGet(index); } set { if (BaseGet(index) != null) BaseRemoveAt(index); BaseAdd(index, value); } } /// /// Получение элемента по его имени /// /// ///public new FieldElement this[string name] { get { return (FieldElement)BaseGet(name); } } /// /// Добавление элемента в коллекцию /// /// public void Add(FieldElement element) { BaseAdd(element); } ////// Очистка коллекции от элементов /// public void Clear() { BaseClear(); } public int IndexOf(FieldElement element) { return BaseIndexOf(element); } public void Remove(FieldElement element) { if (BaseIndexOf(element) >= 0) { BaseRemove(element.Name); } } public void Remove(string name) { BaseRemove(name); } public void RemoveAt(int index) { BaseRemoveAt(index); } #endregion }
Разберемся, что к чему. Из кода видно, что была произведена замена атрибутов на реализацию собственной коллекции свойст.
- ConfigurationProperty - класс, который представляет атрибут или дочерний элемент элемента конфигурации.
- Класс ConfigurationPropertyCollection представляет коллекцию объектов ConfigurationProperty, которые могут быть либо атрибутами, либо объектами ConfigurationElement элементов конфигурации. В нашем случае, Properties содержат два атрибута (fileName и codePage) и коллекцию Fields.
Для того, чтобы не создавался дополнительный элемент, важна строка:
FieldsProperty = new ConfigurationProperty("", typeof(FieldsCollection), null, ConfigurationPropertyOptions.IsDefaultCollection);
Обратите внимание, что первым параметром мы передаем null (можно и пустую строку "" поставить), а при обращении к свойству Fields возвращаем коллекцию не по имени, как остальные свойства, а по самому объекту:
////// Коллекция столбцов таблицы /// public FieldsCollection Fields { get { return (FieldsCollection)base[FieldsProperty]; } }
Кроме того, чтобы работать с коллекцией элементов (FieldsCollection) в коде подобно тому, как это делается в случае KeyValueConfigurationCollection или ConnectionStringSettingsCollection, я описал дополнительные методы в FieldsCollection.
Полученный обработчик совершенно спокойно скушает следующую конфигурацию:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="tableFields" type="UserSettings2.TableSection, UserSettings2" /> </configSections> <tableFields fileName="file.txt" codePage="cp1251"> <add name="Id" elementType="int" length="10" /> <add name="Name" elementType="string" length="100" /> <add name="UserName" elementType="string" length="55" /> <add name="ObjectId" elementType="int" length="10" /> <add name="ParentObjectId" elementType="int" length="10" /> <add name="SerialNumber" elementType="string" length="20" /> </tableFields> </configuration>
Управление конфигурацией из кода
На небольшом примере покажу, как программно можно изменять и сохранять конфигурацию:
class Program { static void Main(string[] args) { Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); TableSection tableSection = new TableSection { CodePage = "cp1251", FileName = "file.txt" }; tableSection.Fields.Add(new FieldElement { Name = "Id", ElementType = "int", Length = 10 }); tableSection.Fields.Add(new FieldElement { Name = "Name", ElementType = "string", Length = 100 }); tableSection.Fields.Add(new FieldElement { Name = "UserName", ElementType = "string", Length = 50 }); tableSection.Fields.Add(new FieldElement { Name = "ObjectId", ElementType = "int", Length = 10 }); tableSection.Fields.Add(new FieldElement { Name = "ParentObjectId", ElementType = "int", Length = 10 }); tableSection.Fields.Add(new FieldElement { Name = "SerialNumber", ElementType = "string", Length = 20 }); tableSection.Fields["UserName"].Length = 55; config.Sections.Remove("tableFields"); config.Sections.Add("tableFields", tableSection); config.SaveAs("test.xml"); } }
Думаю, что код предельно понятен и не требует пояснений. Если возникнут вопросы пишите в комментариях или смотрите справку на msdn.
Конечно, может показаться, что проделано неоправданно много работы. С одной стороны - да. С другой - если, например, одни и те же настройки таскаются из проекта в проект, то можно выделить это все в отдельную сборку и дальше пользоваться этим удобным инструментом. К тому же такой вид добавляет настройкам наглядность и позволяет использовать единый подход для работы с файлом конфигурации. Считаю, что мы получили красивый результат.
Ссылки:
Здравствуйте. В последнем примере вы удаляете одно поле. В случае с web.config будет ли происходит перезагрузка пула приложения?
ОтветитьУдалитьЧестно говоря, никогда не проверял). Но если учесть, что при изменении файла конфигурации пул приложения обычно перезагружается, то думаю, что и в этом случае будет точно также. Если, конечно, не возникнет каких-нибудь проблем с правами на запись...
ОтветитьУдалить