Как передать данные из серверного модуля в клиентский в 1С:Предприятие 8.3

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

При разработке в управляемом приложении «1С:Предприятие 8.3» одной из наиболее частых задач является организация взаимодействия между клиентской и серверной частями системы. Физически клиент и сервер могут находиться на разных компьютерах, поэтому просто так передать переменную или объект из одного контекста в другой не удастся.

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

Ограничение клиент-серверной границы: Сериализация данных

Прежде чем переходить к коду, давайте выясним ключевую причину, почему нельзя просто передать любой объект с сервера на клиент. При передаче данных через границу «Клиент-Сервер» происходит процесс сериализации — преобразования объектов 1С во внутренний XML- или JSON-формат для последующей передачи по сети и сборки на «другой стороне».

Давайте разберем, какие типы данных поддерживают сериализацию, а какие — нет:

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

Проанализируем практические способы обхода этих ограничений и корректной передачи информации.

---

Способ 1. Использование возвращаемого значения функции (Структура)

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

Пусть у нас есть серверный общий модуль ОМ_Серверный, в котором выполняется расчет параметров номенклатуры. Нам нужно получить эти параметры на клиенте.

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


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

Теперь посмотрим, как вызвать этот метод в модуле формы на стороне клиента:


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

---

Способ 2. Обход ограничений на передачу Таблиц значений

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

Давайте разберем по шагам, как выполнить это преобразование на сервере:


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

На клиенте мы получаем обычный массив, который легко можем обойти в цикле:


&НаКлиенте
Процедура ПоказатьЦены(Команда)
    
    МассивТоваров = Новый Массив;
    МассивТоваров.Добавить(Объект.Номенклатура);
    
    СписокДанных = ОМ_Серверный.ПолучитьСписокЦен(МассивТоваров);
    
    Для Каждого ЭлементСписка Из СписокДанных Цикл
        Сообщить("Цена товара " + ЭлементСписка.Товар + " составляет: " + ЭлементСписка.Price);
    КонецЦикла;
    
КонецПроцедуры

---

Способ 3. Использование Временного хранилища (Передача Адреса)

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

Для корректной передачи информации через временное хранилище мы должны использовать правильный паттерн:

  1. Инициализировать уникальный адрес во временном хранилище на клиенте, привязав его к уникальному идентификатору текущей формы (ЭтаФорма.УникальныйИдентификатор).
  2. Передать этот адрес на сервер в качестве параметра вызова.
  3. На сервере поместить реальные данные по уже созданному адресу.
  4. На клиенте извлечь данные по этому адресу.

Рассмотрим пример реализации этой схемы:


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

&НаСервереБезКонтекста
Процедура ЗаписатьДанныеВХранилищеНаСервере(Знач АдресХранилища, Знач Товар)
    
    // Формируем структуру на сервере
    ДанныеДляКлиента = Новый Структура;
    ДанныеДляКлиента.Вставить("Код", Товар.Код);
    ДанныеДляКлиента.Вставить("Наименование", Товар.Наименование);
    
    // Шаг 3. Помещаем структуру в хранилище по переданному адресу
    ПоместитьВоВременноеХранилище(ДанныеДляКлиента, АдресХранилища);
    
КонецПроцедуры

---

Способ 4. Архитектурный подход к флагу «Вызов сервера»

В сообщениях форума упоминается важная деталь: чтобы вызвать метод серверного общего модуля напрямую с клиента, у этого общего модуля должен стоять флаг «Вызов сервера» (Server Call).

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

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

  1. Вызов с клиента должен адресоваться не к общему модулю напрямую, а к локальной процедуре или функции формы.
  2. Эта локальная процедура формы должна иметь директиву &НаСервереБезКонтекста (или &НаСервере).
  3. Уже внутри серверного контекста формы мы вызываем экспортный метод общего серверного модуля. Сервер-серверные вызовы работают без каких-либо ограничений и не требуют включения флага «Вызов сервера».

Посмотрим на пример такой реализации:


// В общем модуле ОМ_Серверный_БезВызоваКлиента (Свойства модуля: Сервер = Истина, Вызов сервера = Ложь)
Функция РассчитатьСкидку(Знач Товар, Знач Контрагент) Экспорт
    // Тяжелые вычисления на сервере
    Скидка = 10; // Пример расчета
    Возврат Скидка;
КонецФункции

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

// Функция-мост на сервере формы
&НаСервереБезКонтекста
Функция ПолучитьСкидкуНаСервереФормы(Знач Товар, Знач Контрагент)
    
    // Без проблем вызываем серверный ОМ из серверного контекста формы
    Возврат ОМ_Серверный_БезВызоваКлиента.РассчитатьСкидку(Товар, Контрагент);
    
КонецФункции

Использование директивы &НаСервереБезКонтекста в качестве «моста» предпочтительнее, поскольку при таком вызове на сервер не передаются данные всей формы (данные реквизитов), что существенно экономит сетевой трафик и ускоряет работу программы.

---

Резюме

Мы подробно проанализировали механизмы передачи данных с сервера на клиент в платформе 1С:Предприятие 8.3. Подведем итоги:

← На главную