При разработке в управляемом приложении «1С:Предприятие 8.3» одной из наиболее частых задач является организация взаимодействия между клиентской и серверной частями системы. Физически клиент и сервер могут находиться на разных компьютерах, поэтому просто так передать переменную или объект из одного контекста в другой не удастся.
В этой статье мы подробно разберем, как правильно передавать данные (в частности, структуры, таблицы значений и файлы) из серверного модуля в клиентский, проанализируем основные ограничения платформы и рассмотрим архитектурно верные подходы.
Прежде чем переходить к коду, давайте выясним ключевую причину, почему нельзя просто передать любой объект с сервера на клиент. При передаче данных через границу «Клиент-Сервер» происходит процесс сериализации — преобразования объектов 1С во внутренний XML- или JSON-формат для последующей передачи по сети и сборки на «другой стороне».
Давайте разберем, какие типы данных поддерживают сериализацию, а какие — нет:
Строка, Число, Булево, Дата), ссылки на объекты базы данных (СправочникСсылка, ДокументСсылка), универсальные коллекции (Структура, Массив, Соответствие), если их элементы также состоят из сериализуемых типов.СправочникОбъект, ДокументОбъект), запросы и их результаты (Запрос, РезультатЗапроса, ВыборкаИзРезультатаЗапроса), а также таблицы и деревья значений (ТаблицаЗначений, ДеревоЗначений).Если вы попытаетесь передать на клиент несериализуемый тип (например, вернете таблицу значений из серверной функции), платформа выдаст критическую ошибку выполнения.
Проанализируем практические способы обхода этих ограничений и корректной передачи информации.
---
Самый простой и стандартный способ передачи данных — это вызов серверной функции из клиентского метода с возвратом результата в виде структуры или массива. Рассмотрим классический пример взаимодействия.
Пусть у нас есть серверный общий модуль ОМ_Серверный, в котором выполняется расчет параметров номенклатуры. Нам нужно получить эти параметры на клиенте.
Создадим функцию в серверном общем модуле:
// В общем модуле ОМ_Серверный (галочка "Сервер" включена, "Экспорт" обязателен)
Функция ПолучитьДанныеТовара(Знач ТоварСсылка) Экспорт
Результат = Новый Структура;
Результат.Вставить("Код", ТоварСсылка.Код);
Результат.Вставить("Наименование", ТоварСсылка.Наименование);
Результат.Вставить("СтавкаНДС", ТоварСсылка.СтавкаНДС);
Возврат Результат;
КонецФункции
Теперь посмотрим, как вызвать этот метод в модуле формы на стороне клиента:
// В модуле управляемой формы
&НаКлиенте
Процедура КомандаПолучитьДанные(Команда)
ТоварСсылка = Объект.Номенклатура;
Если Не ЗначениеЗаполнено(ТоварСсылка) Тогда
Возврат;
КонецЕсли;
// Получаем структуру данных напрямую из серверного модуля
СтруктураРезультат = ОМ_Серверный.ПолучитьДанныеТовара(ТоварСсылка);
// Обрабатываем полученные данные на клиенте
Сообщить("Код товара: " + СтруктураРезультат.Код);
Сообщить("Наименование: " + СтруктураРезультат.Наименование);
КонецПроцедуры
---
Рассмотрим ситуацию, когда серверный модуль формирует таблицу значений, и эти данные жизненно необходимы на клиенте. Передать саму таблицу значений напрямую нельзя. В этом случае мы должны преобразовать её в массив структур.
Давайте разберем по шагам, как выполнить это преобразование на сервере:
// В серверном общем модуле
Функция ПолучитьСписокЦен(СписокТоваров) Экспорт
// Формируем запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Товар,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, Номенклатура В (&СписокТоваров)) КАК ЦеныНоменклатурыСрезПоследних";
Запрос.УстановитьПараметр("СписокТоваров", СписокТоваров);
ТаблицаЦен = Запрос.Выполнить().Выгрузить(); // Получили Таблицу Значений
// Преобразуем таблицу значений в массив структур для передачи на клиент
МассивРезультатов = Новый Массив;
Для Каждого СтрокаТЗ Из ТаблицаЦен Цикл
СтрокаСтруктура = Новый Структура;
СтрокаСтруктура.Вставить("Товар", СтрокаТЗ.Товар);
СтрокаСтруктура.Вставить("Цена", СтрокаТЗ.Цена);
МассивРезультатов.Добавить(СтрокаСтруктура);
КонецЦикла;
Возврат МассивРезультатов; // Массив структур успешно сериализуется
КонецФункции
На клиенте мы получаем обычный массив, который легко можем обойти в цикле:
&НаКлиенте
Процедура ПоказатьЦены(Команда)
МассивТоваров = Новый Массив;
МассивТоваров.Добавить(Объект.Номенклатура);
СписокДанных = ОМ_Серверный.ПолучитьСписокЦен(МассивТоваров);
Для Каждого ЭлементСписка Из СписокДанных Цикл
Сообщить("Цена товара " + ЭлементСписка.Товар + " составляет: " + ЭлементСписка.Price);
КонецЦикла;
КонецПроцедуры
---
В обсуждении на форуме возник важный вопрос: «как передать на ту сторону Адрес?» при работе с временным хранилищем. Действительно, если вы просто поместите данные во временное хранилище на сервере без привязки к форме, это хранилище будет очищено сразу после завершения серверного вызова, и клиент не сможет прочитать данные.
Для корректной передачи информации через временное хранилище мы должны использовать правильный паттерн:
ЭтаФорма.УникальныйИдентификатор).Рассмотрим пример реализации этой схемы:
// В модуле управляемой формы
&НаКлиенте
Процедура ПолучитьДанныеЧерезХранилище(Команда)
// Шаг 1. Инициализируем пустой адрес во временном хранилище, привязанный к форме
АдресХранилища = ПоместитьВоВременноеХранилище(Неопределено, ЭтаФорма.УникальныйИдентификатор);
// Шаг 2. Вызываем серверную процедуру и передаем туда адрес
ЗаписатьДанныеВХранилищеНаСервере(АдресХранилища, Объект.Номенклатура);
// Шаг 4. Получаем данные на клиенте из хранилища
РезультатДанных = ПолучитьИзВременногоХранилища(АдресХранилища);
Если РезультатДанных <> Неопределено Тогда
Сообщить("Данные успешно получены из хранилища! Код: " + РезультатДанных.Код);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗаписатьДанныеВХранилищеНаСервере(Знач АдресХранилища, Знач Товар)
// Формируем структуру на сервере
ДанныеДляКлиента = Новый Структура;
ДанныеДляКлиента.Вставить("Код", Товар.Код);
ДанныеДляКлиента.Вставить("Наименование", Товар.Наименование);
// Шаг 3. Помещаем структуру в хранилище по переданному адресу
ПоместитьВоВременноеХранилище(ДанныеДляКлиента, АдресХранилища);
КонецПроцедуры
---
В сообщениях форума упоминается важная деталь: чтобы вызвать метод серверного общего модуля напрямую с клиента, у этого общего модуля должен стоять флаг «Вызов сервера» (Server Call).
Важное архитектурное предупреждение: Включение флага «Вызов сервера» для всех общих модулей подряд считается плохой практикой (антипаттерном) разработки в 1С. Это увеличивает объем компиляции клиентского контекста, размывает границы архитектуры и может приводить к снижению производительности.
Давайте разберем, как сделать правильно, если у общего модуля отключен флаг «Вызов сервера»:
&НаСервереБезКонтекста (или &НаСервере).Посмотрим на пример такой реализации:
// В общем модуле ОМ_Серверный_БезВызоваКлиента (Свойства модуля: Сервер = Истина, Вызов сервера = Ложь)
Функция РассчитатьСкидку(Знач Товар, Знач Контрагент) Экспорт
// Тяжелые вычисления на сервере
Скидка = 10; // Пример расчета
Возврат Скидка;
КонецФункции
// В модуле управляемой формы
&НаКлиенте
Процедура КомандаРассчитать(Команда)
// Вызываем локальный метод формы-моста
ПроцентСкидки = ПолучитьСкидкуНаСервереФормы(Объект.Номенклатура, Объект.Контрагент);
Сообщить("Рассчитанная скидка: " + ПроцентСкидки + "%");
КонецПроцедуры
// Функция-мост на сервере формы
&НаСервереБезКонтекста
Функция ПолучитьСкидкуНаСервереФормы(Знач Товар, Знач Контрагент)
// Без проблем вызываем серверный ОМ из серверного контекста формы
Возврат ОМ_Серверный_БезВызоваКлиента.РассчитатьСкидку(Товар, Контрагент);
КонецФункции
Использование директивы &НаСервереБезКонтекста в качестве «моста» предпочтительнее, поскольку при таком вызове на сервер не передаются данные всей формы (данные реквизитов), что существенно экономит сетевой трафик и ускоряет работу программы.
---
Мы подробно проанализировали механизмы передачи данных с сервера на клиент в платформе 1С:Предприятие 8.3. Подведем итоги: