Обмен данными между сервером и клиентом в управляемой форме 1С 8.3
Содержание
Данные с сервера 1С на клиент передают одним способом: клиентская процедура вызывает серверную функцию, а та возвращает значение через Возврат. Несколько значений упаковывают в Структура или Массив, большие объёмы кладут в реквизит формы или во временное хранилище.
Частое заблуждение новичков — записать результат в переменную модуля формы внутри процедуры &НаСервере и прочитать её на клиенте. Так не работает
: серверные переменные модуля формы живут только до конца вызова. На клиент попадает лишь то, что вернула функция, плюс данные формы, если вызов был контекстным.
Где выполняется код: клиент, сервер и директивы компиляции
В управляемом приложении клиент и сервер — разные процессы, часто на разных компьютерах. У каждого своя память и свой набор доступных типов. Связь между ними — вызов сервера: клиент отправляет параметры, сервер выполняет код и отправляет ответ.
Где выполнится процедура модуля формы, задаёт директива перед ней.
| Директива | Где выполняется | Что уходит на сервер |
|---|---|---|
&НаКлиенте | на клиенте | ничего, вызова сервера нет |
&НаСервере | на сервере | параметры + все данные формы (реквизиты, Объект) |
&НаСервереБезКонтекста | на сервере | только параметры |
&НаКлиентеНаСервереБезКонтекста | там, откуда вызвали | вызова сервера нет, код компилируется в обоих контекстах |
У общих модулей вместо директив — флажки в свойствах: Клиент (управляемое приложение), Сервер, Вызов сервера, Глобальный, Привилегированный. Чтобы клиентский код мог вызвать функцию общего модуля на сервере, у модуля должны стоять флажки Сервер и Вызов сервера. Без Вызов сервера функция видна только серверному коду.
💡 Удобная схема из типовых конфигураций: серверная логика лежит в модуле с флажком Сервер, а тонкая обёртка для клиента — в отдельном модуле с суффиксом ВызовСервера в имени. Так видно, какие функции открыты для вызова с клиента.
Как вернуть значение из серверной функции
Процедура на клиенте ничего не получает назад. Если нужен результат, серверный метод объявляют как Функция и возвращают значение через Возврат.
&НаКлиенте
Процедура ПоказатьКарточку(Команда)
Данные = ДанныеКонтрагента(Объект.Контрагент);
Если Данные = Неопределено Тогда
Возврат;
КонецЕсли;
ПоказатьПредупреждение(, СтрШаблон("%1, ИНН %2", Данные.Наименование, Данные.ИНН));
КонецПроцедуры
&НаСервереБезКонтекста
Функция ДанныеКонтрагента(Знач Контрагент)
Если Не ЗначениеЗаполнено(Контрагент) Тогда
Возврат Неопределено;
КонецЕсли;
Результат = Новый Структура("Наименование, ИНН");
ЗаполнитьЗначенияСвойств(Результат, Контрагент);
Возврат Результат;
КонецФункции
Что здесь происходит:
- Клиент передаёт ссылку на контрагента — это лёгкий тип, он сериализуется в несколько байт.
- Сервер читает реквизиты объекта и раскладывает их по ключам структуры.
- Структура возвращается на клиент одним пакетом.
Почему не обратиться к Объект.Контрагент.ИНН прямо на клиенте? В тонком и веб-клиенте ссылка — это только идентификатор. Читать её реквизиты через точку на клиенте нельзя, за ними всегда нужен сервер.
Несколько значений за один вызов
Не делайте три функции «вернуть наименование», «вернуть ИНН», «вернуть договор». Каждая — отдельный запрос по сети. Одна функция со структурой на выходе быстрее и проще в сопровождении.
Для набора однотипных записей подходит Массив структур, для поиска по ключу — Соответствие. Если клиенту не нужно менять результат, верните ФиксированнаяСтруктура или ФиксированныйМассив: так случайная запись в него сразу вызовет ошибку.
Передача параметров на сервер и что проходит через границу
Параметры серверной функции по умолчанию передаются по ссылке. При вызове с клиента это значит: после выполнения платформа отправляет значения параметров обратно на клиент, даже если сервер их не трогал. Лишний трафик в каждом вызове.
Ключевое слово Знач перед параметром отключает обратную передачу. Правило простое: все параметры функций, которые вызываются с клиента, объявляйте через Знач, если вам не нужен возврат через параметр.
&НаСервереБезКонтекста
Функция СуммаПоДокументам(Знач МассивСсылок, Знач ДатаОтчета)
Какие типы можно передать между клиентом и сервером
Через границу проходят только сериализуемые значения, доступные на клиенте.
| Можно передавать | Нельзя передавать |
|---|---|
| Число, Строка, Дата, Булево, Неопределено | Запрос, РезультатЗапроса, ВыборкаИзРезультатаЗапроса |
Ссылки на объекты (СправочникСсылка, ДокументСсылка) | ТаблицаЗначений, ДеревоЗначений |
Структура, Массив, Соответствие, их фиксированные версии | СправочникОбъект, ДокументОбъект |
СписокЗначений, ХранилищеЗначения, ДвоичныеДанные | наборы записей регистров, менеджеры |
УникальныйИдентификатор, значения перечислений | ЧтениеXML, ЗаписьJSON и прочие потоковые объекты |
Структура с таблицей значений внутри тоже не пройдёт. Платформа проверяет содержимое коллекции целиком, а не только верхний уровень.
Если функция возвращает объект недопустимого типа, ошибка возникает в момент возврата на клиент, а не на строке, где этот объект создан. При отладке смотрите на последнюю строку Возврат.
Запрос на сервере и передача результата выборки клиенту
Класс Запрос существует только на сервере. Поэтому типичная задача — выполнить запрос и превратить результат в то, что клиент сможет прочитать. Есть три рабочих способа.
Способ 1. Массив структур
Подходит для небольших списков: подбор, выпадающий список, проверка перед действием.
&НаСервереБезКонтекста
Функция НайтиКонтрагентов(Знач Шаблон)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 20
| Контрагенты.Ссылка КАК Ссылка,
| Контрагенты.Наименование КАК Наименование,
| Контрагенты.ИНН КАК ИНН
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Наименование ПОДОБНО &Шаблон
| И НЕ Контрагенты.ПометкаУдаления
|
|УПОРЯДОЧИТЬ ПО
| Наименование";
Запрос.УстановитьПараметр("Шаблон", "%" + Шаблон + "%");
Результат = Новый Массив;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Строка = Новый Структура("Ссылка, Наименование, ИНН");
ЗаполнитьЗначенияСвойств(Строка, Выборка);
Результат.Добавить(Строка);
КонецЦикла;
Возврат Результат;
КонецФункции
ПЕРВЫЕ 20 здесь не для красоты. Клиенту редко нужна тысяча строк, а каждая лишняя строка — это байты в сети и память на клиенте. Если пользователь вводит в шаблон символы % или _, экранируйте их, иначе ПОДОБНО поймёт их как подстановочные знаки.
В конфигурациях на БСП есть готовая функция ОбщегоНазначения.ТаблицаЗначенийВМассив. Она делает то же самое для результата Выгрузить(), но вызывать её нужно на сервере.
Способ 2. Реквизит формы
Если данные нужно показать в таблице на форме, выгружайте их сразу в реквизит формы типа ТаблицаЗначений. На клиенте он виден как ДанныеФормыКоллекция, и платформа сама доставит его на клиент вместе с формой.
&НаСервере
Процедура ЗаполнитьОстаткиНаСервере()
Запрос = Новый Запрос(ТекстЗапросаОстатков());
Запрос.УстановитьПараметр("Склад", Склад);
ТаблицаОстатков.Загрузить(Запрос.Выполнить().Выгрузить());
КонецПроцедуры
Колонки реквизита должны совпадать по именам с полями запроса. Лишние поля запроса просто не загрузятся, недостающие колонки останутся пустыми.
Вызов здесь контекстный (&НаСервере): без него сервер не увидит реквизит ТаблицаОстатков. Для перевода сложных объектов есть пары РеквизитФормыВЗначение / ЗначениеВРеквизитФормы и ДанныеФормыВЗначение / ЗначениеВДанныеФормы.
Способ 3. Временное хранилище
Для крупных данных — файлов, двоичных данных, результатов отчётов — используйте ПоместитьВоВременноеХранилище. Функция возвращает адрес-строку. Клиент получает только адрес, а данные забирает, когда они реально нужны.
&НаСервереБезКонтекста
Функция ПодготовитьФайл(Знач Документ, Знач ИдентификаторФормы)
Данные = СформироватьДвоичныеДанные(Документ);
Возврат ПоместитьВоВременноеХранилище(Данные, ИдентификаторФормы);
КонецФункции
На клиенте адрес передают в ПолучитьИзВременногоХранилища или в методы получения файлов. Вторым параметром передавайте УникальныйИдентификатор формы: тогда значение живёт, пока форма открыта. Без него платформа вправе удалить значение уже после окончания вызова.
Обработка ошибок при вызове сервера
Исключение, вызванное на сервере, долетает до клиента. Если клиентский код не перехватил его через Попытка … Исключение, пользователь увидит окно с текстом ошибки, а выполнение процедуры прервётся.
Есть два подхода, и выбор зависит от того, ожидаемая это ситуация или сбой.
Ожидаемая ситуация — результат со статусом
Не нашли курс, не заполнен склад, документ уже проведён — это нормальный ход работы. Удобнее вернуть структуру с признаком успеха и текстом для пользователя.
&НаСервереБезКонтекста
Функция КурсНаДату(Знач Валюта, Знач Дата)
Результат = Новый Структура("Успешно, Курс, ТекстОшибки", Ложь, 0, "");
Попытка
Срез = РегистрыСведений.КурсыВалют.СрезПоследних(Дата, Новый Структура("Валюта", Валюта));
Если Срез.Количество() = 0 Тогда
ВызватьИсключение СтрШаблон("Нет курса валюты %1 на %2", Валюта, Формат(Дата, "ДЛФ=D"));
КонецЕсли;
Результат.Курс = Срез[0].Курс;
Результат.Успешно = Истина;
Исключение
Инфо = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации("Получение курса", УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(Инфо));
Результат.ТекстОшибки = КраткоеПредставлениеОшибки(Инфо);
КонецПопытки;
Возврат Результат;
КонецФункции
На клиенте остаётся проверить Успешно и показать ТекстОшибки. Подробности со стеком вызовов ушли в журнал регистрации — администратор найдёт их там, а пользователь не увидит технический мусор.
Сбой — исключение до клиента
Если ошибка означает, что продолжать нельзя, не глушите её на сервере. Пусть она дойдёт до клиента, а там перехватите её и выведите понятное сообщение.
&НаКлиенте
Процедура Провести(Команда)
Попытка
ПровестиНаСервере();
Исключение
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
Сообщение.Сообщить();
КонецПопытки;
КонецПроцедуры
Пустой блок Исключение … КонецПопытки без записи в журнал — худший вариант. Ошибка исчезает, данные остаются в непонятном состоянии, а найти причину потом почти невозможно.
В новых версиях платформы для представления ошибок появились методы объекта ОбработкаОшибок. Глобальные КраткоеПредставлениеОшибки и ПодробноеПредставлениеОшибки при этом работают, но в свежем коде лучше следовать стандарту вашей конфигурации.
Как сократить число серверных вызовов
Каждый вызов сервера — это обмен по сети. В локальной сети он занимает миллисекунды, через интернет или веб-клиент — десятки и сотни миллисекунд. Десять вызовов в обработчике изменения поля пользователь заметит.
Главные приёмы:
- 🔹 Один вызов вместо нескольких. Соберите всё, что нужно обработчику, в одну серверную функцию со структурой на выходе.
- 🔹
&НаСервереБезКонтекставместо&НаСервере. Контекстный вызов везёт на сервер и обратно всю форму, включая табличные части. На документе с 500 строками товаров разница ощутима. - 🔹 Заполнение при создании формы. Данные, которые нужны сразу, получайте в
ПриСозданииНаСервереи кладите в реквизиты. Это происходит в рамках открытия формы, без отдельного вызова. - 🔹 Кэш на клиенте. Редко меняющиеся значения (настройки, константы) достаточно получить один раз и хранить в клиентской переменной модуля формы.
- 🔹 Повторное использование. У общего модуля можно включить свойство
Повторное использование возвращаемых значений—На время вызоваилиНа время сеанса. Платформа запомнит результат функции для одинаковых параметров. - 🔹 Расчёт на клиенте. Сумма строки = количество × цена не требует сервера. Код в
&НаКлиентеНаСервереБезКонтекставыполняется там, откуда вызван.
Кэш на время сеанса не сбрасывается сам. Если закешировать значение, которое пользователь может изменить, он увидит старое до перезапуска. Кэшируйте только то, что действительно стабильно.
Как измерить
В Конфигураторе запустите отладку и включите Отладка → Замер производительности. Выполните действие в клиенте и остановите замер. В результатах видно, сколько раз выполнялась каждая строка и сколько времени ушло на серверные вызовы. Строки с вызовами сервера внутри цикла — первые кандидаты на переделку.
Для операций дольше нескольких секунд вызов сервера не подходит вовсе: клиент будет висеть. Такую работу запускают фоновым заданием, а на клиент возвращают его идентификатор и адрес хранилища с результатом. В БСП для этого есть подсистема длительных операций.
Частые ошибки и как их избежать
| Ошибка | Что происходит | Как правильно |
|---|---|---|
| Вызов сервера в цикле по строкам табличной части | 300 строк = 300 обменов по сети | Передать массив строк одним вызовом |
&НаСервере там, где форма не нужна | Вся форма гоняется туда и обратно | &НаСервереБезКонтекста с параметрами |
Возврат ТаблицаЗначений или РезультатЗапроса | Ошибка в момент возврата на клиент | Массив структур или реквизит формы |
Параметры без Знач | Значения передаются обратно без нужды | Знач перед каждым параметром |
| Обращение к реквизиту ссылки на клиенте | Ошибка в тонком клиенте | Получить значение серверной функцией |
| Временное хранилище без идентификатора формы | Адрес пустой к моменту чтения | Передавать УникальныйИдентификатор формы |
Отдельная ловушка — толстый клиент. В нём часть серверных типов доступна на клиенте, и код, который «работает у разработчика», падает у пользователя в тонком или веб-клиенте. Проверяйте доработки хотя бы в тонком клиенте перед выкладкой.