ASP.NET MVC: Читаем настройки сайта из своей секции файла конфигурации web.config

Опубликовано 21.09.2012 07:00:00 в категории Микросервисы

Много раз приходилось сохранять настройки сайта в файле конфигурации. Раздел appSettings предоставляет возможность хранить настройки по принципу "ключ" = "значение" (Dictionary). Я же хочу показать как можно создать свою секцию в файле конфигурации, как читать из нее данные значения.

Постановка задачи

Требуется создать свою секцию настроек в конфигурационном файле web.config, со структурой более "продвинутой" чем appSettings (то есть нечто большее, чем просто Dictionary). А так же надо реализовать механизм считывания и обновления данных в этой секции.

Как сохранить настройки или выбор стратегии

Снова напомню, что есть серия статей "История одного проекта" (далее ИОП), которая посвящена разработке сайта “музей юмора”. И поэтому, давайте снова поговорим про этот проект, как можно расширить возможности по администрированию этого сайта. Предположим, что лента экспонатов, должна хранить записи за какой-то определенный срок. Например, за одну неделю, то есть семь дней. А если я захочу изменить срок, например на 14 дней? А если я захочу вообще временно отключить удаление записей? А если я хочу временно запретить публикацию экспонатов на ленте через IE-ускоритель? А если... Слишком много "а если"... Надо просто эти параметры где-то хранить с возможностью управления ими непосредственно со страниц сайта. Один из вариантов - файл конфигурации web.config, можно также хранить в базе данных, или, например, в текстовом файле, или сделать другой сайт, на котором сделать web-сервис, который сможет "выдать" всем моим сайта настройки для каждого или общие для всех. Или вообще, всё выложить в облако Azure и платить некоторую сумму в Microsoft... Всё ограничено лишь вашей фантазией. Мы будем говорить про первый вариант – сохранение настроек сайта в файле конфигурации.

Итак, хранение настроек в web.config тоже можно реализовать как минимум двумя способами. Читать/писать данные в файле конфигурации можно:

  • Из секции appSettings. ASP.NET предоставляет программистам в своё распоряжение самый простой вариант. Информация в этой секции хранится по принципу Dictionary (ключ = значение).
  • Из собственной секции файла конфигурации. ASP.NET также предоставляет такую возможность. Структура данных в этом случае может быть сколь угодно сложной (комплексные типы), более того, вы можете гарантировать значения по умолчанию для своих параметров и даже валидацию ввода при изменении свойств, что является неоспоримым плюсом для этого способа.

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

Начнем с простого. Пусть на данный момент мне требуется такая ветка в файле конфигурации:

<SiteSettingsGroup>
    <SiteSettings>
        <PagerSize>
            <Entity Name="Logs" Size="20" />
            <Entity Name="Exhibit" Size="10" />
            <Entity Name="Lenta" Size="15" />
        </PagerSize>
        <Lenta DeleteAfterDays="17" AllowPostFromShare="false" />
    </SiteSettings>
</SiteSettingsGroup>

Если описать вкратце, то SiteSettingsGroup – название группы. SiteSettings – это та самая секция, которую мы будем создавать. PagerSize – это коллекция с настройками размера страницы при отображении в пейджере для определенного типа сущности. Lenta – это дополнительный раздел настроек для ленты анекдотов, а значения атрибутов, итак понятны.

Немного специальных классов

Создадим несколько классов. Для начала – главный класс SiteSettings (также называется и файл .cs). Чтобы все получилось, нужно для начала добавить namespace:

using System.Configuration;

Для начала создадим первый “кирпичик” выбранной структуры – класс LentaElement из указанной выше конфигурации:

public class LentaElement : ConfigurationElement {

    [ConfigurationProperty("DeleteAfterDays", DefaultValue = 7, IsRequired = true)]
    [IntegerValidator(MinValue = 3, MaxValue = 30, ExcludeRange = false)]
    public int DeleteAfterDays {
        get {
            return (int)this["DeleteAfterDays"];
        }
        set {
            this["DeleteAfterDays"] = value;
        }
    }

    [ConfigurationProperty("AllowPostFromShare", DefaultValue = true, IsRequired = true)]
    public bool AllowPostFromShare {
        get {
            return (bool)this["AllowPostFromShare"];
        }
        set {
            this["AllowPostFromShare"] = value;
        }
    }
}

Обратите внимание на то, что класс унаследован от ConfigurationElement. Это один из основных базовых классов, которые используются в web.config для разделов. Дальше будет понятнее.

Далее надо бы создать главный раздел для моей секции и уже использовать LentaElement как свойство. Класс я назову SiteSettings:

public class SiteSettings : ConfigurationSection {

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

Между делом или Config-помощник

Для того, чтобы “достучаться” до любого значения любого параметра моей конфигурации, давайте прямо сейчас создадим специальный класс-помощник. Я буду его использовать чтобы протестировать работоспособность конфигурации:

public class Config {

    /// <summary>
    /// скроем конструктор от любопытных программеров
    /// </summary>
    internal Config() { }

    /// <summary>
    /// наименование раздела конфигурации
    /// </summary>
    private const string CONFIGGROUPNAME = "SiteSettingsGroup/";

    /// <summary>
    /// наименование секции
    /// </summary>
    private const string CONFIGSECTIONNAME = "SiteSettings";

    /// <summary>
    /// Чтение раздела конфигурации целиком
    /// </summary>
    /// <returns></returns>
    internal static SiteSettings Get() {
        var config = (SiteSettings)ConfigurationManager
             .GetSection(String.Concat(CONFIGGROUPNAME, CONFIGSECTIONNAME));
        return config;
    }
}

А теперь пришло время попробовать что получилось. Попробуем прочитать информацию из конфигурации. Я предварительно в файле  web.config пока убрал (закомментировал) из своей конфигурации секцию PagerSize. Потому что она еще не реализована, а значит приложение даже не будет пытаться запуститься, а просто вывалится с ошибкой.

В головном контролере в методе Index, я для проверки вставил пару строк, чтобы проверить что конфигурация читается, запустил и…

Отлично! Как раз то, что я и указал в своей конфигурации. Дальше - больше. Я добавил еще несколько классов. Это самое интересное. В раздел SiteSettings я добавил определение класса PagerSizeCollection. Чтобы можно было работать с коллекцией настроек. Далее определил класс PageSizeItemsElement, который и представляет собой одну из строчек в списке PagerSize… Наверное, всё-таки имеет смысл показать весь файл SiteSettings целиком:

public class SiteSettings : ConfigurationSection
{

    [ConfigurationProperty("PagerSize")]
    public PagerSizeCollection PagerSize
    {
        get
        {
            return ((PagerSizeCollection)base["PagerSize"]);
        }
    }

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

[ConfigurationCollection(typeof(PageSizeItemsElement), AddItemName = "Entity")]
public class PagerSizeCollection : ConfigurationElementCollection
{

    public PageSizeItemsElement this[int index]
    {
        get { return (PageSizeItemsElement)BaseGet(index); }
    }

    protected override ConfigurationElement CreateNewElement()
    {
        return new PageSizeItemsElement();
    }

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

public class PageSizeItemsElement : ConfigurationElement
{

    [ConfigurationProperty("Size", DefaultValue = 20, IsRequired = true)]
    [IntegerValidator(MinValue = 5, MaxValue = 100, ExcludeRange = false)]
    public int Size
    {
        get
        {
            return (int)this["Size"];
        }
        set
        {
            this["Size"] = value;
        }
    }

    [ConfigurationProperty("Name", DefaultValue = "EntityName", IsRequired = true)]
    [StringValidator(MinLength = 3, MaxLength = 50,
           InvalidCharacters = " ~!@#$%^&*()[]{}/;'\"|\\")]
    public String Name
    {
        get
        {
            return (String)this["Name"];
        }
        set
        {
            this["Name"] = value;
        }
    }
}

public class LentaElement : ConfigurationElement
{

    [ConfigurationProperty("DeleteAfterDays", DefaultValue = 7, IsRequired = true)]
    [IntegerValidator(MinValue = 3, MaxValue = 30, ExcludeRange = false)]
    public int DeleteAfterDays
    {
        get
        {
            return (int)this["DeleteAfterDays"];
        }
        set
        {
            this["DeleteAfterDays"] = value;
        }
    }

    [ConfigurationProperty("AllowPostFromShare", DefaultValue = true, IsRequired = true)]
    public bool AllowPostFromShare
    {
        get
        {
            return (bool)this["AllowPostFromShare"];
        }
        set
        {
            this["AllowPostFromShare"] = value;
        }
    }
}

Чтобы проверить работоспособность проделываю такую же операцию как и в прошлый раз:

Всё работает. Теперь осталось в подключить настройки к системе, то есть начать использовать их. Читается всё замечательно. Попробуем сохранить?

Заключение

В следующей статье я создам форму редактирования настроек (скорее всего это будет knockout.js, то есть это означает AJAX).

Комментарии к статье ()

Загрузка...

Что-то пошло не по сценарию и завершилось ошибкой. Перезагрузить страницу (F5) 🗙

Переподключаем сервер...

Переподключение сломалось... Пробуем еще раз сек.

Не получилось переподключится.
Пожалуйста, обновите страницу F5

Сессия была поставлена сервером на паузу.

Восстановить сессию не получилось.
Пожалуйста, обновите страницу F5.