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

159

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

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

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

В примере ниже показан элемент, который мы хотим определять в файле Web.config уровня приложения:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <newUserDefaults city="Москва" country="Россия" language="Русский" regionCode="77" />
  ...
</configuration>

Мы собираемся создать раздел конфигурации под названием newUserDefaults, который имеет атрибуты city, country, language и regionCode. Может показаться, что эту задачу легко решить с помощью параметров приложения, но, как вы вскоре узнаете, существует ряд удобных и интересных средств, предлагаемых разделами конфигурации, поэтому в затрате дополнительных усилий есть смысл.

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

Создание класса обработчика раздела

Потребуется создать класс обработчика раздела, который позволит читать значения атрибутов из приложения. Для этого в проект добавляется новый файл класса по имени NewUserDefaultsSection.cs с определением, приведенным в примере ниже:

using System;
using System.Configuration;

namespace ConfigFiles
{
    public class NewUserDefaultsSection : ConfigurationSection
    {
        [ConfigurationProperty("city", IsRequired = true)]
        public string City
        {
            get { return (string)this["city"]; }
            set { this["city"] = value; }
        }

        [ConfigurationProperty("country", DefaultValue = "Россия")]
        public string Country
        {
            get { return (string)this["country"]; }
            set { this["country"] = value; }
        }

        [ConfigurationProperty("language")]
        public string Language
        {
            get { return (string)this["language"]; }
            set { this["language"] = value; }
        }

        [ConfigurationProperty("regionCode", DefaultValue = "1")]
        [IntegerValidator(MaxValue = 99, MinValue = 1)]
        public int Region
        {
            get { return (int)this["regionCode"]; }
            set { this["regionCode"] = value; }
        }
    }
}

Классы обработчиков разделов являются производными от класса ConfigurationSection, который находится в пространстве имен System.Configuration.

Мы начали с определения свойств для всех атрибутов, которые необходимо поддерживать в файле Web.config. Имена свойств обычно соответствуют именам атрибутов с заглавными первыми буквами. Имена свойств должны очевидным образом отражать, к каким атрибутам они относятся, но это всего лишь соглашение и можно назначать любые имена. Например, в приведенном выше классе обработчика свойство, представляющее атрибут regionCode, называется Region.

Базовым для разделов конфигурации является класс ConfigurationSection. В этом классе определена защищенная коллекция, используемая для хранения конфигурационных значений, с которыми осуществляется работа. Коллекция доступна через индексатор this. Мы должны реализовать каждое из определенных свойств, поэтому с помощью блоков set и get присваиваются и извлекаются значения из упомянутой коллекции.

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

Параметры, используемые с атрибутом ConfigurationProperty
Имя Описание
DefaultValue

Указывает стандартное значение для свойства, если оно не установлено в конфигурационном файле

IsDefaultCollection

Используется, когда раздел конфигурации управляет коллекцией элементов

IsRequired

Когда установлено в true, будет генерироваться исключение, если для этого свойства не установлено значение в рамках иерархии

Когда мы запрашиваем объект конфигурации, среда ASP.NET Framework создает новый экземпляр класса обработчика раздела и устанавливает значения свойств на основе значений, указанных в конфигурационных файлах. Среда ASP.NET Framework сообщит об ошибке, если заданные в конфигурационных файлах значения не могут быть преобразованы в соответствующий тип свойства, но мы можем дополнительно ограничить принимаемые значения с помощью атрибутов проверки достоверности, таких как атрибут IntegerValidator, который был применен к свойству Region. В параметрах MinValue и MaxValue указывается диапазон приемлемых значений для свойства. Если значение, заданное в конфигурационном файле, выходит за пределы этого диапазона или не может быть преобразовано в тип int, среда ASP.NET Framework сообщит об ошибке.

В таблице ниже описан набор атрибутов проверки достоверности, которые находятся в пространстве имен System.Configuration:

Классы атрибутов для проверки достоверности конфигурации
Имя Описание
CallbackValidator

Используется для выполнения специальной проверки достоверности, как будет показано ниже

IntegerValidator

Используется для проверки достоверности значений int. По умолчанию этот атрибут принимает значения внутри диапазона, определяемого параметрами MinValue и MaxValue, но за счет установки параметра ExcludeRange в true можно исключать значения из этого диапазона

LongValidator

Используется для проверки достоверности значений long. В нем определены те же самые параметры, что и в IntegerValidator

RegexStringValidator

Используется для проверки, что значение string соответствует регулярному выражению. Регулярное выражение указывается в параметре Regex

StringValidator

Используется для выполнения простых проверок достоверности значений string. Параметры MinLength и MaxLength позволяют ограничивать длину значения, а параметр InvalidCharacters используется для исключения символов

TimeSpanValidator

Используется для проверки достоверности временных промежутков. Параметры MinValueString и MaxValueString позволяют ограничивать диапазон значений в форме 0:30:00. Параметры MinValue и MaxValue делают то же самое, но требуют значений TimeSpan. Установка в true параметра ExcludeRange приводит к исключению значений, которые находятся между минимальным и максимальным значениями

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

using System;
using System.Configuration;

namespace ConfigFiles
{
    public class NewUserDefaultsSection : ConfigurationSection
    {
        [CallbackValidator(CallbackMethodName = "ValidateCity", 
		    Type = typeof(NewUserDefaultsSection))]
        [ConfigurationProperty("city", IsRequired = true)]
        public string City
        {
            // ...
        }

        // ...

        public static void ValidateCity(object candidateValue)
        {
            string value = (string)candidateValue;
            if (value.ToLower() == "париж")
            {
                throw new Exception("Любой город, кроме Парижа");
            }
        }
    }
}

Параметрами атрибута CallbackValidator являются CallbackMethodName и Type; они позволяют указывать метод, который должен вызываться во время обработки данных конфигурации. Метод должен быть статическим, принимать один аргумент типа object и ничего не возвращать. В текущем классе определен такой метод по имени ValidateCity().

Методу проверки достоверности передается значение, полученное из конфигурационного файла. Это значение уже преобразовано в тип свойства, к которому был применен атрибут. Поскольку атрибут был применен к свойству City, нам известно, что аргумент object может быть явно приведен к типу string. Метод проверки достоверности сообщает о проблемах со значением за счет генерации исключений. (В рассматриваемом примере исключение генерируется, если значением является "париж".) Этого можно было бы достигнуть с использованием атрибута RegexStringValidator, но мы хотели продемонстрировать простую проверку достоверности.

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

Определение раздела

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

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="newUserDefaults" type="ConfigFiles.NewUserDefaultsSection"/>
  </configSections>
  ...
</configuration>

Для определения новых разделов и групп разделов применяется элемент configSections. Мы определили новый раздел, для которого используется элемент section. В элементе section определены атрибуты, описанные в таблице ниже:

Атрибуты, определенные в элементе configSections/section
Имя Описание
allowDefinition

Используется для ограничения мест, где может быть определен раздел. Допустимыми значениями являются Everywhere (раздел может быть определен где угодно в иерархии конфигурации), MachineToApplication (раздел может быть определен в иерархии от файла Machine.config до файла Web.config уровня приложения), MachineToWebRoot (раздел может быть определен в файле Machine.config или в глобальном файле Web.config) и MachineOnly (раздел может быть определен только в файле Machine.config). Если этот атрибут не указан, применяется стандартное значение Everywhere

allowLocation

Указывает, может ли раздел быть определен в элементах location. Стандартным значением является true

name

Имя раздела

type

Имя класса обработчика

Благодаря этой таблице, можно понять, что раздел был определен с именем newUserDefaults, для него указан класс обработчика NewUserDefaultsSection и приняты стандартные значения для атрибутов allowDefinition и allowLocation.

Если нужно предотвратить переопределение значений уровня приложения в отдельных папках, атрибут allowDefinition потребуется установить в MachineToApplication, а атрибут allowLocation - в false.

Использование специального раздела конфигурации

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

using System.Web.Configuration;
using System.Collections.Generic;
using System.Configuration;

namespace ConfigFiles.Admin
{
    public partial class Default : System.Web.UI.Page
    {
        public IEnumerable<string> GetConfig()
        {
            NewUserDefaultsSection defaults
                = (NewUserDefaultsSection)WebConfigurationManager
                      .GetSection("customDefaults/newUserDefaults");

            yield return string.Format("Значения: {0}, {1}, {2}, {3}

", defaults.City, defaults.Country, defaults.Language, defaults.Region); } } }

Здесь применяется такой же процесс, как и для встроенных разделов, определенных где-либо в иерархии конфигурации. Доступ к разделу конфигурации осуществляется по имени. Результат типа object приводится к типу NewUserDefaultsSection, что позволяет прочитать значения свойств, соответствующие атрибутам, которые были определены.

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

Многие разделы конфигурации выражаются в виде коллекций, которыми можно манипулировать с помощью элементов add, remove и clear. В этой части статьи мы объясним, как создать специальный раздел конфигурации в виде коллекции. Мы начнем с элементов конфигурации, которые хотим использовать, и затем реализуем специальный раздел.

В примере ниже показано содержимое файла Web.config с добавленными новыми элементами для определения коллекции цветов:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="newUserDefaults" type="ConfigFiles.NewUserDefaultsSection"/>
  </configSections>
  <newUserDefaults city="Москва" country="Россия" language="Русский" regionCode="77" />
  <colors default="GRE">
    <add code="GRE" value="green" name="Зеленый" />
    <add code="BLU" value="blue" name="Синий" />
    <add code="RED" value="red" name="Красный" />
  </colors>
  ...
</configuration>

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

using System;
using System.Configuration;

namespace ConfigFiles
{
    public class Color : ConfigurationElement
    {
        [ConfigurationProperty("code", IsRequired = true)]
        public string Code
        {
            get { return (string)this["code"]; }
            set { this["code"] = value; }
        }

        [ConfigurationProperty("value", IsRequired = true)]
        public string Value
        {
            get { return (string)this["value"]; }
            set { this["value"] = value; }
        }

        [ConfigurationProperty("name", IsRequired = true)]
        public String Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }
    }
}

Класс, представляющий элементы данных конфигурации, является производным от класса ConfigurationElement и в нем определены свойства, которые соответствуют атрибутам элемента add. В случае создания раздела конфигурации такого вида элемент add может определять любые атрибуты, необходимые для представления данных; именно поэтому встречается настолько много вариаций элементов add в элементах конфигурации.

В классе Color определены свойства Code, Value и Name. С помощью атрибута ConfigurationProperty они ассоциируются с атрибутами элемента add, как это уже делалось для простого раздела конфигурации ранее.

Следующий шаг предусматривает определение коллекции, которая будет хранить элементы Color. Это должно делаться специальным образом, чтобы среда ASP.NET Framework знала, как заполнять такую коллекцию во время обработки данных конфигурации. Мы добавляем в проект файл класса по имени ColorCollection.cs с содержимым, представленным в примере ниже:

using System.Configuration;

namespace ConfigFiles
{
    public class ColorCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new Color();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((Color)element).Code;
        }

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

Базовым классом является ConfigurationElementCollection, который интегрирован вместе с другими классами в пространстве имен System.Configuration. Все что понадобится сделать - это переопределить метод CreateNewElement() для создания новых экземпляров класса обработчика элемента (Color в данном примере) и метод GetElementKey() для возврата ключа, который будет применяться для сохранения элемента в коллекции (мы используем свойство Code).

Мы должны также добавить индексатор, чтобы элементы можно было запрашивать по ключу. В базовом классе уже определен индексатор protected, поэтому придется воспользоваться ключевым словом new для сокрытия базовой реализации.

Имея классы обработчиков для элемента и коллекции, можно создавать обработчик раздела. Для этого мы добавили в проект файл класса под названием ColorSection содержимое которого представлено в примере ниже:

using System.Configuration;

namespace ConfigFiles
{
    public class ColorSection : ConfigurationSection
    {
        [ConfigurationProperty("", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(ColorCollection))]
        public ColorCollection Colors
        {
            get { return (ColorCollection)base[""]; }
        }

        [ConfigurationProperty("default")]
        public string Default
        {
            get { return (string)base["default"]; }
            set { base["default"] = value; }
        }
    }
}

Вся сложность управления разделом конфигурации сосредоточена в классах обработчиков для элемента и коллекции. Осталось только создать свойство, возвращающее экземпляр класса коллекции, и применить два атрибута. В атрибуте ConfigurationProperty была указана пустая строка для параметра name и значение true для параметра IsDefaultCollection. Это сообщает среде ASP.NET Framework о том, что элементы add, remove и clear в разделе конфигурации будут применяться к данной коллекции.

Внутри средства get свойства также используется пустая строка, которая является "волшебным словом", настраивающим требуемую коллекцию. Атрибут ConfigurationCollection указывает среде ASP.NET Framework класс коллекции, экземпляр которого должен быть создан для хранения элементов конфигурации. В рассматриваемом примере это класс ColorCollection.

Определение раздела

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

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="newUserDefaults" type="ConfigFiles.NewUserDefaultsSection"/>
    <section name="colors" type="ConfigFiles.ColorSection"/>
  </configSections>
  ...
</configuration>

Для определения раздела в виде коллекции никаких специальных атрибутов не требуется. Природа и сложность коллекции обеспечивается классом обработчика раздела.

Использование раздела конфигурации в виде коллекции

В примере ниже производится перечисление контента коллекции и запрашивается значение напрямую с применением значения атрибута default:

using System.Web.Configuration;
using System.Collections.Generic;
using System.Configuration;

namespace ConfigFiles
{

    public partial class Default : System.Web.UI.Page
    {
        public IEnumerable<string> GetConfig()
        {
            Configuration config
                = WebConfigurationManager.OpenWebConfiguration(Request.Path);

            ColorSection section = config.Sections["colors"] as ColorSection;
            Color defaultColor = section.Colors[section.Default];
            yield return string.Format(
                @"Цвет по умолчанию - <span style=""color:white;background:{0}"">{1}</span><br><br>",
                defaultColor.Value, defaultColor.Name);

            foreach (Color color in section.Colors)
                yield return string.Format(
                @"Цвет - <span style=""color:white;background:{0}"">{1}</span><br><br>",
                color.Value, color.Name);
        }
    }
}

Чтобы протестировать этот код, запустите приложение и запросите веб-форму Default.aspx:

Получение значений из раздела конфигурации в виде коллекции

Разделы конфигурации в виде коллекции подчиняются той же иерархии, что и обычные разделы, т.е. в конфигурационных файлах, находящихся ниже в иерархии, могут применяться элементы add, remove и clear для изменения контента коллекции:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  ...
  <location path="Admin/FolderForm.aspx">
    <appSettings>
      <add key="defaultCity" value="Санкт-Петербург" />
    </appSettings>
    <colors>
      <remove code="BLU" />
      <add code="ORA" value="orange" name="апельсиновый" />
    </colors>
  </location>
</configuration>

Мы удалили элемент "blue" и добавили элемент "orange". Если бы внутри веб-формы Admin\FolderForm.aspx использовался раздел Colors, он подучил бы отличающийся набор значений из Default.aspx. (Это можно было бы предотвратить, указав атрибуты allowLocation и allowDefinition при определении раздела.)

Создание группы разделов конфигурации

Группы разделов конфигурации позволяют привносить определенную структуру в конфигурационные файлы и группировать связанные разделы вместе. Цель этой части статьи - получить возможность выражать специальные разделы в файле Web.config так, как показано в примере ниже:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <sectionGroup name="customDefaults"
                  type="ConfigFiles.UserAndPlaceSectiongroup">
      <section name="newUserDefaults" type="ConfigFiles.NewUserDefaultsSection"/>
      <section name="colors" type="ConfigFiles.ColorSection"/>
    </sectionGroup>
  </configSections>
  <customDefaults>
    <newUserDefaults city="Москва" country="Россия" language="Русский" regionCode="77" />
    <colors default="GRE">
      <add code="GRE" value="green" name="Зеленый" />
      <add code="BLU" value="blue" name="Синий" />
      <add code="RED" value="red" name="Красный" />
    </colors>
  </customDefaults>
  ...
  <location path="Admin/FolderForm.aspx">
    <appSettings>
      <add key="defaultCity" value="Санкт-Петербург" />
    </appSettings>
    <customDefaults>
      <colors>
        <remove code="BLU"/>
        <add code="ORA" value="orange" name="апельсиновый" />
      </colors>
    </customDefaults>
  </location>
</configuration>

Это намного более простой пример, поэтому в одном коде мы определяем группу разделов (раздел configSections) и используем ее. Группа определяется с помощью элемента sectionGroup, в котором определены атрибуты name и type. Атрибут name указывает имя элемента, применяемого для объявления группы в главной части конфигурационного файла, а атрибут type устанавливает класс обработчика. Мы задали имя customDefaults и класс UserAndColorSectionGroup, который определен внутри файла класса UserAndColorSectionGroup.cs:

using System.Configuration;

namespace ConfigFiles
{
    public class UserAndPlaceSectiongroup : ConfigurationSectionGroup
    {
        [ConfigurationProperty("newUserDefaults")]
        public NewUserDefaultsSection NewUserDefaults
        {
            get { return (NewUserDefaultsSection)Sections["newUserDefaults"]; }
        }

        [ConfigurationProperty("places")]
        public ColorSection Colors
        {
            get { return (ColorSection)Sections["colors"]; }
        }
    }
}

Целью класса обработчика группы разделов является определение свойств, которые предоставляют строго типизированный доступ к разделам, содержащимся внутри них. В рассматриваемом случае это означает определение свойств NewUserDefaults и Colors, которые извлекают значения из коллекции Sections и приводят их к ожидаемому типу. В примере ниже демонстрируется использование класса обработчика группы разделов для получения одного из разделов и отображения содержащихся в нем значений:

using System.Web.Configuration;
using System.Collections.Generic;
using System.Configuration;

namespace ConfigFiles
{

    public partial class Default : System.Web.UI.Page
    {
        public IEnumerable<string> GetConfig()
        {
            Configuration config
                = WebConfigurationManager.OpenWebConfiguration(Request.Path);

            UserAndPlaceSectiongroup group
                = (UserAndPlaceSectiongroup)config.SectionGroups["customDefaults"];
            ColorSection section = group.Colors;

            Color defaultColor = section.Colors[section.Default];
            yield return string.Format(
                @"Цвет по умолчанию - <span style=""color:white;background:{0}"">{1}</span><br><br>",
                defaultColor.Value, defaultColor.Name);

            foreach (Color color in section.Colors)
                yield return string.Format(
                @"Цвет - <span style=""color:white;background:{0}"">{1}</span><br><br>",
                color.Value, color.Name);
        }
    }
}

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

Пройди тесты
Лучший чат для C# программистов