вторник, 5 июля 2011 г.

Создание пользовательских разделов конфигурации (ConfigurationSection). Часть 1

В .NET Framework есть очень удобный механизм хранения настроек приложения. Как показывает практика, многие разработчики либо не понимают, как использовать потенциал этого механизма, либо вообще не знают, что в файле конфигурации можно хранить что-то большее, чем строку соединения с базой данных (в секции connectionStrings), ну или, в лучшем случае, пар ключ-значение (в секции appSettings). А механизм заключается в декларативном написании классов, которые описывают необходимые настройки.


Представим, что перед нами стоит задача импорта из текстового файла, в котором лежат данные в позиционном формате с фиксированной шириной поля. В настройках нам необходимо указать путь к файлу, кодировку файла и, конечно, описание полей. Получаем примерно такой вид:

<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>

Создание обработчика собственного раздела конфигурации

Класс, который отвечает за обработку раздела конфигурации, должен быть унаследован от класса ConfigurationSection (нужно добавить ссылку на System.Configuration.dll). В нашем случае получаем следующее:

using System.Configuration;

namespace UserSettings
{
    /// 
    /// Обработчик секции, которая хранит описание файл с данными
    /// 
    public class TableSection : ConfigurationSection
    {
        /// 
        /// Путь к файлу с данными
        /// 
        [ConfigurationProperty("fileName", DefaultValue = "import.txt")]
        public string FileName
        {
            get { return (string)this["fileName"]; }
            set { this["fileName"] = value; }
        }

        /// 
        /// Кодировка файла
        /// 
        [ConfigurationProperty("codePage", DefaultValue = "utf8")]
        public string CodePage
        {
            get { return (string)this["codePage"]; }
            set { this["codePage"] = value; }
        }

        /// 
        /// Коллекция столбцов таблицы
        /// 
        [ConfigurationProperty("fields")]
        public FieldsCollection Fields
        {
            get { return (FieldsCollection)this["fields"]; }
        }
    }
}

Так как у нас есть вложенная коллекция настроек, необходимо описать класс коллекции FieldsCollection, унаследованный от ConfigurationElementCollection, и элемент коллекции FieldElement, унаследованный от ConfigurationElement:

namespace UserSettings
{
    /// 
    /// Обработчик коллекции данных (в нашем случае, коллекции полей)
    /// 
    public class FieldsCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new FieldElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((FieldElement)element).Name;
        }
    }

    public class FieldElement : ConfigurationElement
    {
        /// 
        /// Имя столбца файла данных
        /// 
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }

        /// 
        /// Тип данных столбца
        /// 
        [ConfigurationProperty("elementType", DefaultValue = "string")]
        public string ElementType
        {
            get { return (string)this["elementType"]; }
            set { this["elementType"] = value; }
        }

        /// 
        /// Длина поля
        /// 
        [ConfigurationProperty("length", IsRequired = true)]
        public int Length
        {
            get { return (int)this["length"]; }
            set { this["length"] = value; }
        }
    }
}

С помощью атрибута ConfigurationProperty декларативно описываются свойства конфигурации. Суть применения, я думаю, понятна из примера:

  • Name - указывает название атрибута свойства (обязательный параметр, регистрозависимо)
  • IsRequired - требуется ли обязательное указание описываемого свойства
  • DefaultValue - значение свойства по умолчанию

Остальную информацию можно найти на msdn.

Кстати, вместо "самописных" коллекций можно использовать также стандартные коллекции, например, ConnectionStringSettingsCollection или NameValueConfigurationCollection. Причем использовать их можно как в самом классе-обработчике секции, так и в классе-элементе конфигурации.

Стоит также заметить, что класс ConfigurationSection является наследником от ConfigurationElement. Это значит, что, теоретически, обработчики разделов сами могут являтся и элементами коллекции.

Объявление обработчика раздела в файле конфигурации

Для связывания обработчика пользовательского раздела с именем раздела, в файле кофигурации необходимо добавить элемент configSections. Это своеобразные метаданные для менеджера конфигурации. В атрибуте type указывается тип и сборка, в которой лежит обработчик. Соответственно, в name указывается название созданной нами секции.

<configuration>
    <configSections>
        <section name="table" type="UserSettings.TableSection, UserSettings"/>
    </configSections>
    <!-- ... -->
<configuration>

Кстати, хочу сделать здесь небольшое отступление. Обычно, когда файл конфигурации используется для хранения строки соединения с базой, содержимое файла выглядит примерно так:

<configuration>
    <connectionStrings>
        <add name="MyConnectionString" connectionString="Data Source=sqlserver;Initial Catalog=DevBase;User ID=sa;Password=123" providerName="System.Data.SqlClient" />
    </connectionStrings>
<configuration>

Может возникнуть резонный вопрос (по крайней мере, у меня возник): а где же здесь указан обработчик для секции connectionStrings? Ответ кроется в механизме иерархии и наследование файла конфигурации (подробнее). Если вкратце, то существуют различные области параметров конфигурации: некоторые действуют в глобальной области, другие — только на уровне приложения, корневого файла Web.config или Machine.config. Все приложения .NET Framework наследуют базовые параметры конфигурации и параметры по умолчанию от файла systemroot\Microsoft .NET\Framework\номер_версии\CONFIG\Machine.config. Открываем указанный файл и видим связывание обработчика с интересующей нас секцией:

<configuration>
    <configSections>
        <!-- ... -->
        <section name="connectionStrings" type="System.Configuration.ConnectionStringsSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" requirePermission="false" />
    </configSections>
</configuration>

К слову, там же указан обработчик для секции appSettings. Но теперь вернемся к сути.

Добавление пользовательских элементов настройки в область параметров раздела конфигурации

Теперь соединим все вместе:

<configuration>
    <configSections>
        <section name="table" type="UserSettings.TableSection, UserSettings"/>
    </configSections>

    <connectionStrings>
        <add name="MyConnectionString" connectionString="Data Source=sqlexpress;Initial Catalog=DevBase;User ID=sa;Password=123" providerName="System.Data.SqlClient" />
    </connectionStrings>

    <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>
</configuration>

Мы видим, как лаконично уживаются все настройки приложения в едином файле конфигурации. Это же так удобно: все настройки в одном месте. Не нужно тащить за приложением еще какие-либо настроечные файлы. А самое приятное, мы воспользовались встроенной возможностью .NET Framework. Но это еще не все. Теперь посмотрим как пользоваться этим в приложении.

Обращение к конфигурации из кода

Всю самую сложную работу мы выполнили. Теперь можно пользоваться результатами. Получить настройки из пользовательского раздела можно так:

class Program
{
    static void Main(string[] args)
    {
        TableSection tableConfiguration = (TableSection)ConfigurationManager.GetSection("table");
        Console.WriteLine(tableConfiguration.FileName);
        Console.WriteLine(tableConfiguration.CodePage);

        foreach (FieldElement field in tableConfiguration.Fields)
        {
            Console.WriteLine(string.Format("Имя поля: {0}, тип поля: {1}, длина поля: {2}", field.Name, field.ElementType, field.Length));
        }

        Console.ReadLine();
    }
}

Единственное, что меня омрачает как разработчика на .NET Compact Framework, так это полное отсутствие данного механизма в указанной версии фрейморка. Там уж без написания своих собственных обработчиков не обойтись.

В следующий раз я покажу, как программно можно редактировать, создавать, удалять и сохранять файлы конфигурации. Также немного модифицируем класс TableSection для унификации вида настроек.

Ссылки:

Комментариев нет:

Отправить комментарий