Как правильно вызывать серверные методы из клиентского контекста в 1С и избежать ошибок сериализации?

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

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

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

Проанализируем механику работы общих модулей. Если у общего модуля включены флаги Клиент (управляемое приложение) и Сервер, то при вызове его методов из интерфейса или клиентского кода он компилируется и выполняется непосредственно в контексте тонкого клиента. В этот момент ему недоступны:

  1. Общие модули, у которых установлен только флаг Сервер (без признака Вызов сервера).
  2. Объекты для прямой работы с базой данных (запросы, регистры сведений, объектные типы данных СправочникОбъект, ДокументОбъект).

При попытке напрямую обратиться к чисто серверному модулю из клиентского контекста платформа 1С сообщает, что имя модуля не определено, так как данный контекст на клиенте попросту не скомпилирован.

Ошибка сериализации XDTO при передаче данных между клиентом и сервером

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

Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа. Ошибка преобразования данных XDTO: Отсутствует отображение для типа 'ВыборкаИзРезультатаЗапроса' (или 'ТаблицаЗначений')

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

Решение 1: Преобразование серверных коллекций в сериализуемые типы

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


Функция ПолучитьДанныеКартыЛояльности(КартаЛояльности) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   КартыЛояльности.БонуснаяПрограмма,
        |   КартыЛояльности.Статус
        |ИЗ
        |   Справочник.КартыЛояльности КАК КартыЛояльности
        |ГДЕ
        |   КартыЛояльности.Ссылка = &КартаЛояльности";
        
    Запрос.УстановитьПараметр("КартаЛояльности", КартаЛояльности);
    ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
    
    // Преобразуем таблицу значений в массив структур
    ТаблицаМассивомСтруктур = ОбщегоНазначения.ТаблицаЗначенийВМассив(ТаблицаРезультата);
    
    Возврат ТаблицаМассивомСтруктур;
    
КонецФункции

В типовых конфигурациях на базе БСП для подобных преобразований используется метод ОбщегоНазначения.ТаблицаЗначенийВМассив(). Полученный массив структур успешно сериализуется и считывается на тонком клиенте.

Решение 2: Правильное разделение логики по стандартам 1С (Архитектурный подход)

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

  1. Модули [Имя]КлиентСервер — содержат только универсальные алгоритмы (математические расчеты, обработка строк, парсинг), не обращающиеся к базе данных и метаданным. В них не должно быть прямых обращений к запросам или вызовам сервера.
  2. Модули [Имя]Сервер — содержат всю бизнес-логику и работу с базой данных. Имеют признак Сервер.
  3. Модули [Имя]ВызовСервера — выступают в качестве фасада (API) для клиентской части. Имеют признаки Сервер и Вызов сервера, содержат минимальное количество компактных функций, возвращающих примитивные типы, структуры или ссылки.
  4. Модули [Имя]Клиент — содержат обработчики интерфейса, управление формами и диалогами.

Решение 3: Вызов серверного контекста через модуль формы

Если вызов инициируется из интерфейса (формы документа или справочника), оптимальным решением будет вызов серверной процедуры самой формы, а не глобального модуля. Разберем этот вариант:


&НаКлиенте
Процедура ПриИзмененииКартыЛояльности(Команда)
    
    // Вызываем серверный метод формы без передачи контекста формы
    ПараметрыКарты = ПолучитьПараметрыКартыНаСервере(Объект.КартаЛояльности);
    
    // Обрабатываем полученную структуру на клиенте
    Если ПараметрыКарты.Найдена Тогда
        Элементы.БонуснаяПрограмма.Текст = ПараметрыКарты.БонуснаяПрограмма;
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьПараметрыКартыНаСервере(КартаЛояльности)
    
    // На сервере без контекста мы можем напрямую вызывать чисто серверный модуль
    Возврат БонусныеБаллыСервер.БонуснаяПрограммаКартыЛояльности(КартаЛояльности);
    
КонецФункции

Директива &НаСервереБезКонтекста позволяет выполнить переход на сервер без накладных расходов на передачу данных формы и открывает полный доступ ко всем чисто серверным модулям конфигурации.

Решение 4: Передача больших объемов данных через временное хранилище

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

← На главную