Pull to refresh

Comments 44

Тупой вопрос от js-кодера: а почему плохо просто построить XML DOM и читать всё оттуда?
Приветствую

В мире .NET разработчики привыкли работать с объектами. Поэтому практически в любом приложении можно встретить строчки

AppSettingsSection appSettings  = (AppSettingsSection)config.GetSection("appSettings");
string connectionString = appSettings.Settings["SqlConnectionString"].Value;
MyAppSettings myAppSettings = new MyAppSettings (connectionString);


Используем myAppSettings.ConnectionString по своему усмотрению.

Это очень простой пример, но даже в нем нам приходится собственноручно создавать объект с настройками и заполнять его. Библиотека все это сделает за нас.

Все усложняется, когда появляются настройки со многими вложенностями. Например, в моем текущем проекте у нас в зависимости от страны — свои настройки, в которых прописываются подключения к различные сторонним сервисам, строки подключения к БД и тд. В результате чего для загрузки конфигурационного файла пришлось бы писать очень много кода, а так — создал классы и загрузил все одной строчкой.
Дополню ответ DotNetCraft.
В случае прямой работы придется вводить название секций и атрибутов в виде строк.
А это прямой путь к опечаткам и головной боли при переименовании.
А каким образом приведённый здесь подход избавляет от опечаток и боли? Всё равно где-то придётся прописывать имя тега, откуда берётся настройка, телепатию в дотнет вроде ещё не завезли. И если что-то переименуется, всё равно придётся что-то в связи с этим менять. Разве нет?
В примере автора имя тега совпадает с именем поля, если изменится, то просто дописываем атрибут, а библиотека за нас смаппит. Даже для загрузки секции можно написать обертку, чтобы не вводить её имя вручную.
У вас же при изменении атрибута или секции придется изменять все места, где она используется.
лучше ошибиться в одном месте, чем в нескольких. разве нет?
Дополню Deosis

Если в конфигурационном файле будет жлемент, а в классах его не будет, то система сгенерит Exception. Таким образом происходит проверка конфига и классов.
Ещё наверно тупее вопрос — а почему просто не использовать json? Пишешь нужные тебе классы, автоматически сериализуешь\десериализуешь без всяких монструозных инструментов. Читабельно, быстро, просто, удобно.
Приветствую

Listrigon правильно ответил про стандартную систему конфигурационных файлов .NET. От себу лишь слегка добавлю, что в .NET для хранения конфигурации используется App.Config, в котором и записывается все настройки. Формат хранения данных — XML.

Более подробно можно прочитать здесь:
1. https://msdn.microsoft.com/en-us/library/1xtk877y.aspx
2. https://msdn.microsoft.com/en-us/library/ms254494(v=vs.110).aspx
Скорее всего потому, что все это дело является частью стандартной системы конфигурационных файлов .NET.
Чтобы не делать отдельного файла под свои настройки нужно быть частью структуры этого файла, который пользуется и другими библиотеками. Хотя да, с JSON было бы все проще
А в чем отличие от Custom Configuration Sections? Только то, что POCO без атрибутов? Я не вижу в System.Configuration особых сложностей и тяжестей.
Приветствую
На мой взгляд прелесть в том, что кода становится меньше.

Для примера возьмем секцию из статьи по вашей ссылке

<StartupFolders>
   <Folders>
     <Folder folderType = "A" path="c:\foo" />
     <Folder folderType = "B" path="C:\foo1" />
   </Folders>
 </StartupFolders>


Создадим классы

class StartupFolders
{
    public Folders Folders { get; set; }
}

class Folders
{
    public List<Folders> Folders { get; set; }
}

class Folder
{
    public string FolderType { get; set; }
    public string Path { get; set; }
}


Вызовем загрузчик
StartupFolders startupFolders= (dynamic)ConfigurationManager.GetSection("StartupFolders");


Ну и как бы все :) Мы загрузили все настройки.

Обратите внимание, что я нигде не использовал ключи а-ля «Folders», что, с моей точки хрения, уменьшает ошибку при копипасте.

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

… а теперь, значит, вы хотите переименовать свойство Folders.Folders в Folders.NewFolders, но не хотите трогать конфигурационные файлы (т.е., в них элемент так и должен остаться Folders). И как это сделать?

Если я правильно понял вопрос, то

[PropertyMapping("Folders ")]
public List<Folders> NewFolders { get; set; }


Это означает, что в конфигу мы ищем Folders и грузим данные в NewFolders.

Хотя, если честно, то я почти не сталкивался с ситуациями, когда надо было поменять имя… Вот структуру — это да, частое явление. То мы храним это в Листе, то давайте сделаем словарь…

З.Ы. Забыл упомянуть об этом аттрибуте в статье.
вместо
            string[] items = input.Split(';');
            List<string> result = new List<string>();

            result.AddRange(items);

            return result;


может лучше
return input.Split(';').ToList();
Приветствую

Да, соглашусь, что можно input.Split(';').ToList();

Однако, я хотел акцентировать внимание на то, что в этом методе можно написать любую логику. Например, в текущем примере мы можем добавить проверку правильность email'ов и сгенерить Exception если что-то не так.
UFO just landed and posted this here
Приветствую

Да, соглашусь, что можно input.Split(';').ToList();

Однако, я хотел акцентрировать внимание на то, что в этом методе можно написать любую логику. Например, в текущем примере мы можем добавить проверку правильность email'ов и сгенерить Exception если что-то не так.
В .NET настройки делятся на настройки приложения и пользовательские. (вторые доступны для изменения, первые только для чтения [если не править файл вручную])
Библиотека как-либо обрабатывает эти различия?
Например, пользователь переопределил несколько настроек и хочет их сохранить.
Что нужно использовать вместо Configuration.Save()?
Основное предназначение этой библиотеки — чтение настроек приложения, поэтому методы сохранения не предусмотрены.

Если честно, то я не стрононник что-либо менять в конфиге приложения в рантайме. Если что-то и надо заменить, то, используя, например, TeamCity+Octopus, создаем релиз и деплоим его с измененными настройками. Это относится к настройкам приложения.

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

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

ЗЫ. Атрибут «configSource» продолжает нормально поддерживаться?
И, заодно, я так понимаю, у вас нет поддержки стандартного паттерна коллекций в web/app.config?

<items>
    <clear />
    <add name="Item 1" />
    <add name="Item 2" />
</items>
Под «рантаймом» я понимаю, когда программа запущена и она сама меняет ее конфиг. Вот я именно против этого. А вот когда в процессе развертывания кто-то меняет конфиг, так это я «за». Например, мы создаем релизный конфиг и там указываем заменить connectionString на реальзое значение в зависимости от среды развертывания. Что-то мне подсказывает, что в этом вопросе мы по одну сторону баррикады.

Что касается <clear/> и configSource… Я с этимт «параметрами» сталкивался очень давно и опыт был негативным, так что я стараюсь их избегать. Более того, это лично мое мнение, задвать в конфигурации, то что конфигурация в таком-то файле как-то странновато. Смысл данного действия для меня представляется только в том, чтобы разделить Debug от Release, ну, или разные конфиги для разных клиентов. Все это легко делается путем создания конфигурационного файла с разметкой и, в момент создания билда, автоматически подставляем нужные параметры.
«clear/add/remove» упрощает создание иерархических конфигов с настройками по-умолчанию: есть некий корневой конфиг с дефолтными элементами коллекции, и в своём под-конфиге можно переиспользовать их, можно добавить свои, можно убрать некоторые (или все) дефолтные. Без этого придётся каждый раз указывать все необходимые элементы. С одной стороны, это вроде как хорошо — все элементы явно присутствуют, с другой — конфиг может быть сильно перегружен вспомогательными элементами, появляется соблазн копипастить и т.п.

configSource — это как "#include" или «using» или «import» — полезен тем, способствует модуляризации: можно разбить огромный неудобоваримый конфиг с кучей настроек «обо всём» на несколько более сфокусированных конфигов. Например, ту часть, что настраивается девелопером и далее не предполагается перенастраивать (всякие там assemblyBindings и проч), хранить в основном модуле, а части, которые нужно настраивать при деплое — в своих модулях. Кроме того, можно создать несколько под-конфигов на разные случаи (скажем, с разными тонкими настройками производительности), и подключать/переключать их одним движением, не переписывая основной конфиг, и не дублируя его.

В чём был ваш негативный опыт? У меня как раз негативный опыт от одного супер-конфига, в основном из-за его вырвиглазности и из-за копипасты, распространяющейся как рак.
Забыл добавить: при разбиении конфига на части и иерархии/композиции появляется возможность тонко настраивать безопасность, назначая разные права на разные секции.
Если я правильно понял, то все эти приемы направлены чтобы превратить мегагромадный конфиг во что-то удобоваримое…

Опыт как раз в том, что огромный конфиг пытались разбить на подконфиги и тем самым только усугубив проблему, вместо того, чтобы разобраться в чем причина. А проблема-то была в том, что система взяла на себя слишком много (получился этакий монолит-гигант) и плюс не было нормального деплоймента (конфиги ручками менялись для debug, test, sandbox и пр). Зато потом, когда все-таки разбили этот кусок гиганта на сервисы (мини или микро — это пожеланию), то монстрообразный конфиг пропал. Вместо него появились несколько мелких конфигов и для каждой среды свой конфиг.
Пример:
  • app.config — это конфигурация у разработкича
  • app.Release.config — это для всех остальных (тест, релиз и пр)

При деплое меням переменные в указанном конфиге. Например, при деплое тестировщикам имеем конфиг вида
<DatabaseSettings  ConnectionString=#{CurrentConnectionString}/>

В Octopus'e задаем значение для CurrentConnectionString
  • CurrentConnectionString = TestDatabase для тестирования
  • CurrentConnectionString = SandBox для предрелиза
  • CurrentConnectionString = Release для релиза.

Нажимаем на кнопку и деплоим куда надо. Переменная заменится на нужное значение автоматом.
Для меня «мегагаромадный» начинается уже с момента, когда кастомные настройки составляют менее 50% от веса всего конфига. Т.е. если конфиг занимает целый экран, и в нём всего одна настройка — коннект к базе данных, — то это кандидат на декомпозицию.

Возможно это связано с тем, что деплоймент у нас действительно отличается от вашего — мы не деплоим сервисы у себя, мы отдаём их заказчикам. И нам нужно обеспечить, чтобы наши конфиги были простыми и понятными, даже если у заказчика нет полноценного деплоймента и он выкатывает сервисы вручную, настраивая конфиги в Блокноте. К сожалению, почти все статьи заточены на единственный сценарий деплоймента сервисов у себя дома, где можно поставить сколь угодно продвинутый деплоймент.
Натройка конфига в блокноте — это шедеврально. Посоветуйте им хотя Nodepad++. А вообще, предложите им свои услуги по настройке.

Мы разворачиваем на разных компах, что-то у нас, что-то у заказчика, но всегда стараемся минимизировать ручное редактирование конфига. Плюс, конфигурационный файл должен содержать только ту информацию, которая нужна данному конкретному микросервису. Т.о. образом конфиг не разбухает, до неимоверных размеров.
Натройка конфига в блокноте — это шедеврально.

«Когда б вы знали, из какого сора...»

А вообще, предложите им свои услуги по настройке.

У нас есть solution team, но его тоже нужно содержать, обучать и, что хуже, постоянно переобучать, иначе они сами оказываются тормозом, а не источником дохода. Часто проще сделать простые конфиги и внятную документацию, понятную даже дураку.

всегда стараемся минимизировать ручное редактирование конфига.

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

конфигурационный файл должен содержать только ту информацию, которая нужна данному конкретному микросервису.

И я о том же. Но даже у самого микро- из микросервисов есть два типа конфигурационных данных: те, что диктуются выбранным фреймворком/библиотеками, настроены девелопером и не предполагаются к изменению кастомером (скажем, многочисленные настройки WCF со всем ворохом сервисов, байндингов, behaviours и т.п., или настройки dependency injector-а, или настройки доступа к разным путям), и те, которые предполагаются быть настроенными кастомером (в простейшем случае, секция «appSettings»). Причём настроек первого типа может быть огромное количество, и зависит оно не от девелопера. Девелопер рад бы иметь малеький конфигурационный файл с только теми настройками, которые он сам создал, но без разбиения конфига на части это практически нереально.
Никогда не понимал возни с Settings (или как там этот класс зовут) и вообще использование словарей для хранения/доступа к настройкам…

Пишется свой класс, расставляются аттрибуты, сериализуется/десериализуется чем вам больше нравится (json.net, XmlSerializer, ...) в куда вам необходимо (в MemoryStream -> byte[] -> db или же в FileStream)… Если конфигурация одна — синглтон, несколько — доступ через менеджер (Config.Active, Config.Load, etc.).

Все вышеперечисленное занимает буквально десяток строк (если не реализовывать что-то слишком навороченное, к примеру версионинг).

Имхо нет смысла в надстройке над чем-то дефолтовым и кривым. Плюс я не очень понял чем же лучше
SmtpSettings smtpSettings = (dynamic)ConfigurationManager.GetSection(«SmtpSettings»)
особенно доставляет dynamic и (на внимательность) «StmpSettings».
Как оказалось, некоторые любят app.config.
Я бы ещё понял web.config, но app.config мне категорически не нравится. Пользователя туда не пошлешь, там могут быть и программные настройки.
Это утилита работает также с web.config…

Пользователя и не надо туда слать. Для этого существуют другие места. App.Config предназначен для хранения настроек приложения и очень хорошо с этим справлется. Добавляем к нему build-сервер (например, TeamCity) и Octopus (мне он нравится) и мы получаем развертывание приложения в один клик/чекин для разных сред (Dev, Test, Sandbox, Release). Поэтому я не понимаю почему он вам так не нравится…
Как правило это работает на маленький проекта без автоматических билдов и развертываний или в самом начале разработки, когда приложение опять-таки маленькое. Когда приложение разрастается до несколько десятков сервисов, да еще и с подключением к сотне других сторонних программ… Вот тут и начинается, что надо бы такую настроечку, а вот тут при чтении конфигурации обязательно проверить, что переменные в Octopuse имеют правильные значения и получается, что изначальные 10 строчек кода в разных проектах превращабтся в 20 в одном, 50 в другом, 10 в третьем, причем от исходных может вообще ничего не остаться.
В .NET Core из коробки идет хорошее решение, которое и вложенность любую поддерживает, и принцип разделения ответственности, и встроенный в фреймворк DI container, и вообще. Вот тут можно глянуть, начиная с раздела «Using Options and configuration objects».
Неплохая утилита, но есть как минимум два «но».
1. Читает json.
2. Нужен .NET Core

Мне больше всего не нравится то, что конфигураци хранится в json файлах… Сейчас попробую объяснить почему.

При создание проекта в .NET автоматически будет создан app.config, в который студия поместит какие-то свои данные. Затем, например, мы добавим WCF сервис и в том же файле увидим его параметры. При этом, давайте предположим, что мы используем самописный ORM и поэтому строку подключения можем хранить где угодно и мы выбрали json… Как-то выглядит, что настройки расползлись по двум файлам вместо одного…
Не, .NET Core же как раз в json переполз настройками, в том числе и теми, что «автоматически будет создан...»
В последней версии, правда, вроде обратно переползли, я её еще не ковырял.

Это что касаемо пункта 1 вашего.
Что касаемо пункта 2 — ну это уже надо как данность воспринимать. Вероятнее всего, что в ближайшие пару лет все дружной толпой будем в сторону .NET Core двигаться.
Пока это будущее наступит пройдет время в течении которого надо выпускать продукты, да еще и поддерживать и развивать старые…

Но, все равно, спасибо за комментарий и ссылку на статью.
.NET Core поддерживает из коробки чтение JSON и XML конфигураций. По умолчанию просто выбирается JSON.
UFO just landed and posted this here
Интересный подход. По-сути, вы написали свою «библиотеку», подобной ConfigurationParser, но на Т4.

Кстати, а если в Т4 появляется ошибка, то как тестировать? Несколько раз посоздавали разные конфиги и на этом все?
На и в догонку, а как быть если адреса почты написаны через точку с запятой, а в системе они должны быть с листе, да еще и проверены с помощью RegEx?

И на последок, если это не коммерческая тайна, то можно взглянуть на код?
UFO just landed and posted this here
Спасибо за пример. Попробую поиграться…

А тем временем позвольте немного критики.

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

Также, если мы захотим добавить еще логики, то мы должны менять tt? Например, повторные адреса игнорировать. Можно, конечно, написать partial class и там реализовать метод со всякими проверками, а в tt его только дергать… По-моему как-то слишком сложно. Учитывая, что конфиг это та часть программы, которая очень редко меняется.

З.Ы. Еще раз спасибо за пример — буду разбираться.
UFO just landed and posted this here
А если создается что-то новое, более-менее часто, то как скопировав tt в новый проект, у вас уже все сгенерированно.

Новый проект — новый конфиг. А учитывая движение в сторону микросервисов, то получается, что и конфиги почти пыстые. Максимум 5-7 микросервисов (это мой опыт) и всякие частные настройки для конкретного сервиса. Т.о. повторное использование tt тут не подходит. Да и любое повторное использование кода не подходит. Конфиг почти каждый раз пишется с 0.

Надо сгенерировать mock config для тестов — поменяли пару строчек в шаблоне и он вам тестовые классы сделал.

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

В общем, пока я не увидел преимущество автоматической генерации конфигурационных классов с из заполнением. Скорей всего, тут кто к чему привык, ибо серебрянной пули не существует…

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

Вот после этого я спорить не буду ибо согласен. Если есть смысл и возможность, то надо генерить.
Sign up to leave a comment.

Articles