Как получить прикладной объект на клиенте в 1С:Предприятии 8.3 и избежать ошибки "Значение недопустимого типа"?

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

При разработке в 1С:Предприятии 8.3 с использованием управляемого приложения (тонкий и веб-клиент) многие разработчики сталкиваются с необходимостью работы с данными, которые, казалось бы, должны быть доступны на клиенте, но при попытке получить их напрямую возникают ошибки. Рассмотрим типичную ситуацию, когда вы пытаетесь получить объект прикладного типа (например, СправочникОбъект или ДокументОбъект) на клиенте через серверный вызов, и платформа выдает ошибку: "Ошибка при вызове серверного метода. по причине: Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа. по причине: Ошибка преобразования данных XDTO".

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

Причина ошибки: Архитектура клиент-серверного взаимодействия

Для начала давайте выясним причину возникновения ошибки "Значение недопустимого типа. Ошибка преобразования данных XDTO". На платформе 1С:Предприятие 8.3 в управляемом приложении существует четкое разделение обязанностей между клиентом и сервером. Клиент (тонкий или веб) предназначен главным образом для отображения пользовательского интерфейса, приема пользовательского ввода и выполнения легкой логики, связанной с отображением. Все операции, требующие обращения к базе данных, выполнения сложной бизнес-логики, модификации данных или доступа к прикладным объектам, должны выполняться на сервере.

Прикладные объекты 1С (такие как ДокументОбъект, СправочникОбъект, РегистрНакопленияЗапись и т.д.) являются "тяжелыми" объектами. Они содержат логику взаимодействия с базой данных, метаданными конфигурации и другими системными компонентами. Эти объекты существуют только в контексте сервера 1С:Предприятия. Клиент не имеет прямой возможности создавать, изменять или читать эти объекты непосредственно.

Когда вы пытаетесь передать прикладной объект с сервера на клиент (например, через оператор Возврат Объект; из серверной функции), платформа пытается его сериализовать для передачи по сети. Однако на клиенте нет соответствующих типов для десериализации такого "тяжелого" объекта. Клиент не "понимает", что это за объект, как с ним работать, и не может загрузить его в свою память. Это приводит к ошибке преобразования данных XDTO (XML Data Transfer Objects), поскольку механизм сериализации/десериализации не может найти соответствие для прикладного объекта на стороне клиента.

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

Основной подход: Работа с объектами на сервере

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

Давайте проанализируем изначальный код автора:


// Клиентский код
Объект = ОбщийМодуль.ПолучитьОбъект(Ссылка); // Вызов серверной функции

// Код общего модуля на сервере
&НаСервере
Функция ПолучитьОбъект(Ссылка) Экспорт
    Объект = Ссылка.ПолучитьОбъект(); // Здесь Ссылка.ПолучитьОбъект() возвращает прикладной объект
    Возврат Объект; // Попытка вернуть прикладной объект на клиент
КонецФункции

В этом примере функция Ссылка.ПолучитьОбъект() на сервере действительно возвращает нам прикладной объект (например, ДокументОбъект.ЗаказКлиента). Однако именно попытка вернуть этот объект на клиент через оператор Возврат Объект; и приводит к ошибке передачи данных. Клиент не может принять такой тип данных.

Таким образом, если нам нужно выполнить какие-либо действия с прикладным объектом, мы должны сделать это в контексте серверного вызова и не пытаться передать сам объект на клиент. Вместо этого мы можем передать необходимые данные объекта (например, его реквизиты) в виде сериализуемых типов.

Решение: Использование данных формы и методов преобразования

Часто возникает ситуация, когда пользователь на форме изменяет какие-либо поля, и эти поля соответствуют реквизитам прикладного объекта. В 1С:Предприятии 8.3 для таких сценариев предусмотрен специальный механизм — Данные формы. На клиенте мы работаем не напрямую с прикладным объектом, а с его представлением — реквизитами формы. Эти реквизиты могут быть связаны с данными прикладного объекта.

Чтобы эффективно работать с данными формы и прикладными объектами, мы используем два ключевых метода:

  1. РеквизитФормыВЗначение(ИмяРеквизита): Преобразует данные указанного реквизита формы в объект прикладного типа на сервере.
  2. ЗначениеВРеквизитФормы(Значение, ИмяРеквизита): Преобразует прикладной объект (или другое значение) в данные указанного реквизита формы.

Использование РеквизитФормыВЗначение()

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

Предположим, у нас есть форма документа, и на форме есть основной реквизит Объект, который представляет собой ДокументОбъект. Пользователь изменил какие-то поля этого документа на клиенте. Чтобы записать эти изменения, нам нужно передать их на сервер, где мы сможем получить прикладной объект и вызвать его метод Записать().

Рассмотрим пример:


// В модуле формы

// На клиенте пользователь изменяет реквизиты формы, например, "Объект.Наименование" или "Объект.Ответственный"
// Теперь нам нужно сохранить эти изменения.
// Мы вызываем серверную процедуру для выполнения сохранения.

&НаКлиенте
Процедура КомандаСохранитьНаКлиенте()
    // Перед вызовом сервера, можно выполнить клиентскую валидацию
    Если Не ПроверитьДанныеКлиента() Тогда
        Возврат;
    КонецЕсли;
    
    // Вызов серверного метода для сохранения объекта
    СохранитьОбъектНаСервере();
КонецПроцедуры

&НаСервере
Процедура СохранитьОбъектНаСервере()
    // 1. Получаем прикладной объект из текущих данных формы
    // Метод РеквизитФормыВЗначение() берет все изменения, внесенные в реквизит формы "Объект" на клиенте,
    // и преобразует их в полноценный прикладной объект на сервере.
    ДокументОбъект = РеквизитФормыВЗначение("Объект");
    
    // 2. Теперь мы можем работать с ДокументОбъект как с обычным прикладным объектом:
    // Выполняем серверную логику, которая не может быть на клиенте
    Если ДокументОбъект.ПометкаУдаления Тогда
        Сообщить("Документ помечен на удаление. Сохранение невозможно.", СтатусСообщения.Важное);
        Возврат;
    КонецЕсли;

    // Например, можем проверить заполнение или выполнить дополнительную логику
    Если ПустаяСтрока(ДокументОбъект.Наименование) Тогда
        ДокументОбъект.Наименование = "Новый документ без наименования";
    КонецЕсли;
    
    // 3. Записываем прикладной объект в базу данных
    // Если объект новый, он будет записан. Если существующий - изменен.
    Попытка
        ДокументОбъект.Записать(РежимЗаписиДокумента.Записать);
        Сообщить("Документ успешно сохранен на сервере!");
    Исключение
        Сообщить("Ошибка при записи документа: " + ОписаниеОшибки(), СтатусСообщения.Важное);
    КонецПопытки;
    
    // Если нужно обновить форму после записи, можно использовать ЗначениеВРеквизитФормы,
    // чтобы отразить, например, присвоенный новый номер или дату, если они формируются при записи.
    // ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры

В этом примере, РеквизитФормыВЗначение("Объект") позволяет нам получить на сервере прикладной объект, который уже содержит все изменения, сделанные пользователем на форме. Таким образом, мы избегаем прямой передачи объекта с клиента, но при этом работаем с актуальными данными.

Использование ЗначениеВРеквизитФормы()

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

Рассмотрим, как это можно сделать, основываясь на идее из сообщения 27, где приводится отличный пример:


// В модуле формы

// Допустим, нам нужно обновить данные формы на клиенте после каких-либо серверных манипуляций
// Например, заполнить документ-основание или обновить какие-то поля.

&НаКлиенте
Процедура ОбновитьДанныеФормыПоКнопке()
    // Вызываем серверный метод, который обновит объект и передаст данные на форму
    ОбновитьИОтобразитьОбъектНаСервере();
КонецПроцедуры

&НаСервере
Процедура ОбновитьИОтобразитьОбъектНаСервере()
    // 1. Получаем текущий прикладной объект из реквизита формы,
    // чтобы работать с его актуальным состоянием или чтобы получить ссылку.
    // Если нам нужен просто новый объект или объект по ссылке,
    // можно получить его напрямую: ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
    ПрикладнойОбъект = РеквизитФормыВЗначение("Объект");
    
    // 2. Выполняем необходимые изменения с прикладным объектом на сервере
    // Пример из сообщения: заполнение объекта на основе другого документа
    Если ПрикладнойОбъект.ДокументОснование.Пустая() Тогда
        // Если документа-основания нет, возьмем произвольный для примера
        СсылкаНаОснование = Документы.ЗаказКлиента.НайтиПоНомеру("000000001");
        Если СсылкаНаОснование <> Неопределено Тогда
            ПрикладнойОбъект.ДокументОснование = СсылкаНаОснование;
        КонецЕсли;
    КонецЕсли;

    ПрикладнойОбъект.Заполнить(ПрикладнойОбъект.ДокументОснование); // Заполнение на сервере
    ПрикладнойОбъект.Комментарий = "Данные обновлены на сервере: " + Формат(ТекущаяДата(), "ДФ=dd.MM.yyyy HH:mm:ss");

    // 3. Передаем измененный прикладной объект обратно в реквизит формы "Объект".
    // Это приводит к обновлению полей формы на клиенте соответствующими значениями из ПрикладнойОбъект.
    ЗначениеВРеквизитФормы(ПрикладнойОбъект, "Объект");
    
    // В данном случае, так как мы изменили объект на сервере и хотим, чтобы эти изменения
    // были доступны на форме для дальнейшей работы или для отображения,
    // мы передаем его обратно в данные формы. Если бы изменения требовали записи в базу,
    // мы бы сначала вызвали ПрикладнойОбъект.Записать().
КонецПроцедуры

В этом примере мы сначала получаем данные формы в виде прикладного объекта с помощью РеквизитФормыВЗначение() (или просто получаем объект по ссылке из базы), затем манипулируем этим объектом на сервере, а после этого используем ЗначениеВРеквизитФормы(ПрикладнойОбъект, "Объект") для того, чтобы обновленные данные отобразились на клиентской форме. Этот механизм позволяет создавать интерактивные формы, где пользователь видит актуальное состояние данных после серверных операций.

Существуют также глобальные процедуры ДанныеФормыВЗначение() и ЗначениеВДанныеФормы(). Они выполняют аналогичные преобразования, но ДанныеФормыВЗначение() требует явного указания типа конвертируемых данных и может работать без контекста формы (то есть вызываться из общих модулей). Однако в большинстве случаев, при работе с реквизитами непосредственно формы, рекомендуется использовать РеквизитФормыВЗначение() и ЗначениеВРеквизитФормы() из-за их простоты и автоматического определения типа.

Альтернативный подход: Толстый клиент

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

Однако современная разработка в 1С:Предприятии 8.3 ориентирована на тонкий и веб-клиент, где основная работа происходит на сервере. Использование толстого клиента для новых разработок или существенных изменений в управляемом приложении обычно не рекомендуется, так как это снижает гибкость, масштабируемость и доступность приложения через веб.

Заключение

Итак, мы выяснили, что прямая передача прикладных объектов 1С с сервера на клиент в управляемом приложении невозможна из-за архитектурных ограничений и особенностей сериализации данных. Ошибка "Значение недопустимого типа. Ошибка преобразования данных XDTO" является прямым следствием попытки нарушить это правило.

Чтобы корректно работать с данными прикладных объектов в управляемом приложении, мы должны следовать следующим принципам:

  1. Все операции с прикладными объектами (чтение, запись, изменение реквизитов, выполнение методов) должны выполняться на сервере.
  2. Клиент взаимодействует с сервером через вызовы серверных процедур и функций.
  3. Для отображения и редактирования данных прикладных объектов на клиенте используем реквизиты формы.
  4. Для преобразования данных формы в прикладной объект на сервере используем метод РеквизитФормыВЗначение().
  5. Для обновления данных формы после серверных манипуляций с прикладным объектом используем метод ЗначениеВРеквизитФормы().

Применяя эти подходы, вы сможете эффективно разрабатывать функциональные и производительные управляемые приложения, полностью соответствующие архитектуре платформы 1С:Предприятие 8.3.

← На главную