SysConfigFile – это класс, который позволяет получать значения параметров из конфигурационных xml-файлов в Microsoft Dynamics AX 2009, Microsoft Dynamics AX 2012 и Axapta 4.0.
Как правило, на проектах используется несколько экземпляров (инстансов) Аксапты - рабочая, тестовая, для разработки и т.д. Причем зачастую рабочий экземпляр полностью копируется в один из тестовых экземпляров (и база, и приложение). Кроме того, зачастую создается экземпляр-зеркало рабочего экземпляра. Зеркало используется для BI-отчетов.
Получается, что в разных экземплярах и база, и приложение может совпадать. Но при этом все равно нужно обеспечить различную работу этих экземпляров. Например, только рабочий экземпляр может рассылать письма, тестовым экземплярам запрещено запрашивать внешние системы или отвечать на запросы внешних систем. А зеркало, например, не должно ничего разносить.
Такие параметры экземпляра (инстанса) удобно хранить централизовано во внешнем файле в отдельном каталоге на сервере.
Класс SysConfigFile
предполагает:
- параметры хранятся централизовано на сервере в небольших xml-файлах с расширением
.config
или.config.xml
- имя config-файла по умолчанию
Axapta
(имя можно задать вnew
или в конструкторе) - каталог по умолчанию
%Appl%\Config
(каталог можно задать вnew
или в конструкторе, изменить можно в parm-методе) - параметр хранится как xml-элемент, а значение параметра хранится как текстовое значение xml-элемента. Например,
<name>Microsoft Dynamics AX</name>
- пустые xml-элементы трактуются как
true
методомgetBoolean
и как пустое значение методомget
. См. Пример config-файла - валидность config-файл можно проверить при помощи xsd-схемы, которую можно централизовано хранить в ресурсах Аксапты или в xsd-файле рядом с config-файлом. Схему также можно явно указать в конструкторе
В простейшем случае программисту достаточно написать название параметра, значение которого он хочет получить. Но для сложных случаев в качестве названия параметра можно использовать XPath-выражения.
Формат XML сам по себе не ограничивает уникальность элементов внутри, поэтому класс SysConfigFile
позволяет получать как одно найденное значение (первое), так и список всех найденных значений. Кроме того, метод getBy
позволяет получить значения в нужном порядке, а не в порядке размещения в xml-файле. См. также раздел Направления для развития.
Класс SysConfigFile
будет работать даже в том случае, если config-файл отсутствует. В этом случае get-методы будут возвращать значения по умолчанию. Однако класс позволят программисту явно сказать, что параметр обязательно должен присутствовать в файле при помощи assert
-, ensure
- и check
- методов. Обратите внимание, что статический метод ::Value
бросает исключение, а статический метод ::ValueOrDefault
возвращает значение по умолчанию, если параметра нет в конфигурационном файле. См. Устойчивость.
Для работы с XML-файлами класс SysConfigFile
пока использует XmlDocument
. XmlDocument
позволяет искать элементы внутри этого файла при помощи XPath, но загружает xml-файл целиком в память. Поэтому следите за размером config-файла, не позволяйте этому файлу стать слишком большим. См. Несколько разных config-файлов и Кэширование.
<?xml version="1.0" encoding="utf-8"?>
<config>
<id>PROD</id>
<name>Microsoft Dynamics AX</name>
<reportTemplateFolder>\\dax\template\</reportTemplateFolder>
<sender>Axapta</sender>
<sender email="note">Notification server</sender>
<sender email="mail">Company name</sender>
<AOS>
<batch serverId="01@AOS" />
<batch serverId="01@RESERV">true</batch>
</AOS>
</config>
Предположим, что в каталоге %Appl%\Config\
содержится файл Axapta.config
с текстом из примера выше. Тогда код будет работать так (см. тестовый метод SysConfigFileTest.testExample
):
SysConfigFile config = SysConfigFile::construct();
config.get('id'); // 'PROD'
config.get('notFound'); // ''
config.ensureExists('notFound').get(); // бросит исключение, поскольку
// в данном примере параметр notFound отсутствует
config.getBoolean('AOS/batch'); // true
config.getBoolean('notFound'); // false
config.getAll('sender'); // ['Axapta','Notification server','Company name']
config.getAll('AOS/batch/@serverId'); // ['01@AOS','01@RESERV']
config.getAll('notFound'); // connull()
config.getBy(['sender[@email="mail"]','sender[not(@email)]']); // 'Company name'
config.getBy(['sender[@email="other"]','sender[not(@email)]']); // 'Axapta'
Если нужно получить только одно значение, то можно написать и так:
SysConfigFile::value('name'); // 'Microsoft Dynamics AX'
Два статических метода Value
и ValueOrDefault
по разному работают с отсутствующими в config-файле параметрами:
SysConfigFile::valueOrDefault('notFound','DefaultValue'); // 'DefaultValue'
SysConfigFile::value('notFound'); // бросит исключение
Чтобы проверить семантику config-файла, программисту достаточно вызвать метод ensureFileValid
один раз:
SysConfigFile config = SysConfigFile::construct().ensureFileValid();
config.get('id'); // 'PROD'
config.getBoolean('AOS/batch'); // true
Когда параметров становится очень много, то удобно разместить параметры в разных config-файлах. Например, отдельный config на каждый модуль. Размещение параметров в разных конфигах с одной стороны снижает вероятность конфликтов при обновлении параметров, с другой стороны делает чтение и кэширование config-файла более эффективным.
Если параметры хранятся в отдельном файле PrintSrv.config
, то достаточно в конструкторе или в статическом методе написать:
SysConfigFile::construct('PrintSrv').get('id');
SysConfigFile::value('id', 'PrintSrv');
При желании можно указать и каталог, где хранится конфиг (см. тестирующий класс SysConfigFileTest
). По умолчанию класс использует каталог Config
внутри каталога приложения. К каталогу приложения %Appl%
гарантировано имеют доступ все AOS кластера. Если админы не запретили специально доступ к каталогам внутри %Appl%
, то к подкаталогу %Appl%\Config
доступ скорее всего будет.
Начиная с версии 2.1 класс SysConfigFile
пытается найти конфигурационный файл в файлах с расширением .config
, .config.xml
, .xml
,
а также в файле с именем конфигурации без дополнительного расширения.
Если в Config-каталоге присутствует несколько конфигурационных файлов с одинаковым именем, но с разными расширениями,
то будет возвращен первый существующий в указанном порядке: .config
, .config.xml
, .xml
, пустое расширение.
Если ни один конфигурационный файл не найден, то, как и прежде, используется имя с расширением .config
.
Вариант "без дополнительного расширения" позволяет программисту указать в наименовании конфига то имя файла, какое ему хочется, и не парится о соглашениях.
См. метод filePath()
Класс SysConfigFile
использует аксаптовские классы для работы с xml. Эти классы всегда выполняют проверку xml синтаксиса при загрузке текста - так называемую проверку well formed xml. См.также W3 well-formed, статья
Класс SysConfigFile
может выполнить валидацию config-файла по xsd-схеме: https://www.w3.org/XML/Schema
Чтобы выполнить валидацию, программист должен явно вызвать хотя бы один из методов: ensureFileValid
, assertFileValid
или checkFileValid
.
Если методы не вызваны, то класс выполняет только проверку синтаксиса при чтении config-файла.
Xsd-схема может хранится централизовано в ресурсах AOT или в xsd-файле рядом с config-файлом, а может задаваться как параметр при создании класса. Класс определяет название ресурса в методе schemaResourceName
, а название и путь xsd-файла в методе schemaPath
.
Аксаптовские xml-классы не выполняет валидацию по DTD (выполняется только проверка синтаксиса DTD как подмножества синтаксиса xml).
С некоторого времени Microsoft считает работу с DTD опасной: https://msdn.microsoft.com/en-us/magazine/ee335713.aspx.
Во-первых, из-за "xml bomb" (гуглите).
Во-вторых, потому что способ проверки задает создатель файла внутри этого xml-файла. В далекие 2000е в этом не видели проблему, но уже в 2010е стали считать, что доверять создателю файла слишком наивно и опасно. Поэтому, в аксаптовских xml-классах есть метод prohibitDtd. По умолчанию он возвращает true
.
У меня не получилось включить процессинг DTD
в аксапте даже передав false
в метод prohibitDtd
. Дайте знать, если у вас получится.
SysConfigFile
– серверный класс (параметр RunOn = Server в свойствах AOT). Так сделано для того, чтобы:
- класс гарантировано читал config-файл с сервера
- не нужно было расшаривать каталог с конфигами в сети (в том числе, VPN)
- не путаться с путями к файлами (на клиенте, локально на сервере, сетевые пути)
- не беспокоиться о правах доступа пользователей и AOSов к конфигам (особенно в кластере)
Но это значит, что вызов этого класса из Аксаптовского клиента приведет к потенциально длительному клиент-серверному взаимодействию. Постарайтесь работать с этим классом из серверных объектов и методов.
Чтобы уменьшить накладные расходы на передачу данных между клиентом и сервером, метод getAll
возвращает container
, а не составной объект. По этой же причине метод getBy
принимает container
как параметр.
Если же Аксаптовскому клиенту нужно получить значения большого количества параметров, то создайте дополнительный специализированный метод, который упакует передаваемые данные в container
. См. также Направления для развития.
Класс кэширует и найденные, и отсутствующие значения в глобальном кэше. Поэтому повторный get
возвратит значение очень быстро, без дополнительного чтения config-файла.
Поскольку SysConfigFile
– это серверный класс, то он будет использовать серверный глобальный кэш. В ax4 и ax2009 в методе globalCache
я предлагаю использовать appl.globalCache
. В ax2012 я предлагаю использовать classFactory.globalObjectCache
. На своем проекте вы можете изменить код и использовать кэши из appl
, infolog
или classFactory
.
Класс SysConfigFile
может работать в startup
процедурах, когда глобальные переменные appl
, info
, classFactory
еще не инициализированы. На этом этапе класс создает свой локальный кэш, который будет жить пока живет объект SysConfigFile
.
Кроме того, каждый объект класса SysConfigFile
физически читает config-файл только один раз при необходимости, а потом хранит xmlRoot
пока живет. Поэтому, если вам нужно получить несколько значений из конфига, старайтесь вызывать методы get
, а не статические методы ::value
. Это минимизирует число физических чтений config-файла.
Класс SysConfigFile
создает неизменяемые (Immutable) объекты – все значимые параметры определяются при создании объекта, хранятся в переменных объекта и не изменяются пока объект "живет".
Некоторые внутренние переменные класса вычисляют свое значение при необходимости (Lazy initialization) и не меняют значение после вычисления.
В частности, класс читает содержимое config-файла и xsd-схемы один раз при первом обращении к файлу и схеме, а в дальнейшем переиспользует уже прочитанные файлы. Соответственно значения параметров читаются из config-файла только один раз и хранятся в глобальном кэше. SysConfigFile
возвращает значение из кэша, если оно там есть. В противном случае читает значение из xml-файла.
Программист может получить исходный текст конфига и исходный текст xsd-схемы при помощи методов file()
и schema()
.
Кроме того, класс содержит приватные методы xmlDocument()
, xmlRoot()
и xmlSchema()
, которые возвращают ссылки на объекты в памяти. Если вы хотите получить непосредственный доступ к xml-содержимому, то сделайте эти методы публичными и работайте с содержимым xml.
Однако помните, что SysConfigFile
– серверный. Не обращайтесь к методам xmlDocument()
и xmlSchema()
из клиента – клиент-серверная передача отдельных объектов сопровождается огромными накладными расходами.
Также учитывайте, что в следующих версиях класс SysConfigFile
может использовать другие способы работы с xml.
Класс SysConfigFile
разработан так, чтобы возвращать осмысленный результат в ситуациях, которые могут трактоваться как ошибки. Класс минимизирует вероятность возникновения исключений и в явном виде бросает исключения только в ensure
- методах. Однако само ядро Аксапты может выбросить исключение (exception) во время парсинга xml в методах XmlDocument::newFile()
и XmlSchema::newFile()
если:
- xml-файл или xsd-схема содержит незакрытые теги, неправильно вложенные теги,
"
,&
,<
,>
или другие специальные символы - xsd-схема содержит непредусмотренные стандартом элементы, атрибуты или значения
Обработка исключений в классических Аксаптах имеет особенность: catch блоки внутри транзакции не обрабатываются, Аксапта выпрыгивает за самый внешний transaction-блок и ищет catch уже там (см. раздел Exception handling inside transaction
в документации). Поэтому обработка исключений становится не надежной, если код может выполняться внутри транзакции.
Поэтому если config-файлы содержат синтаксически правильный xml, то исключения могут возникнуть только после вызова ensure
-, assert
- методов и статического метода ::Value
, а остальные методы вернут более-менее осмысленный результат.
В версии 2.0 класс SysConfigFile
использует только стандартный функционал Аксапты и несколько .net
методов, которые обернуты в необходимые CodePermission
. Поэтому вам не придется ни импортировать дополнительные проекты, ни устанавливать дополнительные CodePermissions
в свой код.
Класс SysConfigFile
использует:
- System.IO.File::Exists
- System.String::Copy
- System.String.Trim
Тестовый класс SysConfigFileTest
использует:
- System.IO.Path::GetTempPath
- System.IO.Directory::CreateDirectory
- System.IO.Directory::Delete
- использовать
.net
классы для работы с xml вместо Аксаптовских (проверить производительность, потребление/утечку памяти и работу.net
GC в связке с GC-Аксапты разных версий)- читать из зашифрованных разделов xml, если получится перейти на
.net
xml
- читать из зашифрованных разделов xml, если получится перейти на
- дальнейшая минимизация трафика между клиентом и сервером Аксапты: принять несколько параметров в контейнере и возвратить контейнер значений (развить метод
getAll
)- возможно, стоит вернуть упакованный map
- добавить
getDate
,getTime
,getDateTime
, которые будут умно конвертировать строку формата, использующийся xsd-схемамиxs:date
,xs:time
,xs:dateTime
, в Аксаптовские объекты с типомDate
,Time
иDateTime
соответственно- Насколько я понимаю, это один из вариантов формата ISO 8601:
yyyy-mm-ddThh:mm
- Подумать в каком методе и как правильно учесть часовой пояс
yyyy-mm-ddThh:mmZ###
(а как для ax4?) - Подумать стоит ли делать три метода или один (а как для ax4?)
- см. также
Global::xmlstring
- Насколько я понимаю, это один из вариантов формата ISO 8601:
- добавить работу с
namespace
в config-файле (нужно ли?) - сделать класс, который сможет читать параметры из реестра, а не из файла. В этом случае администраторы смогут раскатывать значения по серверам через групповые политики
Эта проблема проявляется очень редко только в ax4 и ax2009 и не особо мешает использовать данный (в целом полезный) класс. Используемый в ax2012 класс SysGlobalObjectCache
работает с регистрозависимыми ключами и не имеет данной проблемы. Поэтому я решил опубликовать проект с этой проблемой. Дайте знать, если у вас есть предложения по данному пункту.
Прежде всего, привыкшие к Аксапте программисты постоянно забывают о регистрозависимости XML. И постоянно делают попытки получить name
вперемешку с Name
. А это разные элементы с точки зрения XML.
На регистрозависимый XML накладывается Аксаптовский НЕ зависимый от регистра кэш SysGlobalCache
. Поэтому с этим кэшем следующие строки кода будут вести себя по разному (внезапно!):
SysConfigFile::value('Name'); // бросит исключение, поскольку xPath запрос не найдет такого раздела в XML-файле
SysConfigFile::value('name');
SysConfigFile::value('name'); // 'Microsoft Dynamics AX'
SysConfigFile::value('Name'); // 'Microsoft Dynamics AX', а не исключение! поскольку параметр успешно найден в кэше
Дайте знать, если у вас есть предложения, которые не снижают ни производительность, ни уровень гарантий в основном сценарии работы.
SysConfigFile
– серверный класс. Это фича. Но иногда нужно работать с config-файлами на клиенте (POS-терминалы, PinPad, ключи безопасности и прочее оборудование, сертификаты и софт, привязанный к пользовательскому компьютеру). Конечно, с клиентскими config-файлами можно работать как с обычными xml-файлами. Если же хочется получить сервис SysConfigFile
на клиенте, то можно:
- создать дубль
SysConfigFileClient
- изменить свойство
RunOn
- изменить
WinAPIServer
наWinAPI
- изменить метод
parmDefaultDirectory
или явно указывать каталог перед запросом значений из конфига
- Спасибо Сергею Чечкину за похоже единственно возможный в аксаптовских классах способ верификации xml по схеме
- Спасибо всем за конструктивное обсуждение проекта
- Названия классов и методов, иерархия и порядок вызовов в наборе классов будут по возможности сохраняться, но это не гарантируется - в будущих версиях SysConfigFile все может измениться.
- Код в xpp-файлах конвертирован из xpo только для удобства использования человеком. Оригиналом является код в xpo-проектах, отличия между xpo и xpp всегда трактуются в пользу текста из xpo-проектов.
- Проект выложен "как есть" под лицензией MIT: вы можете использовать данный код как угодно безо всяких отчислений, автор не дает никаких гарантий и не несет ответственности за возможный эффект от использования кода на проектах.
- проект сознательно сделан для классических версий Аксапты
- в проекте сознательно не используется xmldocs
- README и комментарии сознательно сделаны на русском языке
- ошибки в ensure методах записаны простой строкой на русском языке и не используют меток
Буду признателен за ваши замечания, предложения и советы по проекту как в разделе Issues, так и в виде письма на адрес mazzy@mazzy.ru
Мазуркин Сергей (mazzy)