Обмен данными между клиентом и сервером в управляемой форме 1С
Содержание
Самый прямой способ передать значение с сервера на клиент в 1С — вернуть его из функции с директивой &НаСервереБезКонтекста или &НаСервере: клиент вызывает функцию, сервер отдаёт результат через Возврат. Второй способ — записать значение в реквизит формы внутри серверной процедуры: после вызова платформа сама вернёт обновлённые данные формы на клиент.
Типичная ситуация: программист дорабатывает документ, на клиенте пользователь выбирает контрагента, а нужно подставить его ИНН и договор. Обратиться к Контрагент.ИНН на клиенте нельзя — данные лежат в базе, а база доступна только серверу. Ниже — как такой обмен устроен в управляемом приложении 1С 8.3 и какими методами его делать без лишних вызовов.
Почему клиент и сервер в 1С разделены
В управляемом приложении код формы выполняется в двух местах. Клиентская часть работает на компьютере пользователя (тонкий клиент или веб-клиент), серверная — в рабочем процессе сервера 1С или, в файловом варианте, в том же процессе, но с теми же ограничениями.
Что доступно на клиенте:
- ✅ примитивы:
Строка,Число,Дата,Булево,Неопределено; - ✅ ссылки на объекты (
СправочникСсылка,ДокументСсылка) — но только как идентификаторы, без чтения реквизитов через точку; - ✅ универсальные коллекции:
Массив,Структура,Соответствие, а также их фиксированные варианты; - ✅
ДвоичныеДанные,УникальныйИдентификатор, данные формы (Объект, реквизиты формы).
Что живёт только на сервере:
- ❌
Запрос,РезультатЗапроса, менеджеры справочников и документов; - ❌
СправочникОбъект,ДокументОбъект, наборы записей регистров; - ❌
ТаблицаЗначений,ДеревоЗначений.
Отсюда главное правило: с сервера на клиент уходят только значения типов, которые существуют на клиенте. Всё остальное перед возвратом надо преобразовать.
Директиву компиляции у каждой процедуры модуля формы пишите явно. Процедура без директивы в модуле управляемой формы считается клиентской, и вызов из неё запроса выдаст ошибку уже при проверке модуля.
Директивы и синтаксис вызова серверной функции
Вызов сервера из клиентского кода выглядит как обычный вызов процедуры. Разница — в директиве над серверным методом.
| Директива | Что передаётся на сервер | Когда использовать |
|---|---|---|
&НаСервере | Все данные формы туда и обратно | Нужно изменить реквизиты формы, табличные части, вызвать методы формы |
&НаСервереБезКонтекста | Только параметры вызова | Нужно получить значение из базы по ссылке или параметрам |
&НаКлиентеНаСервереБезКонтекста | Ничего — вызывается там, откуда позвали | Общие вычисления без обращения к базе |
| Общий модуль с флагами «Сервер» и «Вызов сервера» | Только параметры | Функция нужна в нескольких формах |
Пример: при выборе контрагента подставляем ИНН и КПП в реквизиты своего документа.
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
Реквизиты = РеквизитыКонтрагента(Объект.Контрагент);
Если Реквизиты <> Неопределено Тогда
Объект.ИНН = Реквизиты.ИНН;
Объект.КПП = Реквизиты.КПП;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция РеквизитыКонтрагента(Контрагент)
Если Не ЗначениеЗаполнено(Контрагент) Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.ИНН КАК ИНН,
| Контрагенты.КПП КАК КПП
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Контрагент);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Новый Структура("ИНН, КПП", Выборка.ИНН, Выборка.КПП);
КонецЕсли;
Возврат Неопределено;
КонецФункции
Функция без контекста не видит Объект и Элементы, поэтому ссылку передаём параметром. Зато по сети уходит одна ссылка, а не весь документ с табличными частями.
Если в функции вы пытаетесь обратиться к Объект или ЭтаФорма, а она помечена &НаСервереБезКонтекста, модуль не пройдёт синтаксический контроль. Либо передайте нужные значения параметрами, либо смените директиву на &НаСервере.
Вызов через общий модуль
Если та же функция нужна в пяти формах, вынесите её в общий модуль. В свойствах модуля включите флажки Сервер и Вызов сервера, функцию объявите с ключевым словом Экспорт. На клиенте вызов выглядит так: Реквизиты = РаботаСКонтрагентамиВызовСервера.РеквизитыКонтрагента(Объект.Контрагент);.
Модуль без флажка «Вызов сервера» с клиента не виден, даже если функция экспортная.
Параметры и возвращаемые значения: что можно передать
Параметры серверного метода и результат Возврат проходят сериализацию. Структуры и массивы могут быть вложенными, но каждый элемент внутри тоже должен быть доступен на клиенте.
Возврат нескольких значений
Функция возвращает одно значение. Для нескольких — упакуйте их в Структура: ключи читаются на клиенте как свойства (Результат.Сумма). Если ключей заранее не знаете — Соответствие.
Второй вариант — параметры по ссылке. Параметр без Знач при вызове сервера тоже возвращается на клиент изменённым. Приём рабочий, но читается хуже: глядя на вызов, не видно, что переменная поменяется.
Таблица значений: как вернуть строки
ТаблицаЗначений на клиент не передаётся. Три пути:
- Массив структур — для небольших выборок, которые нужны в клиентском коде.
- Реквизит формы с типом
ТаблицаЗначений— для данных, которые надо показать пользователю в таблице. Заполняется методомЗначениеВРеквизитФормыв процедуре&НаСервере. - Временное хранилище — для крупных объёмов и для передачи результата фонового задания.
&НаСервереБезКонтекста
Функция СтрокиДляКлиента(Таблица)
Результат = Новый Массив;
Для Каждого Строка Из Таблица Цикл
Элемент = Новый Структура;
Для Каждого Колонка Из Таблица.Колонки Цикл
Элемент.Вставить(Колонка.Имя, Строка[Колонка.Имя]);
КонецЦикла;
Результат.Добавить(Элемент);
КонецЦикла;
Возврат Результат;
КонецФункции
Пример с реквизитом формы Остатки (тип ТаблицаЗначений, колонки созданы в редакторе формы):
&НаСервере
Процедура ЗаполнитьОстаткиНаСервере()
Запрос = Новый Запрос;
Запрос.Текст = "..."; // запрос с колонками, как у реквизита Остатки
Таблица = Запрос.Выполнить().Выгрузить();
ЗначениеВРеквизитФормы(Таблица, "Остатки");
КонецПроцедуры
Обратное преобразование — РеквизитФормыВЗначение("Остатки"). Оба метода есть только на сервере и только у формы, поэтому функция без контекста их не вызовет.
Временное хранилище
ПоместитьВоВременноеХранилище(Значение, УникальныйИдентификатор) на сервере возвращает строку-адрес. Адрес — обычная строка, клиент получает её и читает данные через ПолучитьИзВременногоХранилища(Адрес). Второй параметр привязывает данные к форме: когда форма закроется, платформа их удалит.
Так удобно отдавать файл: на сервере собрали ДвоичныеДанные, положили в хранилище, на клиенте по адресу запустили сохранение файла.
Сколько стоит вызов сервера и как его сократить
Каждый вызов — это сетевой запрос. В локальной сети он почти незаметен, а у пользователя на удалённом подключении или в веб-клиенте десяток вызовов при открытии формы даёт заметную паузу.
Где теряется время:
- 🐢 серверная процедура
&НаСерверетащит туда и обратно весь документ — с табличной частью на тысячу строк это ощутимо; - 🐢 вызов сервера в цикле на клиенте: 200 строк — 200 запросов;
- 🐢 отдельный вызов на каждый реквизит: сначала ИНН, потом КПП, потом договор.
Что помогает:
- Один вызов вместо нескольких. Соберите всё нужное в одну функцию и верните структурой.
- Без контекста везде, где форма не нужна. Передавайте ссылку и пару параметров, а не весь объект.
- Цикл переносите на сервер. Передайте массив ссылок одним параметром, обработайте его запросом, верните соответствие «ссылка → значение».
- Повторное использование значений. Для общего модуля с флагом «Вызов сервера» поставьте свойство
Повторное использование возвращаемых значений=На время сеанса. Повторный вызов с теми же параметрами вернёт кешированный результат без обращения к серверу. Годится для настроек и констант, которые не меняются за сеанс. Возвращайте из такой функцииФиксированнаяСтруктураилиФиксированныйМассив, чтобы вызывающий код случайно не изменил кешированную копию. - Параметры при открытии формы. Всё, что форма знает при создании, получайте в
ПриСозданииНаСервереи записывайте в реквизиты. Этот обработчик выполняется в рамках серверного вызова открытия формы, отдельного вызова не нужно.
Увидеть лишние вызовы помогает замер производительности в режиме отладки: в Конфигураторе запустите Отладка → Замер производительности и посмотрите, сколько раз вызывается серверная функция на одно действие пользователя.
&НаСервереБезКонтекста, результат — примитив или Структура.&НаСервере: изменения в данных формы вернутся на клиент сами.ТаблицаЗначений, заполнение через ЗначениеВРеквизитФормы.Долгие операции: фоновое задание и опрос с клиента
Серверный вызов синхронный: пока он выполняется, интерфейс клиента ждёт. Расчёт на минуту превращается в «зависшую» форму. Сервер к тому же не может сам позвать клиента — инициатор всегда клиент.
Рабочая схема для длинных расчётов:
- Клиент вызывает сервер, тот создаёт адрес во временном хранилище и запускает фоновое задание.
- Сервер сразу возвращает адрес, форма остаётся отзывчивой.
- Фоновое задание кладёт результат по этому адресу.
- Клиент через обработчик ожидания раз в пару секунд спрашивает: задание завершилось?
- Когда завершилось — клиент забирает результат из хранилища.
// В форме: реквизиты АдресРезультата (Строка) и ИдентификаторЗадания (УникальныйИдентификатор)
&НаКлиенте
Процедура Рассчитать(Команда)
ЗапуститьРасчетНаСервере();
ПодключитьОбработчикОжидания("ПроверитьРасчет", 2, Ложь);
КонецПроцедуры
&НаСервере
Процедура ЗапуститьРасчетНаСервере()
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(АдресРезультата);
Задание = ФоновыеЗадания.Выполнить("РасчетыСервер.ДолгийРасчет", ПараметрыЗадания);
ИдентификаторЗадания = Задание.УникальныйИдентификатор;
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьРасчет()
Если Не ЗаданиеЗавершено(ИдентификаторЗадания) Тогда
Возврат;
КонецЕсли;
ОтключитьОбработчикОжидания("ПроверитьРасчет");
Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
// дальше работа с результатом на клиенте
КонецПроцедуры
&НаСервереБезКонтекста
Функция ЗаданиеЗавершено(Идентификатор)
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
Если Задание = Неопределено Тогда
Возврат Истина;
КонецЕсли;
Если Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно Тогда
ВызватьИсключение ПодробноеПредставлениеОшибки(Задание.ИнформацияОбОшибке);
КонецЕсли;
Возврат Задание.Состояние <> СостояниеФоновогоЗадания.Активно;
КонецФункции
В общем серверном модуле РасчетыСервер экспортная процедура принимает адрес и в конце пишет результат: ПоместитьВоВременноеХранилище(Результат, АдресРезультата);. Результат должен быть того же «клиентского» типа — массив, структура, двоичные данные.
В типовых конфигурациях на Библиотеке стандартных подсистем эту обвязку уже написали: функции общего модуля ДлительныеОперации запускают задание, а ДлительныеОперацииКлиент ждут его и показывают индикатор. Если конфигурация на БСП, берите их, а не пишите опрос вручную.
А как же Асинх и Ждать?
Ключевые слова Асинх и Ждать появились в платформе 8.3.18. Они упрощают работу с диалогами и асинхронными методами клиента: вопрос пользователю, выбор файла, копирование файлов. Серверный вызов от них асинхронным не становится — для долгого расчёта по-прежнему нужен фоновый процесс.
Ошибки при передаче данных и как их ловить
Тип нельзя передать на клиент
Самая частая ошибка: функция возвращает ТаблицаЗначений, РезультатЗапроса или ДокументОбъект, либо прячет такое значение внутри структуры. Платформа выдаст ошибку в момент возврата, а не при сохранении модуля, поэтому её легко пропустить на тестах с пустыми данными.
Как проверить: пройдите возвращаемую структуру в отладчике и посмотрите тип каждого элемента. Всё серверное преобразуйте — в массив структур, в адрес хранилища, в примитивы.
Чтение реквизита ссылки на клиенте
Код Объект.Контрагент.ИНН в клиентской процедуре вызывает ошибку: через ссылку на клиенте реквизиты не читаются. Решение — серверная функция, как в примере выше. В конфигурациях на БСП для этого есть ОбщегоНазначения.ЗначениеРеквизитаОбъекта и ЗначенияРеквизитовОбъекта, но это серверные функции: вызывайте их из своей функции &НаСервереБезКонтекста.
Исключение на сервере
Если серверный код вызвал ВызватьИсключение или упал на ошибке, исключение доходит до клиента и прерывает клиентскую процедуру. Перехватите его там, где это имеет смысл:
&НаКлиенте
Процедура Заполнить(Команда)
Попытка
ЗаполнитьНаСервере();
Исключение
Информация = ИнформацияОбОшибке();
ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(Информация));
КонецПопытки;
КонецПроцедуры
Пользователю показывайте краткое представление, а подробное (со стеком) пишите в журнал регистрации на сервере через ЗаписьЖурналаРегистрации.
Не глушите исключения пустым блоком Исключение … КонецПопытки. Форма продолжит работу с незаполненными данными, и ошибку потом будут искать по неверным остаткам.
Сообщения с сервера
Чтобы передать пользователю текст без прерывания работы, на сервере создайте Новый СообщениеПользователю, заполните Текст и вызовите Сообщить(). Сообщение придёт на клиент вместе с ответом сервера и появится в окне сообщений формы. Свойство Поле позволяет подсветить конкретный реквизит, например Объект.Контрагент.
Обратное направление: из формы в форму и с клиента на сервер
Передача не ограничена парой «клиент — сервер одной формы». Ещё три сценария, которые встречаются постоянно:
- 📨 Параметры открытия формы.
ОткрытьФорму("Обработка.Расчет.Форма", Новый Структура("Организация", Объект.Организация))— в открываемой форме параметр читается вПриСозданииНаСерверечерезПараметры.Организация. Параметр надо добавить в список параметров формы в конфигураторе. - 📬 Результат закрытия формы. Подчинённая форма вызывает
Закрыть(Результат), вызывающая получает значение в процедуре, указанной вОписаниеОповещенияпри открытии. - 📢 Оповещение других форм. После записи данных на клиенте вызовите
Оповестить("ИмяСобытия", Параметр); открытые формы поймают событие в обработчикеОбработкаОповещенияи перечитают данные сами.
Направление «клиент → сервер» устроено зеркально: параметры серверного метода проходят ту же сериализацию. Значит, передать с клиента можно только то, что на клиенте существует, а данные формы &НаСервере получит и без параметров.