Разработка в среде управляемого приложения 1С:Предприятие требует от программиста четкого понимания того, как распределяются данные между клиентской и серверной частями. Одной из классических проблем, с которой сталкиваются даже опытные разработчики при создании динамических интерфейсов и универсальных форм-конструкторов, является невозможность передачи объекта ОписаниеТипов с сервера на клиент, если внутри этого описания содержится тип ТаблицаЗначений или ДеревоЗначений. В этой статье мы подробно разберем, почему возникает эта ошибка, как устроены механизмы платформы "под капотом" и рассмотрим эффективные практические способы обхода данного ограничения.
Проанализируем стандартную ситуацию. Допустим, мы разрабатываем универсальную форму для программного создания реквизитов. На форме у нас присутствует реквизит с типом ДеревоЗначений, в одной из колонок которого хранится ОписаниеТипов для будущих реквизитов формы. На сервере в процедуре ПриСозданииНаСервере мы инициализируем строку дерева и присваиваем колонке типов следующее значение:
НовоеОписание = Новый ОписаниеТипов("ТаблицаЗначений");
СтрокаДерева.ОписаниеТипа = НовоеОписание;
При попытке открыть такую форму платформа аварийно завершает работу или выдает ошибку передачи данных между клиентом и сервером. На первый взгляд это кажется странным: ведь мы передаем не саму таблицу значений с ее данными, а всего лишь описание типа. Однако на физическом уровне происходит следующее:
ОписаниеТипов сериализуется в структуру, содержащую список входящих в него типов (в виде их внутренних идентификаторов или системных имен).ТаблицаЗначений, ДеревоЗначений, а также любые прикладные объекты (например, ДокументОбъект или СправочникОбъект), существуют и поддерживаются только на сервере. Тонкий и веб-клиент не имеют этих типов в своем адресном пространстве.ОписаниеТипов. Столкнувшись с типом ТаблицаЗначений, клиентский парсер понимает, что такого типа данных на клиенте не существует. Платформа не может создать корректный объект на стороне клиента, что приводит к критической ошибке и невозможности открыть форму.Таким образом, ограничение накладывается не на сам объект ОписаниеТипов, а на конкретный тип, который в нем содержится. Если бы мы использовали простые типы (например, Число, Строка) или типы ссылок (например, СправочникСсылка.Номенклатура), то передача прошла бы без каких-либо проблем, так как эти типы известны клиенту.
Теперь, когда мы понимаем физику процесса, рассмотрим наиболее эффективные варианты решения этой проблемы.
---
Если нам нужно сохранить сложную структуру метаданных (например, дерево настроек, где фигурирует ОписаниеТипов с серверными типами) между серверными вызовами и при этом избежать передачи этих данных на клиент, самым надежным решением будет использование Временного хранилища.
Рассмотрим этот процесс по шагам:
Шаг 1. Сохранение данных во временном хранилище на сервере:
Вместо того чтобы хранить тяжелое дерево с серверными описаниями типов прямо в реквизите формы, мы помещаем его во временное хранилище, привязывая к уникальному идентификатору формы. В реквизите формы на клиенте мы будем хранить только строковый адрес.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Создаем таблицу или дерево значений на сервере
ТаблицаНастроек = Новый ТаблицаЗначений;
ТаблицаНастроек.Колонки.Добавить("ИмяРеквизита", Новый ОписаниеТипов("Строка"));
ТаблицаНастроек.Колонки.Добавить("ТипДанных", Новый ОписаниеТипов("ОписаниеТипов"));
// Заполняем данными, включая проблемный тип "ТаблицаЗначений"
НоваяСтрока = ТаблицаНастроек.Добавить();
НоваяСтрока.ИмяРеквизита = "МояТаблица";
НоваяСтрока.ТипДанных = Новый ОписаниеТипов("ТаблицаЗначений");
// Помещаем таблицу во Временное хранилище
// АдресХранилища — это реквизит формы с типом "Строка"
АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаНастроек, УникальныйИдентификатор);
КонецПроцедуры
Шаг 2. Восстановление данных при повторном серверном вызове:
Когда нам на сервере снова понадобятся эти данные (например, при нажатии кнопки на форме), мы легко извлекаем их по сохраненному адресу:
&НаСервере
Процедура ВыполнитьОбработкуНаСервере()
// Проверяем наличие данных в хранилище
Если ЭтоАдресВременногоХранилища(АдресХранилища) Тогда
ТаблицаНастроек = ПолучитьИзВременногоХранилища(АдресХранилища);
// Теперь мы можем работать с исходной таблицей настроек и ее типами
Для Каждого СтрокаНастройки Из ТаблицаНастроек Цикл
// Выполняем логику генерации реквизитов
Если СтрокаНастройки.ТипДанных.СодержитТип(Тип("ТаблицаЗначений")) Тогда
// Наша логика обработки таблицы значений
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Данный подход полностью исключает появление ошибок сериализации, так как на клиент передается только короткая текстовая строка (адрес в памяти сервера), а сами сложные данные остаются в серверном кэше сеанса.
---
Если логика работы формы требует, чтобы информация о типах обязательно "путешествовала" вместе с формой на клиент (например, если на стороне клиента выполняется анализ доступных типов или их передача в другие клиентские процедуры), мы можем применить ручную сериализацию объекта ОписаниеТипов в строку XML.
Выясним, как это работает. Клиент не может десериализовать объект типа ОписаниеТипов, содержащий ТаблицаЗначений, но клиент без проблем может хранить обычную текстовую строку XML, так как платформа не валидирует типы внутри текста до тех пор, пока мы не попытаемся превратить эту строку обратно в объект.
Рассмотрим пример реализации функций сериализации и десериализации:
&НаСервереБезКонтекста
Функция СериализоватьОписаниеТиповВXML(Знач ТекущееОписаниеТипов) Экспорт
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
// Используем встроенный механизм сериализации платформы
ЗаписатьXML(ЗаписьXML, ТекущееОписаниеТипов);
Возврат ЗаписьXML.Закрыть();
КонецФункции
&НаСервереБезКонтекста
Функция ДесериализоватьОписаниеТиповИзXML(Знач СтрокаXML) Экспорт
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаXML);
// Восстанавливаем объект ОписаниеТипов на стороне сервера
Возврат ПрочитатьXML(ЧтениеXML);
КонецФункции
При создании формы на сервере мы конвертируем наше описание типа в строку и записываем в строковую колонку нашего дерева/таблицы на форме:
&НаСервере
Процедура ЗаполнитьРеквизитыФормы()
ОписаниеТЗ = Новый ОписаниеТипов("ТаблицаЗначений");
СтрокаДерева = ДеревоРеквизитовФормы.ПолучитьЭлементы().Добавить();
СтрокаДерева.ИмяРеквизита = "ДинамическаяТаблица";
// Сохраняем не сам объект, а его XML-представление
СтрокаДерева.ОписаниеТипаСтрокой = СериализоватьОписаниеТиповВXML(ОписаниеТЗ);
КонецПроцедуры
Когда на сервере нам понадобится восстановить исходный объект типа ОписаниеТипов для программного изменения реквизитов формы, мы просто вызываем функцию десериализации:
&НаСервере
Процедура СоздатьРеквизитыНаСервере()
Для Каждого СтрокаРеквизита Из ДеревоРеквизитовФормы.ПолучитьЭлементы() Цикл
// Восстанавливаем исходный объект ОписаниеТипов на сервере
ТекущееОписаниеТипов = ДесериализоватьОписаниеТиповИзXML(СтрокаРеквизита.ОписаниеТипаСтрокой);
// Используем его для добавления реквизитов формы
// (например, через ИзменитьРеквизиты)
КонецЦикла;
КонецПроцедуры
---
Для простых конфигураторов и форм динамического конструирования отличным решением будет полный отказ от хранения объектов ОписаниеТипов в реквизитах формы. Вместо этого мы можем хранить информацию о типах в виде массива строковых представлений (имен типов).
Строковые массивы без проблем передаются между клиентом и сервером. Конструировать же сам объект ОписаниеТипов мы будем непосредственно перед выполнением операции на сервере.
Проанализируем пример реализации такого подхода:
&НаСервере
Процедура ДобавитьТипВНастройки(ИмяТипа)
// МассивИменТипов — это реквизит формы с типом "СписокЗначений" или "Массив"
МассивИменТипов.Добавить(ИмяТипа); // Например, Добавить("ТаблицаЗначений") или Добавить("СправочникСсылка.Номенклатура")
КонецПроцедуры
&НаСервере
Процедура ПрименитьНастройкиТипов()
// Конструируем ОписаниеТипов на сервере перед созданием реквизитов
МассивДляОписания = Новый Массив;
Для Каждого ЭлементСписка Из МассивИменТипов Цикл
МассивДляОписания.Добавить(ЭлементСписка.Значение);
КонецЦикла;
НовоеОписаниеТипов = Новый ОписаниеТипов(МассивДляОписания);
// Используем созданное ОписаниеТипов в системных методах 1С
// ...
КонецПроцедуры
Этот способ максимально прост в реализации, снижает нагрузку на сеть при передаче данных формы и исключает любые конфликты десериализации на клиенте.
---
Если ваша задача состоит в том, чтобы отобразить таблицу или дерево на управляемой форме, помните, что на клиенте нельзя напрямую работать с объектами ТаблицаЗначений и ДеревоЗначений. Платформа предоставляет для этих целей специализированные типы-оболочки: ДанныеФормыКоллекция, ДанныеФормыДерево и ДанныеФормыСтруктура.
Для правильного вывода динамических таблиц на форму выполните следующие шаги:
ДанныеФормыКоллекция).ИзменитьРеквизиты(). При этом платформа сама позаботится о корректной трансформации типов между сервером и клиентом.ЗначениеВРеквизитФормы() и обратно — РеквизитФормыВЗначение().Пример корректного переноса серверной таблицы на форму:
&НаСервере
Процедура ЗаполнитьТаблицуНаФорме()
// Создаем физическую ТЗ на сервере
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Код");
ТЗ.Колонки.Добавить("Наименование");
// Заполняем данными
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Код = "001";
НоваяСтрока.Наименование = "Товар 1";
// Копируем данные в реквизит управляемой формы "ТаблицаНаФорме" (ДанныеФормыКоллекция)
ЗначениеВРеквизитФормы(ТЗ, "ТаблицаНаФорме");
КонецПроцедуры
Использование правильных абстракций (таких как коллекция данных формы) предотвращает возникновение непредвиденных ошибок при обмене данными и обеспечивает максимальное быстродействие интерфейса управляемого приложения.