Обмен данными между сервером и клиентом в управляемых формах 1С 8.3
Содержание
Сам объект (ДокументОбъект, СправочникОбъект) с сервера на клиент 1С передать нельзя: на тонком и веб-клиенте такого типа просто нет. Передают данные объекта: ссылку, реквизиты формы, Структуру с нужными полями, массив структур вместо таблицы значений или адрес во временном хранилище.
Какой способ выбрать, зависит от объёма и от того, сколько живут данные. Пара полей — возвращаемое значение функции в Структуре. Документ целиком для редактирования — реквизит формы Объект. Большая таблица или файл, которые нужны в нескольких вызовах, — ПоместитьВоВременноеХранилище.
Как устроено взаимодействие клиента и сервера в 1С 8.3
В управляемом приложении код модуля формы выполняется в двух местах. Где именно — задаёт директива компиляции перед процедурой.
| Директива | Где выполняется | Что видит |
|---|---|---|
&НаКлиенте | на компьютере пользователя | реквизиты формы, клиентские типы |
&НаСервере | на сервере 1С | форму целиком, серверные типы |
&НаСервереБезКонтекста | на сервере 1С | только свои параметры |
&НаКлиентеНаСервереБезКонтекста | там, откуда вызвали | только общие для обеих сторон типы |
Каждый вызов серверной процедуры с клиента — это сетевой запрос. Параметры и возвращаемое значение платформа сериализует, передаёт и восстанавливает на другой стороне. Ссылок на один и тот же объект в памяти у клиента и сервера нет: на каждой стороне живёт своя копия.
Отсюда три следствия:
- 📦 передать можно только то, что умеет сериализоваться и существует на обеих сторонах;
- 🔁 изменить значение на сервере и увидеть это на клиенте можно только через возврат или через параметр без
Знач; - 🐢 чем больше вызовов и чем тяжелее данные, тем медленнее форма, особенно через интернет и в веб-клиенте.
&НаСервере при вызове гонит туда и обратно все реквизиты формы. Если серверному коду форма не нужна, ставьте &НаСервереБезКонтекста — запрос становится легче.
Какие типы пересекают границу
| Можно передавать | Нельзя передавать |
|---|---|
| Строка, Число, Дата, Булево, Неопределено | ДокументОбъект, СправочникОбъект и другие объекты |
Ссылки: ДокументСсылка, СправочникСсылка, перечисления | ТаблицаЗначений, ДеревоЗначений |
Структура, Массив, Соответствие и их фиксированные версии | Запрос, РезультатЗапроса, ВыборкаИзРезультатаЗапроса |
СписокЗначений, УникальныйИдентификатор | менеджеры регистров, наборы записей |
ДвоичныеДанные, ТабличныйДокумент | ОписаниеОповещения (живёт только на клиенте) |
Правило проверяется рекурсивно. Структура, внутри которой лежит ТаблицаЗначений, уже не пройдёт, и весь вызов упадёт.
Параметры серверных процедур: как передать значение и получить его обратно
Самый простой способ вернуть данные с сервера — функция. Возвращаемое значение платформа сама отправит на клиент.
&НаКлиенте
Процедура ЗаполнитьИтоги(Команда)
Итоги = ПолучитьИтогиНаСервере(Объект.Ссылка);
СуммаВсего = Итоги.Сумма;
КоличествоСтрок = Итоги.Количество;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьИтогиНаСервере(Знач Ссылка)
Запрос = Новый Запрос(
"ВЫБРАТЬ
| ЕСТЬNULL(СУММА(Т.Сумма), 0) КАК Сумма,
| КОЛИЧЕСТВО(Т.НомерСтроки) КАК Количество
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Т
|ГДЕ
| Т.Ссылка = &Ссылка");
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();
Возврат Новый Структура("Сумма, Количество", Выборка.Сумма, Выборка.Количество);
КонецФункции
Запрос и Выборка остались на сервере. На клиент ушла только Структура из двух чисел.
Зачем ставить Знач в серверных процедурах
Параметр без Знач при вызове с клиента работает «туда и обратно». Платформа передаёт значение на сервер, а после выполнения сериализует его ещё раз и возвращает на клиент. Так можно получить несколько результатов через параметры:
&НаСервереБезКонтекста
Процедура ПроверитьДокумент(Ссылка, ТекстОшибки, ЕстьОшибки)
ТекстОшибки = "";
ЕстьОшибки = Не ЗначениеЗаполнено(Ссылка);
Если ЕстьОшибки Тогда
ТекстОшибки = "Документ не записан";
КонецЕсли;
КонецПроцедуры
Но если параметр нужен только на входе, обратная передача — лишний трафик. Для большого массива или длинной строки это заметно. Поэтому входные параметры серверных процедур объявляйте с Знач. Это же защищает от путаницы: никто не будет ждать, что изменение параметра «доедет» до клиента.
Вместо нескольких выходных параметров удобнее вернуть одну Структуру. Код читается проще, а лишний параметр не превращается в ловушку при доработке.
Один вызов вместо пяти
Типичная ошибка производительности — серия серверных вызовов подряд: сначала получить склад, потом организацию, потом цену. Каждый — отдельный сетевой запрос. Соберите всё в одну серверную функцию и верните Структуру. Для пользователя на медленном канале разница ощутима: пять обращений к серверу превращаются в одно.
Передача ссылки на документ и чтение его реквизитов
Ссылку на документ или элемент справочника передавать можно и нужно — это самый лёгкий способ «передать объект». На клиент уходит идентификатор и представление, сам документ не читается.
Но прочитать реквизит через точку на клиенте не получится. Код Ссылка.Дата или Ссылка.Контрагент.ИНН в процедуре &НаКлиенте упадёт с ошибкой Поле объекта не обнаружено (Дата). Реквизиты нужно получить на сервере:
&НаСервереБезКонтекста
Функция РеквизитыДокумента(Знач Ссылка, Знач ИменаРеквизитов)
Результат = Новый Структура(ИменаРеквизитов);
Если Не ЗначениеЗаполнено(Ссылка) Тогда
Возврат Результат;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = СтрШаблон("ВЫБРАТЬ %1 ИЗ %2 ГДЕ Ссылка = &Ссылка",
ИменаРеквизитов, Ссылка.Метаданные().ПолноеИмя());
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ЗаполнитьЗначенияСвойств(Результат, Выборка);
КонецЕсли;
Возврат Результат;
КонецФункции
Вызов с клиента: Данные = РеквизитыДокумента(ТекущийДокумент, "Дата, Номер, Контрагент"); — и дальше Данные.Номер, Данные.Контрагент.
Имена реквизитов в такой функции подставляются в текст запроса. Не принимайте их от пользователя и не собирайте из внешних данных — передавайте только константные строки из своего кода.
В типовых конфигурациях на базе БСП уже есть готовые серверные функции получения реквизитов в общем модуле ОбщегоНазначения. Если конфигурация на БСП, лучше использовать их, чем писать свою.
Документ целиком в форме
Когда пользователь редактирует документ, «объектом на клиенте» служит основной реквизит формы Объект. Его тип на клиенте — ДанныеФормыСтруктура, это не ДокументОбъект. Методов Записать() или Заполнить() у него нет.
Чтобы вызвать метод объекта, переходите на сервер и превращайте данные формы в объект:
&НаСервере
Процедура ПересчитатьНаСервере()
Документ = РеквизитФормыВЗначение("Объект");
Документ.ЗаполнитьПоОснованию(Документ.ДокументОснование); // экспортный метод модуля объекта
ЗначениеВРеквизитФормы(Документ, "Объект");
КонецПроцедуры
Здесь нужна директива &НаСервере: без контекста формы РеквизитФормыВЗначение недоступен. Метод ЗаполнитьПоОснованию в примере — условный экспортный метод модуля объекта, подставьте свой.
Структуры, массивы и соответствия как контейнер данных
Когда нужно передать «объект» за пределами формы — например, в общий клиентский модуль или в форму выбора, — соберите данные в универсальную коллекцию.
| Коллекция | Когда подходит | Тонкость |
|---|---|---|
Структура | фиксированный набор именованных полей | ключ — только допустимое имя переменной |
Массив | список однотипных значений | доступ по индексу с нуля |
Соответствие | ключи произвольного типа, например ссылки | ключ тоже должен сериализоваться |
ФиксированнаяСтруктура | данные только для чтения | изменить на клиенте нельзя |
СписокЗначений | список с пометками и представлениями | удобен для выбора пользователем |
Пример: передать документ с табличной частью как структуру с массивом строк.
&НаСервереБезКонтекста
Функция ДанныеДокументаДляКлиента(Знач Ссылка)
Результат = Новый Структура("Номер, Дата, Строки");
ЗаполнитьЗначенияСвойств(Результат, Ссылка, "Номер, Дата");
Результат.Строки = Новый Массив;
Для Каждого СтрокаТЧ Из Ссылка.Товары Цикл
Результат.Строки.Добавить(Новый Структура("Номенклатура, Количество, Сумма",
СтрокаТЧ.Номенклатура, СтрокаТЧ.Количество, СтрокаТЧ.Сумма));
КонецЦикла;
Возврат Результат;
КонецФункции
На сервере чтение реквизитов через точку от ссылки работает. Платформа прочитает объект из базы, поэтому для больших документов эффективнее запрос с отбором нужных полей.
Соответствие с ключом-ссылкой удобно для быстрого поиска на клиенте: Цены.Получить(Номенклатура) вместо перебора массива.
Временное хранилище и глобальные параметры клиента
ПоместитьВоВременноеХранилище
Временное хранилище — это область на сервере, куда кладут значение и получают адрес-строку. Клиент держит у себя только адрес, а данные остаются на сервере. Это решает две задачи: не гонять тяжёлые данные по сети при каждом вызове и передать то, что на клиенте не существует, например ТаблицуЗначений.
&НаСервере
Процедура ПодготовитьДанныеНаСервере()
Таблица = ПолучитьОстатки(); // ТаблицаЗначений
АдресОстатков = ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
КонецПроцедуры
&НаСервереБезКонтекста
Функция СуммаОстатков(Знач Адрес)
Таблица = ПолучитьИзВременногоХранилища(Адрес);
Возврат Таблица.Итог("Сумма");
КонецФункции
АдресОстатков — строковый реквизит формы. Вторым параметром передан УникальныйИдентификатор формы: значение проживёт, пока открыта форма. Без идентификатора рассчитывайте только на текущий вызов сервера — в следующем вызове данных может уже не быть.
- 🧹 освободить память раньше —
УдалитьИзВременногоХранилища(Адрес); - 🔎 проверить, что строка — адрес, —
ЭтоАдресВременногоХранилища(Адрес); - 📁 тот же механизм используют для загрузки файлов с клиента и для результатов фоновых заданий.
На клиенте ПолучитьИзВременногоХранилища тоже работает, но только если лежащее там значение существует на клиенте. Достать на клиенте таблицу значений не выйдет.
Глобальные значения на клиенте
Иногда данные нужны не одной форме, а всему клиентскому сеансу: настройки пользователя, признак режима, кэш справочника. Варианты:
- 🌐
ПараметрыПриложения— глобальноеСоответствие, доступное в клиентском коде любой формы. БСП хранит там свои клиентские кэши, поэтому ключи давайте с префиксом своей подсистемы; - 📌 экспортные переменные модуля управляемого приложения — работают, но засоряют глобальный контекст;
- 🔐
ПараметрыСеанса— серверная сторона. На клиенте их нет, значение получают серверной функцией.
Заполнять глобальный кэш удобно при старте: один серверный вызов возвращает ФиксированнуюСтруктуру, клиент кладёт её в ПараметрыПриложения и дальше читает без обращения к серверу.
Как передать таблицу значений с сервера на клиент
ТаблицаЗначений на тонком клиенте не существует. Код Новый ТаблицаЗначений в клиентской процедуре даст ошибку Тип не определен (ТаблицаЗначений). Способов обойти ограничение три.
| Способ | Когда выбирать | Минус |
|---|---|---|
Реквизит формы типа ТаблицаЗначений | таблицу надо показать пользователю | работает только в форме |
| Массив структур | данные нужны в клиентском коде | копия, объём растёт с числом строк |
| Временное хранилище | таблица большая, обрабатывается на сервере | клиент видит только адрес |
Реквизит формы
Создайте реквизит формы с типом ТаблицаЗначений и колонками. На клиенте он виден как ДанныеФормыКоллекция: перебор строк, Добавить(), Удалить(), Количество() доступны. Заполняется на сервере:
&НаСервере
Процедура ЗаполнитьТаблицуНаСервере()
Таблица = ПолучитьОстатки();
ЗначениеВРеквизитФормы(Таблица, "ТаблицаОстатков");
КонецПроцедуры
Обратно — РеквизитФормыВЗначение("ТаблицаОстатков"), чтобы снова получить на сервере полноценную таблицу с методами Свернуть(), Итог(), Найти().
Массив структур
Универсальная функция, которая превращает любую таблицу в массив структур по именам колонок:
&НаСервереБезКонтекста
Функция ТаблицаВМассив(Таблица)
Результат = Новый Массив;
ИменаКолонок = Новый Массив;
Для Каждого Колонка Из Таблица.Колонки Цикл
ИменаКолонок.Добавить(Колонка.Имя);
КонецЦикла;
Для Каждого СтрокаТаблицы Из Таблица Цикл
Элемент = Новый Структура;
Для Каждого ИмяКолонки Из ИменаКолонок Цикл
Элемент.Вставить(ИмяКолонки, СтрокаТаблицы[ИмяКолонки]);
КонецЦикла;
Результат.Добавить(Элемент);
КонецЦикла;
Возврат Результат;
КонецФункции
Функцию вызывайте из другой серверной функции, а результат возвращайте клиенту. Значения в колонках тоже должны сериализоваться: колонка с типом Произвольный, где лежит объект, сломает вызов.
Не передавайте на клиент таблицы на десятки тысяч строк. Форма зависнет на сериализации, а в веб-клиенте ответ может не дойти. Отбирайте и сворачивайте данные на сервере, а на клиент отдавайте итог или страницу.
Ошибки сериализации: почему значение не передаётся
Ошибки при передаче почти всегда сводятся к одному: значение не существует на другой стороне или не умеет сериализоваться. Платформа сообщает об этом в момент вызова, и текст указывает на тип.
Как найти «виновника» в сложной структуре
Когда возвращается большая Структура с вложенными коллекциями, сообщение об ошибке не всегда называет конкретное поле. Порядок поиска:
- Поставьте точку останова на строке
Возвратв серверной функции. - В окне вычисления выражений раскройте возвращаемое значение и пройдите по ключам.
- Ищите типы из правого столбца таблицы в первом разделе:
ТаблицаЗначений,...Объект,РезультатЗапроса,ОписаниеОповещения. - Временно уберите подозрительное поле из структуры и повторите вызов — если ошибка ушла, виновник найден.
Нестандартные типы: JSON и XDTO
Если значение нельзя передать штатно, его можно превратить в строку на сервере. Для простых коллекций подходит JSON (ЗаписатьJSON в ЗаписьJSON), для типов платформы — СериализаторXDTO. Клиент получает строку и при необходимости разбирает её ПрочитатьJSON. Это обходной путь: прибегайте к нему, когда структура данных заранее неизвестна, а не вместо нормального возврата Структуры.