Как исправить ошибки передачи пакетов XDTO через веб-сервис 1С в сторонние приложения (.NET/C#)?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Интеграция систем на платформе «1С:Предприятие» со сторонними приложениями (например, консольными утилитами на C#/.NET) с использованием SOAP-веб-сервисов — мощный, но довольно капризный инструмент. Часто разработчики сталкиваются с труднообъяснимыми ошибками на принимающей стороне: от банального отсутствия настроек конечной точки в конфигурационном файле клиента до сложных исключений парсера XML, таких как XmlException при попытке прочитать структуру XDTO.

В этой статье мы подробно разберем, почему возникают подобные проблемы, как правильно настроить типы возвращаемых значений, как организовать передачу массивов (списков) объектов через XDTO и рассмотрим более надежную альтернативу — использование JSON-сериализации.

Анализ проблемы: почему возникает ошибка XmlException на стороне клиента?

Рассмотрим ситуацию: веб-сервис 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;, это работает, но лишает интеграцию всякого смысла, ведь цель — передать всю структуру данных контрагента (наименование, долг, кредитную линию и т.д.).

Решение 1: Правильная настройка типов в веб-сервисе 1С

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

  1. Настройка операции веб-сервиса в конфигураторе 1С: Перейдем к свойствам вашего веб-сервиса, откроем нужную операцию (например, GetKontragent) и найдем свойство «Тип возвращаемого значения». Вместо стандартного типа string необходимо выбрать ваш собственный тип из пакета XDTO. Он должен соответствовать типу создаваемого объекта (например, Kontragent из пространства имен http://localhost/VV/ws/Kontragent).
  2. Обновление публикации на веб-сервере: После внесения изменений сохраним конфигурацию и обновим публикацию веб-сервиса на IIS или Apache. Это критически важно, так как файл описания веб-сервиса (WSDL) должен обновиться.
  3. Обновление ссылки на службу в .NET: В проекте Visual Studio кликнем правой кнопкой мыши по ссылке на службу (Service Reference) и выберем пункт «Update Service Reference». Среда .NET перегенерирует прокси-класс, и теперь он будет ожидать на входе сложный объект, а не простую строку.

Также не забудем про ошибку инициализации клиента 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);

Решение 2: Передача коллекций (массивов) объектов через XDTO

Рассмотрим ситуацию, когда по ИНН контрагента нам требуется вернуть не одного контрагента, а целый список его заказов (как в Сообщении 26). Прямого понятия «массив» в XDTO нет. Списки реализуются с помощью специальных настроек свойств объектов.

Разберем по шагам настройку XDTO-пакета для списка заказов:

  1. В вашем пакете XDTO создадим тип объекта Order (Заказ) со свойствами: Number (строка), Sum (число) и т.д.
  2. Создадим еще один тип объекта, который будет выступать контейнером (оберткой) списка, назовем его OrdersList (СписокЗаказов).
  3. Внутри типа OrdersList добавим свойство Order. В поле «Тип» укажем созданный ранее тип объекта Order.
  4. Самый важный шаг: установим у свойства 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>), с которым очень удобно работать.

Решение 3: Альтернативный подход с использованием JSON (Рекомендуется)

Как справедливо заметили участники обсуждения в Сообщениях 23 и 24, использование строго типизированных XDTO-пакетов часто усложняет разработку. Любое изменение в составе полей базы данных требует изменения схемы XDTO в 1С, обновления веб-сервиса на сервере и повторной генерации прокси-классов в Visual Studio.

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

  1. В операции веб-сервиса 1С укажем тип возвращаемого значения как простую строку (string из стандартного пространства XML-схем).
  2. В коде 1С свернем результаты запроса в массив структур и сериализуем в JSON.

Посмотрим на пример реализации сериализации на стороне 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.

← На главную