Программирование: Работа с файлами конфигурации приложения

» Раздел: C#

В данной статье будет разобраны основы работы с конфигурационными файлами, секциями конфигурации и созданием своих конфигурационных разделов. Перед прочтением рекомендуется ознакомиться с языком разметки xml, индексаторами, свойствами, приведением типов и всем C# в целом.
Файл конфигурации позволяет хранить параметры приложения в формате XML для одного и более вычислительного устройства.
System.Configuration и Microsoft платформа.NET Framework пространства имен System.Collections.Specialized содержат классы, необходимые для извлечения информации из файла конфигурации приложения .NET во время выполнения.
Для того, чтобы ощутить всю мощь работы с конфигурационными файлами, создадим проект-консольное приложение, а затем добавим файл конфигурации.
Сделать это можно следующим образом:
В обозревателе решений правый клик мыши на проект -> Добавить (Add ...) -> Создать элемент (Create new)
Далее видим список с доступными для добавления элементами (появившемся окне по дефолту выделен пункт Элементы Visual C#).
Находим "файл конфигурации приложения" или "Configuration file", задаем имя файла, жмем "Добавить" и радуемся на 10%.
Примечание: в старых версиях Visual Studio файл конфигурации добавляется через добавить -> создать новый -> выбираем "xml файл" в списке с элементами.
После добавления откроем файл конфига и лицезреем следующее:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
</configuration>
Добавим раздел appSettings в наш файл, и затем элементы "ключ-значение" :
   <appSettings>
      <add key="KeyA" value="Msey" />
      <add key="KeyB" value="Love" />
      <add key="KeyC" value="Goosey" />
   </appSettings>
Что мы имеем? Стандартный конфигурационный раздел приложения appSettings, в котором содержится три элемента-ключа: KeyA, KeyB, KeyC.
Обращаясь к разделу с ключом, мы извлекаем по нему значение. Практический пример извлечения данных по ключу мы рассмотрим ниже.
Важно (!): перед тем, как начать работать с конфигурационными файлами из кода C#, нам необходимо добавить ссылку на сборку System.Configuration.
Для этого в обозревателе решений сделаем правый клик по элементу списка в проекте с названием Ссылки (References) -> Добавить ссылку (Add Reference) и в появившемся окне в разделе "Сборки" находим в списке сборок System.Configuration, слева от которой нужно поставить галочку в чекбоксе и нажать Ok.
Готово. Теперь нам доступны классы и методы для работы с конфигурационными файлами. Здесь начинается самое интересное.
Откроем наш класс программы и введем в метод Main следующий код:
            var a = ConfigurationManager.AppSettings.Get("KeyA");
            var b = ConfigurationManager.AppSettings.Get("KeyB");
            var c = ConfigurationManager.AppSettings.Get("KeyC");

//          var a = ConfigurationManager.AppSettings["KeyA"];
//          var b = ConfigurationManager.AppSettings["KeyB"]; // так тоже можно записать. результат будет идентичен
//          var c = ConfigurationManager.AppSettings["KeyC"];

            Console.WriteLine(a);
            Console.WriteLine(b);
            Console.WriteLine(c);
Здесь мы объявляем три переменные неявного типа, которым в то же время присваиваем значение элемента appSettings по ключу.
Очевидным образом, мы увидим следующий вывод на консоли:
Msey
Love
Goosey
Так как ConfigurationManager.AppSettings является NameValueCollection, косвенно наследуемым от IEnumerable, то нижеприведенный код имеет место быть:
            var settings = ConfigurationManager.AppSettings;
            foreach (var key in settings.AllKeys)
            {
                Console.WriteLine(settings.Get(key));
            }
Здесь мы получаем элементы appSettings в виде коллекции, затем берем у каждого элемента коллекции ключ и подставляем в метод Get, который впоследствии возвращает нам необходимое значение. То есть, мы получим тот же самый результат, но зато без явной подстановки ключей.
Итак, брать конфигурационные данные из стандартной секции appSettings мы научились. Едем дальше.
Собственные конфигурационные секции.
Для того, чтобы добавить свою собственную конфигурационную секцию, мы освободим поле боя, оставив консольное приложение и конфиг в первоначальном виде.
Структура configSections:
<configSections>
<!-- 
name = Имя, которое используется для ссылки на данный раздел в файле настройки.
type = Обработчик раздела настроек. Включает две секции: полный путь - пространство имен обработчика наших данных + имя самого обработчика, наименование сборки, где данный класс располагается. (только в случае наличия своей структуры данных, о которой разговор пойдет позже)
-->   
</configSections>
Теперь предоставим сведения о конфигурации пары имя/значение в разделе конфигурации:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <section name="customSection" type="System.Configuration.NameValueSectionHandler" />
    </configSections>

    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
   <customSection>
      <add key="KeyFromCustomSection" value="valueFromCustomSection" />
   </customSection>

</configuration>
Важно (!) секция configSections должна всегда быть в шапке файла конфигурации, тк она отвечает за инициализацию остальных секций. В ином случае приложение будет выдавать ошибки выполнения.
В коде программы напишем следующее:
            var customValue = (ConfigurationManager.GetSection("customSection") as NameValueCollection).Get("KeyFromCustomSection");

            Console.WriteLine(customValue);
Вывод будет принимать подобный вид:
valueFromCustomSection
Что и как здесь происходит? Мы получаем секцию по имени в виде System.Object, приводим к NameValueCollection, и получаем значение элемента по ключу. Здесь все довольно просто.
Перейдем к самой сложной части данной статьи: создание структуры данных через App.Config (не рекомендуется для новичков)
Итак, для того что бы загрузить свою структуру данных из файла App.Config нам потребуются следующие классы:
ConfigurationSection — Этот объект вернет нам пользовательскую секцию.
ConfigurationElementCollection — Это собственно коллекция элементов, которые мы определим в пользовательской секции.
ConfigurationElement — Это сам элемент, описывающий какую-то определенную вами сущность.
Очистим поле боя и приведем секцию configSections к следующему виду:
<configuration>
      <configSections>
       <section name="StartupFolders" type="ConfigSectionTester.StartupFoldersConfigSection, ConfigSectionTester"/>
     </configSections>
</configuration>
Далее создадим нашу собственную секцию, которая будет реализовывать нашу собственную модель данных:
<StartupFolders>
    <Folders>
        <add folderType="A" path="С:\foo" />
        <add folderType="B" path="C:\foo1" />
    </Folders>
</StartupFolders>
Замечание: если кому-то не нравится добавление узлов командой add в данном примере, то всегда можно сделать свой собственный префикс, используя следующий код:
[ConfigurationCollection(typeof(FolderElement)), AddItemName = "Folder"]
public class FoldersCollection : ConfigurationElementCollection
{
при определении коллекции элементов в структуре данных. Тогда в конфигурационном файле можно будет писать так:
<StartupFolders>
   <Folders>
     <Folder folderType="A" path="С:\foo" />
     <Folder folderType="B" path="C:\foo1" />
   </Folders>
 </StartupFolders>
Закончим модификации в файле конфигурации и перейдем к организации взаимодействия нашего приложения с ним.
Первым делом создадим класс-наследник от ConfigurationSection, что позволит нам взаимодействовать с нашей секцией в файле конфигурации через ConfigurationManager во время исполнения программы.
public class StartupFoldersConfigSection : ConfigurationSection
{
    [ConfigurationProperty( "Folders" )]
    public FoldersCollection FolderItems
   {
        get { return ( (FoldersCollection)( base[ "Folders" ] ) ); }
    }
}
Атрибут ConfigurationProperty("Folders") требуется для сопоставления свойства FolderItems с корневым узлом нашей структуры данных.
Класс FoldersCollection является наследником ConfigurationElementCollection, который обеспечивает взаимодействие с коллекцией наших элементов, описанных в Аpp.config. Определяется класс так:
[ConfigurationCollection(typeof(FolderElement))]
public class FoldersCollection : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new FolderElement();
    }

    protected override object GetElementKey(ConfigurationElement element )
    {
        return ((FolderElement)(element)).FolderType;
    }

    public FolderElement this[int idx]
    {
        get{return (FolderElement) BaseGet(idx); }
    }
}
Последним нам нужно создать ConfigurationElement, класс который свяжет нас с конечными данными, определенными в конфигурационном файле.
public class FolderElement : ConfigurationElement
{

    [ConfigurationProperty("folderType", DefaultValue="", IsKey=true, IsRequired=true)]
    public string FolderType
    {
        get {return ((string) (base["folderType"]));}
        set{base["folderType"] = value; }
    }

    [ConfigurationProperty( "path", DefaultValue = "", IsKey = false, IsRequired = false )]
    public string Path
    {
        get{return ((string)(base["path"])); }
        set{base["path"] = value; }
    }
}
Атрибут ConfigurationProperty("folderType") требуется для того, что бы проассоциировать имя xml-атрибута в файле конфигурации. Остальные параметры атрибута такие как DefaultValue="", IsKey=true, IsRequired=true определяют только различные опции применимые к свойствам.
Итак, мы имеем все необходимые данные и классы, которые предоставляют нам возможность хранить в конфигурационном файле app.config пользовательскую структуру данных.
Использовать данный подход можно так:
StartupFoldersConfigSection section = (StartupFoldersConfigSection)ConfigurationManager.GetSection( "StartupFolders" );

if ( section != null )
{
      Console.WriteLine( section.FolderItems[0].FolderType );
      Console.WriteLine( section.FolderItems[0].Path );
}
Спасибо за внимание.

Просмотров: 237

» Лучшие комментарии


ScorpioT1000 #1 - 2 недели назад (отредактировано ) 0
Ох уж этот xml) там на жсон ещё не торопятся в геймдеве переходить?
Эргалон #2 - 1 неделю назад 0
ScorpioT1000, JSON для конфига не лучший вариант. В том плане, что xml в этом плане гораздо лучше читаемый и регулируемый.
H #3 - 1 неделю назад 0
xml формат не для хранения конфигов имхо, а для передачи сложных типов данных (объектов и т.п.). Есть же json/yml или хотя-бы ini.
xml
<appSettings>
      <add key="KeyA" value="Msey" />
      <add key="KeyB" value="Love" />
      <add key="KeyC" value="Goosey" />
</appSettings>
json
{
  "appSettings": {
    "KeyA": "Msey",
    "KeyB": "Love",
    "KeyC": "Goosey",
  }
}
yml
appSettings:
  KeyA: Msey
  KeyB: Love
  KeyC: Goosey
ini
[appSettings]
KeyA=Msey
KeyB=Love
KeyC=Goosey
ScorpioT1000 #4 - 1 неделю назад (отредактировано ) 0
Эргалон, лолшто, приведи примеры
{
	"configuration": {
		"configSections": {
			"section": {
				"name": "customSection",
				"type": "System.Configuration.NameValueSectionHandler"
			}
		},
		"startup": {
			"supportedRuntime": {
				"version": "v4.0",
				"sku": ".NETFramework,Version=v4.6.1"
			}
		},
		"customSection": {
			"add": {
				"key": "KeyFromCustomSection",
				"value": "valueFromCustomSection"
			}
		}
	}
}
Msey #5 - 1 неделю назад 0
ScorpioT1000:
Ох уж этот xml) там на жсон ещё не торопятся в геймдеве переходить?
На самом деле это проблемный вопрос, тк большинство конфигураций для расширений (логгеры, фреймворки итд) инжектятся именно в xml формате, и чтобы убедиться, что они читают конфиги в нескольких форматах, нужно, либо читать документацию, либо в случае ее отсутсвия чекать рефлектором.
По крайней мере в дотнете
ScorpioT1000 #6 - 1 неделю назад 0
Msey, а в геймдеве принято не читать документацию?
Devion #7 - 1 неделю назад 0
не знаю что за yml, знаю yaml.
для сложных типов данных как раз не xml а yaml, т.к. это больше чем разметка, есть поддержка алиасов специально для сериализации ссылочных типов
Эргалон #8 - 1 неделю назад (отредактировано ) 0
ScorpioT1000, А теперь прокомментируй каждую строку в джейсоне своем и покажи редактор, который грамотно выделит синтаксис)
Может он и подойдет, для более маленьких конфигураций, а если у тебя конфигурация на 1000+ строк? Как ты будешь потом разгребаться, что есть где?
Msey #9 - 1 неделю назад (отредактировано ) 0
ScorpioT1000:
  • В самой статье ни слова не было о геймдеве, а ключевое слово под статьей unity3d чисто ради кликбейта
  • Сейчас речь идет конкретно о платформе .net, где xml - штатный язык разметки, и писать отдельную библиотеку чисто ради единоразового чтения параметров как минимум глупо (это без учета того, что конфигурационными файлами пользуются зачастую заказчики, и они могут по физиономии настучать за такие вот распространенные конфиги)
В тех проектах, за которые мне чаще всего не платят, я обычно самописными конфигами пользуюсь
alexprey #10 - 1 неделю назад 0
тк большинство конфигураций для расширений (логгеры, фреймворки итд) инжектятся именно в xml формате
щто, оно же через тот же конфигуратор грузится.
Hellfim #11 - 1 неделю назад 0
ScorpioT1000, А теперь прокомментируй каждую строку в джейсоне своем
Это, конечно, классно просить сделать то, для чего формат не предназначен.
и покажи редактор, который грамотно выделит синтаксис)
Notepad++ более чем достаточно
Может он и подойдет, для более маленьких конфигураций, а если у тебя конфигурация на 1000+ строк? Как ты будешь потом разгребаться, что есть где?
Ровно также как и в xml, только в xml больше тегов стоит.
писать отдельную библиотеку чисто ради единоразового чтения параметров как минимум глупо
Ну да, Newtonsoft подключить к проекту нельзя. Нужно что-то своё навелосипедить :)
Это сообщение удалено
ScorpioT1000 #13 - 1 неделю назад (отредактировано ) 0
Эргалон, да, комментарии в жсон не предусмотрены, т.к. он изначально был для передачи данных по сети. Зато они есть в yml, как ранее уже описывали. yml лучше подходит для конфигурации, но он всё ещё мало поддерживается старпёрскими библиотеками.
В самой статье ни слова не было о геймдеве
Про геймдев я сказал, т.к. это широкая стенд-элон индустрия, в вебе и сетевых аппликухах xml уже давно пережиток прошлого, за исключением некоторых протоколов (хотя нет, привет андроиду и жаве с их вьюхами)
ZlaYa1000 #14 - 1 неделю назад 0
ScorpioT1000, который из yml? под таким разрешением несколько форматов
Devion #15 - 1 неделю назад (отредактировано ) 3
Про геймдев я сказал, т.к. это широкая стенд-элон индустрия, в вебе и сетевых аппликухах xml уже давно пережиток прошлого, за исключением некоторых протоколов (хотя нет, привет андроиду и жаве с их вьюхами)
Файлы конфигурации в xml очень жестко навязывались в дотнетах. Например
  • app.config, который, как пример, мы создаем если хотим например зареплейсить какой-нибудь референс в зависимостях проекта
  • web.config
  • банально файлы sln и csproj
  • в ксамарине шаблоны форм (причем и под платформы и "свое" в таком виде)
  • вроде как MVC тоже что-то такое тянули, но не открывал давненько, боюсь ошибиться
  • файлы ресурсов всякие
  • WPF
  • WCF
  • если зайти на msdn и поглядеть там 90% вшитых файлов конфигураций xml
Видно, что сейчас от этого отходят, например .Net Core где солюшн уже в другом формате, или стандарт online темплейтов в json сделан, и на .Net Core можно переобъявлять старые app.config на другой лад
но как суть, более старые решения там навязывали xml, мб из-за вопросов совместимости.
Понятно что xml это синтаксически перегруженный кусок говна и юзать его намеренно на новых проектах то еще удовольствие, но момент относительно "всякого нативного" в дотнете еще актуален - то что постулируют писать в xml, пишется в xml.
ScorpioT1000 #16 - 1 неделю назад 0
Devion, его форсили, т.к. была libxml, а кроме неё ничего не было, только всякие бомжовские ini