Ошибка не удается загрузить файл конфигурации xml

Обновлено: 07.07.2024

Все мучаюсь с загрузкой/выгрузкой данных в XML. Написал 2 процедурки:

Процедура ОсновныеДействияФормыДействие(Кнопка)
Режим = РежимДиалогаВыбораФайла.Сохранение;
ДиалогСохранения = Новый ДиалогВыбораФайла(Режим);
ДиалогСохранения.Фильтр= "XML|*.xml";
ДиалогСохранения.МножественныйВыбор=Ложь;
Если ДиалогСохранения.Выбрать() тогда
ЗаписьXML = Новый ЗаписьXML();
ЗаписьXML.ОткрытьФайл(ДиалогСохранения.ПолноеИмяФайла);
ЗаписьXML.ЗаписатьНачалоЭлемента("ROOT");
Выборка=Документы.ВыполнениеНаучноТехническихПрограмм.Выбрать();
Пока выборка.Следующий() цикл
ВыгружаемыйОбъект=ВЫборка.ПолучитьОбъект();
ЗаписатьXML(ЗаписьXML,ВыгружаемыйОбъект);
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.Закрыть();
КонецЕсли;
КонецПроцедуры

При выгрузке вроде файл создается нормально, без каких-либо ошибок, система ни на что не ругается, все необходимые данные попадают в файл. Когда же я пытаюсь загрузить из этого файла информацию, то все доходит до функции "ВозможностьЧтенияXML(ЧтениеXML)", которая выдает мне "Ложь" и соответственно ничего не происходит. Если эту функцию убрать, то при "ЗагружаемыйОбъект = ПрочитатьXML(ЧтениеXML)" происходит ошибка чтения данных. ПОЧЕМУ.

Колосально. По моему вы просто слишком высокого мнения об XML в нем нет встроенного интелекта:), вопользуйтесь лучше КД.
Да дело в том, что я этот механиз взял из учебника, лишь слегка подправив. И там написано .что мол все работает.
А что такое КД.
(3) Конвертация данных.
А кто тебе сказал что Докумен.Объект это сериализуемое значение? Оттуда и ошибка, так просто документы выгрузить не получиться, а ведь с ними еще и справочники надо грузить.

(4) Не, справочники там не нужны. предполагается перенос значений из аналогичных баз данных. т.е. справочники и регистры там буду одинаковые!

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

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

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

Когда вы имеете дело с файлом конфигурации .cf, вы изменяете его средствами 1С:Предприятия. Но, как вы знаете, в платформе существует и другой механизм, который позволяет вносить изменения в конфигурацию без использования платформы. Это механизм выгрузки конфигурации в файлы XML и загрузки из них.

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

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

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

Для того чтобы избежать подобных ситуаций и привести работу платформы к единому порядку, мы ввели ряд ограничений на загрузку конфигурации из файлов XML. Эти ограничения вводятся в трёх ситуациях.

Загрузка в конфигурацию, подключённую к хранилищу

01.jpg

  • Полная загрузка невозможна;
  • Частичная загрузка возможна только в том случае, когда все объекты, которые изменятся после загрузки, захвачены в хранилище.

Загрузка в конфигурацию, находящуюся на поддержке

03.jpg

  • Полная загрузка возможна только в том случае, когда все объекты конечной конфигурации являются редактируемыми;
  • Частичная загрузка возможна только в том случае, когда все объекты конечной конфигурации, которые изменятся после загрузки, являются редактируемыми.

Загрузка конфигурации, которая содержит настройки поддержки

02.jpg

Если XML выгрузка содержит настройки поддержки (файл ParentConfigurations.xml) то:

  • Полная загрузка невозможна;
  • Частичная загрузка невозможна в том случае, когда загружается корневой объект конфигурации (файл Configuration.xml) .

По поводу последней ситуации нужно дать небольшое объяснение. Снятие конфигурации с поддержки в интерактивном режиме, в конфигураторе, не приводит к удалению настроек поддержки из данных. Поэтому такая конфигурация, выгруженная в файлы XML, всё равно будет содержать файл с настройками поддержки. А это значит, что загрузить ее из XML теперь не удастся.

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

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

Нашей компанией, среди прочего, разработаны несколько сервисов (точнее — 12), работающих бэкендом наших систем. Каждый из сервисов представляет собой Windows-службу и выполняет свои специфические задачи.

Было решено перейти на конфигурирование через модный YAML. Какие проблемы при этом перед нами встали, и как мы их решили — в этой статье.

Что имеем

Как мы читаем конфигурацию из XML

Читаем XML стандартным и для большинства других проектов способом.

Подобная техника разделения настроек на интерфейсы позволяет удобно использовать их в дальнейшем через DI-контейнер.

Вся основная магия по хранению настроек на самом деле скрыта в PortableSettingsProvider (см. атрибут класса), а также в файле дизайнера AppSettings.Designer.cs:


Как видно, «за кулисами» скрыты все те свойства, которые мы добавляем в конфигурацию сервера, когда редактируем ее через дизайнер настроек в Visual Studio.

Наш класс PortableSettingsProvider, упомянутый выше, занимается непосредственно чтением XML-файла, а прочитанный результат уже используется в SettingsProvider для записи настроек в свойства AppSettings.

Пример XML-конфига, который мы читаем (большая часть настроек скрыта из соображений безопасности):

Какие YAML-файлы хотелось бы читать

Проблемы перехода

Во-первых, конфиги в XML — «плоские», а в YAML — нет (поддерживаются секции и подсекции). Это хорошо видно в приведенных выше примерах. При использовании XML мы решали проблему плоских настроек вводом собственных парсеров, которые умеют строки определенного вида преобразовывать в наши более сложные классы. Пример такой сложной строки:


Заниматься такими преобразованиями при работе с YAML совсем не хочется. Но при этом мы ограничены существующей «плоской» структурой класса AppSettings: все свойства настроек в нем свалены в одну кучу.

Во-вторых, конфиги наших серверов — это не статичный монолит, мы их время от времени меняем прямо по ходу работы сервера, т.е. эти изменения надо уметь отлавливать «на лету», в рантайме. Для этого в XML-реализации мы наследуем наш AppSettings от INotifyPropertyChanged (на самом деле от него унаследован каждый интерфейс, который реализует AppSettings) и подписываемся на события обновления свойств настроек. Работает такой подход от того, что базовый класс System.Configuration.ApplicationSettingsBase «из коробки» реализует INotifyPropertyChanged. Подобное поведение надо сохранить и после перехода на YAML.

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

И еще одна проблема — доступ к настройкам идет не только через интерфейсы, но и прямым обращением к AppSettings.Default. Напомню как он объявлен в закулисном AppSettings.Designer.cs:


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

Решение

Инструментарий

Непосредственно для чтения YAML решили использовать готовые библиотеки, доступные через NuGet:

Переход к YAML

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

Подготовка YML-файла

Сперва требуется подготовить сам YAML-файл. Назовем его именем проекта (полезно для будущих интеграционных тестов, которые должны уметь работать с разными серверами и различать их конфиги между собой), положим файлик прямо в корне проекта, рядом с AppSettings:


В самом YML-файле для начала сохраним «плоскую» структуру:

Наполнение AppSettings свойствами настроек

Перенесем все свойства из AppSettings.Designer.cs в AppSettings.cs, попутно избавляясь от ставших лишними атрибутов дизайнера и самого кода в get/set-частях.


Удалим полностью AppSettings.Designer.cs за ненадобностью. Теперь, кстати говоря, можно полностью избавиться от секции userSettings в файле app.config, если он есть в проекте — там хранятся те самые дефолтные настройки, которые мы прописываем через дизайнер настроек.
Идем дальше.

Контроль изменения настроек «на лету»

Так как нам надо уметь ловить обновления наших настроек в рантайме, то требуется реализовать INotifyPropertyChanged в нашем AppSettings. Базового System.Configuration.ApplicationSettingsBase больше нет, соответственно, рассчитывать на какую-то магию не приходится.

Можно реализовать «в лоб»: добавив имплементацию метода, выкидывающего нужное событие, и вызывая его в сеттере каждого свойства. Но это лишние строки кода, которые к тому же надо будет копировать по всем сервисам.

Поступим красивее — введем вспомогательный базовый класс AutoNotifier, который фактически делает то же самое, но «за кулисами», прямо как делал ранее System.Configuration.ApplicationSettingsBase:


Здесь атрибут [CallerMemberName] позволяет автоматически получать название свойства вызывающего объекта, т.е. AppSettings.

Теперь мы можем занаследовать наш AppSettings от этого базового класса AutoNotifier, а далее каждое свойство несколько видоизменить:


С таким подходом наши классы AppSettings, даже содержащие довольно много настроек, выглядят компактно, и при этом полноценно реализовывают INotifyPropertyChanged.

Да, я знаю, что можно было бы ввести чуть больше магии, используя, например, Castle.DynamicProxy.IInterceptor, перехватывая изменения необходимых свойств и рейзя там события. Но такое решение показалось мне слишком перегруженным.

Чтение настроек из YAML-файла

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

Секции в YAML-файле

Ответим на этот вопрос попозже, а сейчас вернемся к требованию хранения иерархически структурированных настроек в YML-файле.

В принципе, реализовать это достаточно просто. Сначала в самом YML-файле введем требующуюся нам структуру:


А теперь пойдем в AppSettings и научим его разделять наши свойства по секциям. Как-то так:


Как видно, мы добавили прямо в AppSettings словарик, где ключами выступают типы интерфейсов, которые реализовывает класс AppSettings, а значениями — заголовки соответствующих секций. Теперь мы можем сопоставить иерархию в YML-файле с иерархией свойств в AppSettings (хотя и не глубже, чем один уровень вложенности, но в нашем случае этого было достаточно).

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

Если не нужна иерархия в настройках?

В принципе странный кейс, но у нас такое было именно на первом этапе, когда просто переходили от XML к YAML, без использования преимуществ YAML.

В этом случае весь этот список секций можно не хранить, да и ServerConfigurationProvider будет значительно проще (рассматривается далее).

Но важный момент — если мы решим оставить плоскую иерархию, то мы как раз-таки сможем выполнить требование о сохранении возможности обращаться к настройкам через AppSettings.Default. Для этого добавим вот такой простой публичный конструктор в AppSettings:


Теперь мы везде можем продолжать обращаться к классу с настройками через AppSettings.Default (при условии, что настройки уже были ранее прочитаны через IConfigurationRoot в ServerConfigurationProvider и, соответственно, AppSettings был проинстанциирован).

Если же плоская иерархия недопустима, то, как ни крути, придется избавляться от AppSettings.Default везде по коду и работать с настройками только через интерфейсы (что в принципе хорошо). Почему так — станет ясно дальше.

ServerConfigurationProvider

Специальный класс ServerConfigurationProvider, упомянутый ранее, занимается той самой магией, которая позволяет полноценно работать с новым иерархическим YAML-конфигом при наличии лишь плоского AppSettings.

Если не терпится — вот он.

ServerConfigurationProvider параметризирован по классу настроек AppSettings:

Это, как нетрудно догадаться, позволяет применять его сразу во всех сервисах.


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

Казалось бы — зачем читать каждую секцию, почему бы не читать только те, которые в кэше (т.е. востребованные)? Потому что в чтении секции у нас реализована проверка на корректность конфигурации. И в случае некорректных настроек выкидываются соответствующие алерты, логируются проблемы. О проблемах в изменениях конфига лучше узнавать как можно скорее, от того читаем все секции сразу же.

Обновление старых значений в кэше новыми значениями достаточно тривиально:


А вот с чтением секций не всё так просто:


Тут мы, прежде всего, читаем саму секцию, используя стандартный IConfigurationRoot.GetSection. Затем как раз-таки проверяем корректность прочитанной секции.

Далее прочитанную секцию биндим к типу наших сеттингов: section.GetТут мы сталкиваемся с особенностью YAML-парсера — он не различает пустую секцию (без параметров, т.е. отсутствующую) от секции, в которой все параметры пустые.

Вот подобный кейс:


Тут в секции VirtualFeed есть параметр Names с пустым списком значений, но YAML-парсер, к сожалению, скажет, что секция VirtualFeed вообще полностью пустая. Печально.

Ну и напоследок в этом методе реализовано немного уличной магии для поддержки IEnumerable-свойств в настройках. Добиться нормального чтения списков «из коробки» у нас не получилось.


Как видно, мы находим все свойства, тип которых унаследован от IEnumerable и присваиваем в них значения из фиктивной «секции», именованной также как и интересующая нас настройка. Но перед этим не забываем проверить: а есть ли переопределенное значение этого перечислимого свойства во втором конфиг-файле? Если есть — то только его и берем, а настройки, прочитанные из базового конфиг-файла, зачищаем. Если этого не делать, то оба свойства (из базового файла и из переопределенного) будут автоматически слиты в один массив на уровне IConfigurationSection, причем ключами для объединения послужат индексы массивов. Получится какая-то мешанина вместо нормального переопределенного значения.

Показанный метод ReadSection в итоге используется и в главном методе класса: FindSection.


В принципе, тут и становится ясно, почему при поддержке секций мы никак не можем поддерживать AppSettings.Default: каждое обращение к новой (ранее непрочитанной) секции настроек через FindSection на самом деле будет выдавать нам новый инстанс класса AppSettings, хоть и прикастенный к нужному интерфейсу, и, соответственно, если бы мы использовали AppSettings.Default, то он бы переопределялся при каждом чтении новой секции и содержал бы означенными лишь те настройки, которые относятся к последней прочитанной секции (остальные имели бы дефолтные значения — NULL и 0).

Проверка корректности настроек в секции реализована следующим образом:


Тут прежде всего извлекаются все публичные свойства интересующего нас интерфейса (читай — секции настроек). И по каждому из этих свойств ищется соответствие в прочитанных настройках: если соответствие не найдено, то логируется соответствующая проблема, ведь это означает, что в файле конфига не хватает какой-то настройки. В конце дополнительно проверяется, остались ли какие-либо из прочитанных настроек несопоставленными с интерфейсом. Если такие есть, то опять же логируется проблема, т.к. это означает, что в файле конфига обнаружены свойства, не описанные в интерфейсе, чего тоже не должно быть в нормальной ситуации.

Возникает вопрос — а откуда у нас требование, что в прочитанном файле все настройки должны соответствовать имеющимся в интерфейсе по принципу «один-к-одному»? Дело в том, что на самом деле, как упоминалось выше, на этот момент прочитан не один файл, а сразу два — один с дефолтными настройками, а другой с переопределенными, и оба они смержены. Соответственно, на самом деле мы смотрим настройки не из одного файла, а полные. И в этом случае, конечно же, их набор должен соответствовать ожидаемым настройкам один к одному.

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

Получение настроек сервера

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


Первый метод этого интерфейса — FindSection — позволяет обращаться к интересующей секции настроек. Как-то так:


Зачем нужны второй и третий метод — объясню далее.

У нас в проекте в качестве IoC-контейнера используется Castle Windsor. Именно он поставляет в том числе и интерфейсы настроек сервера. Соответственно, эти интерфейсы требуется в нем зарегистрировать.

С этой целью написан простой Extension-класс, позволяющий упростить эту процедуру, чтобы не писать регистрацию всего набора интерфейсов в каждом сервере:


Первый метод позволяет зарегистрировать все секции настроек (для этого и нужно свойство AllSections в интерфейсе IServerConfigurationProvider).

А второй метод используется в первом, и он автоматически читает заданную секцию настроек с использованием нашего ServerConfigurationProvider, тем самым записывает ее сразу в кэш ServerConfigurationProvider и регистрирует в Windsor.
Именно здесь и используется второй, непараметризированный, метод FindSection из IServerConfigurationProvider.

Остаётся лишь позвать в коде регистрации контейнера Windsor наш Extension-метод:

Вывод

Что получилось

Представленным способом удалось достаточно безболезненно перевести все настройки наших серверов с XML на YAML, при этом произведя минимум изменений по существующему коду серверов.

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

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

Удалось сохранить возможность отлавливания изменений настроек в веб-мордах наших серверов «на лету». Более того, бонусом появилась возможность также налету отлавливать изменения в самом YAML-файле (ранее приходилось перезагружать сервер при любых изменений в конфиг-файлах).

Мы сохранили возможность мержа двух файлов конфигов — дефолтных и переопределенных настроек, причем сделали это с использованием сторонних решений «из коробки».

Что не очень получилось

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

Таким образом, у меня есть установщик wix, который отлично работает при новой установке, но когда я обновляю предыдущую версию до этой новой версии, где я меняю значение конфигурации, я получаю следующую ошибку, и установщик откатывается назад:

ExecXmlFile: Configuring Xml File: ExecXmlFile: Error 0x8007006e: failed to load XML file: Error 25531. Failed to open XML file , system error: -2147024786 MSI (s) (2C:5C) [14:45:21:281]: Product: MyProduct -- Error 25531. Failed to open XML file , system error: -2147024786 CustomAction ExecXmlFile returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox) Action ended 14:45:21: InstallExecute. Return value 3.

Вот мой файл xml, где я хочу изменить его с 4.5 на 4.6.2

И вот мое соответствующее изменение wix

Может ли это быть проблема, связанная с моим путем элемента? Обратите внимание, что <startup><suuportedRuntime> находится в той же строке. Не уверен, что это имеет значение, но любая помощь будет очень кстати.

Нашел это наблюдение, может быть, я ошибаюсь, но не могли бы вы сказать мне, каким будет ElementPath из sku ниже, чтобы изменить его значение, если путь xml будет таким, где теги startup и supportedRuntime находятся в одной строке:

2 ответа

Следующая ошибка достигается при установке Vmware при установке на x64 Windows 7: vmware mcisocket64.msi не удалось

Использование Installshield 2010 с базовым проектом MSI. У меня есть несколько конфигурационных файлов, которые должны быть заменены во время обновления. Поскольку они являются конфигурационными файлами, у них нет версии. Я считаю, что они не заменяются во время установки, потому что во время.

Я не использовал этот подход, но есть три вещи, которые выделяются для меня:

Ошибка 0x8007006e (также -2147024786), по-видимому, сводится к ошибке 110:

Система не может открыть указанное устройство или файл

Первая строка, которую вы цитируете, гласит:

ExecXmlFile: Настройка файла Xml:

но не перечисляет имя файла после двоеточия. Вторая строка аналогична.

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

Не удалось открыть файл XML , системная ошибка: -2147024786

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

Если это не поможет, примеры, подобные приведенным в этом учебнике WiX, действительно используют форматированные строки в файле, но ссылаются на пути, такие как [INSTALLDIR]settings.xml . Возможно, вам стоит попробовать [INSTALLDIR]config.xml или что-то подобное.

(Или, по крайней мере, введите какое-то жестко закодированное значение файла и посмотрите, подтвердят ли строки журнала мои подозрения.)

Читайте также: