Интеграция систем на платформе «1С:Предприятие» со сторонними приложениями (например, консольными утилитами на C#/.NET) с использованием SOAP-веб-сервисов — мощный, но довольно капризный инструмент. Часто разработчики сталкиваются с труднообъяснимыми ошибками на принимающей стороне: от банального отсутствия настроек конечной точки в конфигурационном файле клиента до сложных исключений парсера XML, таких как XmlException при попытке прочитать структуру XDTO.
В этой статье мы подробно разберем, почему возникают подобные проблемы, как правильно настроить типы возвращаемых значений, как организовать передачу массивов (списков) объектов через XDTO и рассмотрим более надежную альтернативу — использование JSON-сериализации.
Рассмотрим ситуацию: веб-сервис 1С возвращает объект XDTO (например, информацию о контрагенте), но принимающее приложение на C# выдает ошибку:
«XmlException: Непредусмотренный тип узла Element. Метод ReadElementString может быть вызван только для элементов с простым или пустым содержимым»
Или сообщает, что «Обязательное возвращаемое значение не задано».
Выясним причину этого поведения. Данная ошибка указывает на жесткую рассинхронизацию между WSDL-схемой, которую ожидает клиент, и фактическим XML-пакетом, который формирует 1С. Когда в свойствах операции веб-сервиса в 1С в поле «Тип возвращаемого значения» по ошибке выбирается простой тип (например, string из пространства имен http://www.w3.org/2001/XMLSchema), клиентский прокси-класс на C# генерируется с расчетом на получение обычной строки. Для этого он вызывает внутренний метод ReadElementString().
Однако на уровне кода в 1С разработчик создает сложную структуру XDTO с вложенными свойствами (такими как name, inn и др.) и возвращает её из функции:
Функция ПолучитьДанныеКонтрагентаПоИНН(ИНН)
URIПространстваИмен = "http://localhost/VV/ws/Kontragent";
ПакетCML = ФабрикаXDTO.Пакеты.Получить(URIПространстваИмен);
КонтрагентТип = ПакетCML.Получить("Kontragent");
КонтрагентXDTO = ФабрикаXDTO.Создать(КонтрагентТип);
// Заполнение реквизитов объекта XDTO
КонтрагентXDTO.name = "ООО Рога и Копыта";
Возврат КонтрагентXDTO; // Возвращается сложный тип!
КонецФункции
В результате SOAP-ответ веб-сервиса содержит вложенные теги XML (узел Element), а C#-клиент ожидает плоский текст. Парсер .NET натыкается на открывающий тег и выбрасывает исключение.
Если же разработчик пытается исправить ситуацию «в лоб» и возвращает только строку через Возврат КонтрагентXDTO.name;, это работает, но лишает интеграцию всякого смысла, ведь цель — передать всю структуру данных контрагента (наименование, долг, кредитную линию и т.д.).
Проанализируем шаги, которые необходимо выполнить для того, чтобы успешно передать сложный объект XDTO целиком:
GetKontragent) и найдем свойство «Тип возвращаемого значения». Вместо стандартного типа string необходимо выбрать ваш собственный тип из пакета XDTO. Он должен соответствовать типу создаваемого объекта (например, Kontragent из пространства имен http://localhost/VV/ws/Kontragent).Также не забудем про ошибку инициализации клиента WCF из Сообщения 0. Если ваше консольное приложение не находит файл конфигурации, инициализируйте подключение программно, не полагаясь на App.config:
var binding = new BasicHttpBinding();
var endpoint = new EndpointAddress("http://11.1.0.1/VV/ws/IXIOffice.1cws");
var client = new IXIOfficePortTypeClient(binding, endpoint);
Рассмотрим ситуацию, когда по ИНН контрагента нам требуется вернуть не одного контрагента, а целый список его заказов (как в Сообщении 26). Прямого понятия «массив» в XDTO нет. Списки реализуются с помощью специальных настроек свойств объектов.
Разберем по шагам настройку XDTO-пакета для списка заказов:
Order (Заказ) со свойствами: Number (строка), Sum (число) и т.д.OrdersList (СписокЗаказов).OrdersList добавим свойство Order. В поле «Тип» укажем созданный ранее тип объекта Order.Order значение «Верхняя граница» (Upper Bound) в значение -1 (это эквивалентно значению unbounded в XML Schema, что означает неограниченное количество элементов — то есть массив).Теперь посмотрим на пример кода в 1С, который выбирает данные запросом, заполняет этот список и возвращает его через веб-сервис:
Функция ПолучитьСписокЗаказов(ИНН) Экспорт
URIПространства = "http://localhost/VV/ws/Kontragent";
Пакет = ФабрикаXDTO.Пакеты.Получить(URIПространства);
// Создаем корневой объект-контейнер для списка
ТипСписка = Пакет.Получить("OrdersList");
СписокXDTO = ФабрикаXDTO.Создать(ТипСписка);
// Получаем тип единичного элемента списка
ТипЗаказа = Пакет.Получить("Order");
// Формируем и выполняем запрос к БД
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказПокупателя.Номер КАК Номер,
| ЗаказПокупателя.СуммаДокумента КАК Сумма
|ИЗ
| Документ.ЗаказПокупателя КАК ЗаказПокупателя
|ГДЕ
| ЗаказПокупателя.Контрагент.ИНН = &ИНН";
Запрос.УстановитьПараметр("ИНН", ИНН);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// Создаем отдельный объект XDTO для каждой строки данных
ЗаказXDTO = ФабрикаXDTO.Создать(ТипЗаказа);
ЗаказXDTO.Number = Выборка.Номер;
ЗаказXDTO.Sum = Выборка.Сумма;
// Добавляем созданный объект в коллекцию контейнера
СписокXDTO.Order.Добавить(ЗаказXDTO);
КонецЦикла;
// В настройках операции веб-сервиса типом возвращаемого значения должен быть указан OrdersList!
Возврат СписокXDTO;
КонецФункции
На стороне .NET-клиента это свойство преобразуется в стандартный массив или список объектов (например, Order[] или List<Order>), с которым очень удобно работать.
Как справедливо заметили участники обсуждения в Сообщениях 23 и 24, использование строго типизированных XDTO-пакетов часто усложняет разработку. Любое изменение в составе полей базы данных требует изменения схемы XDTO в 1С, обновления веб-сервиса на сервере и повторной генерации прокси-классов в Visual Studio.
Для упрощения поддержки и повышения стабильности работы интеграции часто используют передачу плоского текста в формате JSON через обычную строку:
string из стандартного пространства XML-схем).Посмотрим на пример реализации сериализации на стороне 1С:
Функция ПолучитьЗаказыВФорматеJSON(ИНН) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказПокупателя.Номер КАК Номер,
| ЗаказПокупателя.СуммаДокумента КАК Сумма
|ИЗ
| Документ.ЗаказПокупателя КАК ЗаказПокупателя
|ГДЕ
| ЗаказПокупателя.Контрагент.ИНН = &ИНН";
Запрос.УстановитьПараметр("ИНН", ИНН);
Выборка = Запрос.Выполнить().Выбрать();
МассивДанных = Новый Массив;
Пока Выборка.Следующий() Цикл
СтруктураСтроки = Новый Структура;
СтруктураСтроки.Вставить("Number", Выборка.Номер);
СтруктураСтроки.Вставить("Sum", Выборка.Сумма);
МассивДанных.Добавить(СтруктураСтроки);
КонецЦикла;
// Сериализация в JSON строку
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, МассивДанных);
Возврат ЗаписьJSON.Закрыть(); // Возвращается простая JSON-строка
КонецФункции
На стороне C# вам больше не понадобятся сложные XDTO-классы. Полученная строка мгновенно десериализуется в строго типизированные объекты с помощью современных библиотек, таких как System.Text.Json или Newtonsoft.Json:
// Пример на C#
string jsonResult = client.GetOrdersInJSONFormat("1234567890");
List<Order> orders = JsonSerializer.Deserialize<List<Order>>(jsonResult);
Резюмируем: Если вам необходим жесткий контроль типов на уровне контракта SOAP, используйте Решение 1 и Решение 2 с корректной настройкой схем XDTO и типов возвращаемых значений. Если вы хотите сделать интеграцию максимально гибкой, быстрой и независимой от изменений конфигурации 1С, переведите обмен на JSON-строки, как описано в Решении 3.