Обмен данными между клиентом и сервером в управляемой форме 1С

· 11 мин

Содержание

Самый прямой способ передать значение с сервера на клиент в 1С — вернуть его из функции с директивой &НаСервереБезКонтекста или &НаСервере: клиент вызывает функцию, сервер отдаёт результат через Возврат. Второй способ — записать значение в реквизит формы внутри серверной процедуры: после вызова платформа сама вернёт обновлённые данные формы на клиент.

Типичная ситуация: программист дорабатывает документ, на клиенте пользователь выбирает контрагента, а нужно подставить его ИНН и договор. Обратиться к Контрагент.ИНН на клиенте нельзя — данные лежат в базе, а база доступна только серверу. Ниже — как такой обмен устроен в управляемом приложении 1С 8.3 и какими методами его делать без лишних вызовов.

Почему клиент и сервер в 1С разделены

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

Что доступно на клиенте:

  • ✅ примитивы: Строка, Число, Дата, Булево, Неопределено;
  • ✅ ссылки на объекты (СправочникСсылка, ДокументСсылка) — но только как идентификаторы, без чтения реквизитов через точку;
  • ✅ универсальные коллекции: Массив, Структура, Соответствие, а также их фиксированные варианты;
  • ✅ ДвоичныеДанные, УникальныйИдентификатор, данные формы (Объект, реквизиты формы).

Что живёт только на сервере:

  • ❌ Запрос, РезультатЗапроса, менеджеры справочников и документов;
  • ❌ СправочникОбъект, ДокументОбъект, наборы записей регистров;
  • ❌ ТаблицаЗначений, ДеревоЗначений.

Отсюда главное правило: с сервера на клиент уходят только значения типов, которые существуют на клиенте. Всё остальное перед возвратом надо преобразовать.

Директиву компиляции у каждой процедуры модуля формы пишите явно. Процедура без директивы в модуле управляемой формы считается клиентской, и вызов из неё запроса выдаст ошибку уже при проверке модуля.

Директивы и синтаксис вызова серверной функции

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

ДирективаЧто передаётся на серверКогда использовать
&НаСервереВсе данные формы туда и обратноНужно изменить реквизиты формы, табличные части, вызвать методы формы
&НаСервереБезКонтекстаТолько параметры вызоваНужно получить значение из базы по ссылке или параметрам
&НаКлиентеНаСервереБезКонтекстаНичего — вызывается там, откуда позвалиОбщие вычисления без обращения к базе
Общий модуль с флагами «Сервер» и «Вызов сервера»Только параметрыФункция нужна в нескольких формах

Пример: при выборе контрагента подставляем ИНН и КПП в реквизиты своего документа.

&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
	
	Реквизиты = РеквизитыКонтрагента(Объект.Контрагент);
	Если Реквизиты <> Неопределено Тогда
		Объект.ИНН = Реквизиты.ИНН;
		Объект.КПП = Реквизиты.КПП;
	КонецЕсли;
	
КонецПроцедуры

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

Функция без контекста не видит Объект и Элементы, поэтому ссылку передаём параметром. Зато по сети уходит одна ссылка, а не весь документ с табличными частями.

Если в функции вы пытаетесь обратиться к Объект или ЭтаФорма, а она помечена &НаСервереБезКонтекста, модуль не пройдёт синтаксический контроль. Либо передайте нужные значения параметрами, либо смените директиву на &НаСервере.

Вызов через общий модуль

Если та же функция нужна в пяти формах, вынесите её в общий модуль. В свойствах модуля включите флажки Сервер и Вызов сервера, функцию объявите с ключевым словом Экспорт. На клиенте вызов выглядит так: Реквизиты = РаботаСКонтрагентамиВызовСервера.РеквизитыКонтрагента(Объект.Контрагент);.

Модуль без флажка «Вызов сервера» с клиента не виден, даже если функция экспортная.

Параметры и возвращаемые значения: что можно передать

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

Возврат нескольких значений

Функция возвращает одно значение. Для нескольких — упакуйте их в Структура: ключи читаются на клиенте как свойства (Результат.Сумма). Если ключей заранее не знаете — Соответствие.

Второй вариант — параметры по ссылке. Параметр без Знач при вызове сервера тоже возвращается на клиент изменённым. Приём рабочий, но читается хуже: глядя на вызов, не видно, что переменная поменяется.

Таблица значений: как вернуть строки

ТаблицаЗначений на клиент не передаётся. Три пути:

  1. Массив структур — для небольших выборок, которые нужны в клиентском коде.
  2. Реквизит формы с типом ТаблицаЗначений — для данных, которые надо показать пользователю в таблице. Заполняется методом ЗначениеВРеквизитФормы в процедуре &НаСервере.
  3. Временное хранилище — для крупных объёмов и для передачи результата фонового задания.
&НаСервереБезКонтекста
Функция СтрокиДляКлиента(Таблица)
	
	Результат = Новый Массив;
	Для Каждого Строка Из Таблица Цикл
		Элемент = Новый Структура;
		Для Каждого Колонка Из Таблица.Колонки Цикл
			Элемент.Вставить(Колонка.Имя, Строка[Колонка.Имя]);
		КонецЦикла;
		Результат.Добавить(Элемент);
	КонецЦикла;
	
	Возврат Результат;
	
КонецФункции

Пример с реквизитом формы Остатки (тип ТаблицаЗначений, колонки созданы в редакторе формы):

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

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

Временное хранилище

ПоместитьВоВременноеХранилище(Значение, УникальныйИдентификатор) на сервере возвращает строку-адрес. Адрес — обычная строка, клиент получает её и читает данные через ПолучитьИзВременногоХранилища(Адрес). Второй параметр привязывает данные к форме: когда форма закроется, платформа их удалит.

Так удобно отдавать файл: на сервере собрали ДвоичныеДанные, положили в хранилище, на клиенте по адресу запустили сохранение файла.

Сколько стоит вызов сервера и как его сократить

Каждый вызов — это сетевой запрос. В локальной сети он почти незаметен, а у пользователя на удалённом подключении или в веб-клиенте десяток вызовов при открытии формы даёт заметную паузу.

Где теряется время:

  • 🐢 серверная процедура &НаСервере тащит туда и обратно весь документ — с табличной частью на тысячу строк это ощутимо;
  • 🐢 вызов сервера в цикле на клиенте: 200 строк — 200 запросов;
  • 🐢 отдельный вызов на каждый реквизит: сначала ИНН, потом КПП, потом договор.

Что помогает:

  1. Один вызов вместо нескольких. Соберите всё нужное в одну функцию и верните структурой.
  2. Без контекста везде, где форма не нужна. Передавайте ссылку и пару параметров, а не весь объект.
  3. Цикл переносите на сервер. Передайте массив ссылок одним параметром, обработайте его запросом, верните соответствие «ссылка → значение».
  4. Повторное использование значений. Для общего модуля с флагом «Вызов сервера» поставьте свойство Повторное использование возвращаемых значений = На время сеанса. Повторный вызов с теми же параметрами вернёт кешированный результат без обращения к серверу. Годится для настроек и констант, которые не меняются за сеанс. Возвращайте из такой функции ФиксированнаяСтруктура или ФиксированныйМассив, чтобы вызывающий код случайно не изменил кешированную копию.
  5. Параметры при открытии формы. Всё, что форма знает при создании, получайте в ПриСозданииНаСервере и записывайте в реквизиты. Этот обработчик выполняется в рамках серверного вызова открытия формы, отдельного вызова не нужно.

Увидеть лишние вызовы помогает замер производительности в режиме отладки: в Конфигураторе запустите Отладка → Замер производительности и посмотрите, сколько раз вызывается серверная функция на одно действие пользователя.

Какой способ вернуть данные на клиент выбрать?

Долгие операции: фоновое задание и опрос с клиента

Серверный вызов синхронный: пока он выполняется, интерфейс клиента ждёт. Расчёт на минуту превращается в «зависшую» форму. Сервер к тому же не может сам позвать клиента — инициатор всегда клиент.

Рабочая схема для длинных расчётов:

  1. Клиент вызывает сервер, тот создаёт адрес во временном хранилище и запускает фоновое задание.
  2. Сервер сразу возвращает адрес, форма остаётся отзывчивой.
  3. Фоновое задание кладёт результат по этому адресу.
  4. Клиент через обработчик ожидания раз в пару секунд спрашивает: задание завершилось?
  5. Когда завершилось — клиент забирает результат из хранилища.
// В форме: реквизиты АдресРезультата (Строка) и ИдентификаторЗадания (УникальныйИдентификатор)

&НаКлиенте
Процедура Рассчитать(Команда)
	ЗапуститьРасчетНаСервере();
	ПодключитьОбработчикОжидания("ПроверитьРасчет", 2, Ложь);
КонецПроцедуры

&НаСервере
Процедура ЗапуститьРасчетНаСервере()
	
	АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
	
	ПараметрыЗадания = Новый Массив;
	ПараметрыЗадания.Добавить(АдресРезультата);
	
	Задание = ФоновыеЗадания.Выполнить("РасчетыСервер.ДолгийРасчет", ПараметрыЗадания);
	ИдентификаторЗадания = Задание.УникальныйИдентификатор;
	
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьРасчет()
	
	Если Не ЗаданиеЗавершено(ИдентификаторЗадания) Тогда
		Возврат;
	КонецЕсли;
	
	ОтключитьОбработчикОжидания("ПроверитьРасчет");
	Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
	// дальше работа с результатом на клиенте
	
КонецПроцедуры

&НаСервереБезКонтекста
Функция ЗаданиеЗавершено(Идентификатор)
	
	Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
	Если Задание = Неопределено Тогда
		Возврат Истина;
	КонецЕсли;
	
	Если Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно Тогда
		ВызватьИсключение ПодробноеПредставлениеОшибки(Задание.ИнформацияОбОшибке);
	КонецЕсли;
	
	Возврат Задание.Состояние <> СостояниеФоновогоЗадания.Активно;
	
КонецФункции

В общем серверном модуле РасчетыСервер экспортная процедура принимает адрес и в конце пишет результат: ПоместитьВоВременноеХранилище(Результат, АдресРезультата);. Результат должен быть того же «клиентского» типа — массив, структура, двоичные данные.

В типовых конфигурациях на Библиотеке стандартных подсистем эту обвязку уже написали: функции общего модуля ДлительныеОперации запускают задание, а ДлительныеОперацииКлиент ждут его и показывают индикатор. Если конфигурация на БСП, берите их, а не пишите опрос вручную.

А как же Асинх и Ждать?

Ключевые слова Асинх и Ждать появились в платформе 8.3.18. Они упрощают работу с диалогами и асинхронными методами клиента: вопрос пользователю, выбор файла, копирование файлов. Серверный вызов от них асинхронным не становится — для долгого расчёта по-прежнему нужен фоновый процесс.

Ошибки при передаче данных и как их ловить

Тип нельзя передать на клиент

Самая частая ошибка: функция возвращает ТаблицаЗначений, РезультатЗапроса или ДокументОбъект, либо прячет такое значение внутри структуры. Платформа выдаст ошибку в момент возврата, а не при сохранении модуля, поэтому её легко пропустить на тестах с пустыми данными.

Как проверить: пройдите возвращаемую структуру в отладчике и посмотрите тип каждого элемента. Всё серверное преобразуйте — в массив структур, в адрес хранилища, в примитивы.

Чтение реквизита ссылки на клиенте

Код Объект.Контрагент.ИНН в клиентской процедуре вызывает ошибку: через ссылку на клиенте реквизиты не читаются. Решение — серверная функция, как в примере выше. В конфигурациях на БСП для этого есть ОбщегоНазначения.ЗначениеРеквизитаОбъекта и ЗначенияРеквизитовОбъекта, но это серверные функции: вызывайте их из своей функции &НаСервереБезКонтекста.

Исключение на сервере

Если серверный код вызвал ВызватьИсключение или упал на ошибке, исключение доходит до клиента и прерывает клиентскую процедуру. Перехватите его там, где это имеет смысл:

&НаКлиенте
Процедура Заполнить(Команда)
	
	Попытка
		ЗаполнитьНаСервере();
	Исключение
		Информация = ИнформацияОбОшибке();
		ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(Информация));
	КонецПопытки;
	
КонецПроцедуры

Пользователю показывайте краткое представление, а подробное (со стеком) пишите в журнал регистрации на сервере через ЗаписьЖурналаРегистрации.

Не глушите исключения пустым блоком Исключение … КонецПопытки. Форма продолжит работу с незаполненными данными, и ошибку потом будут искать по неверным остаткам.

Сообщения с сервера

Чтобы передать пользователю текст без прерывания работы, на сервере создайте Новый СообщениеПользователю, заполните Текст и вызовите Сообщить(). Сообщение придёт на клиент вместе с ответом сервера и появится в окне сообщений формы. Свойство Поле позволяет подсветить конкретный реквизит, например Объект.Контрагент.

Диагностика: выберите симптом

Обратное направление: из формы в форму и с клиента на сервер

Передача не ограничена парой «клиент — сервер одной формы». Ещё три сценария, которые встречаются постоянно:

  • 📨 Параметры открытия формы. ОткрытьФорму("Обработка.Расчет.Форма", Новый Структура("Организация", Объект.Организация)) — в открываемой форме параметр читается в ПриСозданииНаСервере через Параметры.Организация. Параметр надо добавить в список параметров формы в конфигураторе.
  • 📬 Результат закрытия формы. Подчинённая форма вызывает Закрыть(Результат), вызывающая получает значение в процедуре, указанной в ОписаниеОповещения при открытии.
  • 📢 Оповещение других форм. После записи данных на клиенте вызовите Оповестить("ИмяСобытия", Параметр); открытые формы поймают событие в обработчике ОбработкаОповещения и перечитают данные сами.

Направление «клиент → сервер» устроено зеркально: параметры серверного метода проходят ту же сериализацию. Значит, передать с клиента можно только то, что на клиенте существует, а данные формы &НаСервере получит и без параметров.

Частые вопросы

Можно ли вернуть на клиент ссылку на документ?
Да. Ссылка доступна на клиенте, её можно сравнить, передать в другую форму или открыть форму документа. Реквизиты по ссылке на клиенте не читаются — для них нужен серверный вызов.
Чем НаСервере отличается от НаСервереБезКонтекста по скорости?
НаСервере передаёт данные формы на сервер и обратно, включая табличные части. При большом документе разница видна сразу. Без контекста передаются только параметры вызова.
Работает ли временное хранилище в файловой базе?
Да, методы временного хранилища одинаково работают в файловом и клиент-серверном вариантах. Данные с привязкой к уникальному идентификатору формы удаляются после её закрытия.
Можно ли вызвать сервер из обработчика ожидания?
Можно, обработчик ожидания — обычная клиентская процедура. Ставьте разумный интервал, например 1–2 секунды, и отключайте обработчик, когда ответ получен.
Нужно ли кешировать данные на клиенте самому?
Для настроек, которые не меняются за сеанс, проще использовать общий модуль с повторным использованием возвращаемых значений. Свой кеш в реквизитах формы оправдан, когда данные нужны только этой форме.