При разработке внешних обработок на управляемых формах в системе «1С:Предприятие 8.3» программисты часто сталкиваются с неприятным поведением платформы. При первом нажатии на кнопку управляемой формы серверный вызов отрабатывает штатно, но при повторном нажатии на ту же кнопку система выбрасывает критическую ошибку: «Недопустимое значение параметра (параметр номер '1')» на строке с вызовом РеквизитФормыВЗначение("Объект"). Давайте подробно разберем, почему возникает эта ошибка, как устроены механизмы платформы «под капотом» и какими способами можно навсегда решить эту проблему.
Для начала проанализируем, что именно происходит в момент выполнения функции РеквизитФормыВЗначение. Этот метод берет данные, хранящиеся в контексте управляемой формы (в данном случае, в реквизите Объект типа ДанныеФормыСтруктура), и выполняет их сериализацию в полноценный прикладной объект (например, ВнешняяОбработкаОбъект), у которого доступны экспортные процедуры и функции его модуля.
Существует несколько основных причин, по которым этот процесс сбоит при повторном обращении:
ЛюбаяСсылка, то при первом преобразовании платформа успешно конструирует объект. Однако, если в процессе выполнения серверного алгоритма этот реквизит как-то меняется, очищается в Неопределено или заполняется ссылкой, платформа при повторном вызове теряет информацию о точном типе этого реквизита. Из-за динамической природы внешних обработок кэш типов платформы сбоит, и она не может сопоставить структуру данных формы с метаданными динамического объекта внешней обработки.ЗначениеВРеквизитФормы. На форме остается старое состояние структуры данных, которое при повторной попытке конвертации признается платформой невалидным.Рассмотрим подробнее пять практических способов решения этой проблемы — от простых обходных путей до профессиональных архитектурных решений.
Если вам необходимо работать в контексте формы и использовать именно модуль объекта, убедитесь, что вы правильно возвращаете измененные данные обратно на форму и принудительно очищаете переменную, содержащую объект, в конце процедуры. Это поможет операционной системе и платформе своевременно уничтожить ссылки в памяти.
Посмотрим на пример правильной организации кода в серверной процедуре модуля формы:
&НаСервере
Процедура ВыполнитьЗагрузкуНаСервере()
// 1. Получаем прикладной объект из реквизита формы
ОбъектВЗначение = РеквизитФормыВЗначение("Объект");
// 2. Вызываем экспортный метод модуля объекта
ОбъектВЗначение.ВыполнитьОбработкуДанных();
// 3. ОБЯЗАТЕЛЬНО возвращаем измененный объект обратно в реквизит формы
ЗначениеВРеквизитФормы(ОбъектВЗначение, "Объект");
// 4. Явно очищаем переменную для исключения зависания ссылок в памяти
ОбъектВЗначение = Неопределено;
КонецПроцедуры
Этот простой шаг часто помогает снять проблему блокировки кэша типов, если структура ваших реквизитов на форме не слишком сложна.
Метод РеквизитФормыВЗначение имеет второй, необязательный параметр — Тип. Если вы явно укажете платформе, в какой конкретно тип данных нужно преобразовать реквизит формы, она не будет пытаться угадать его по кэшу метаданных, что полностью исключит ошибку из-за реквизитов типа Любая ссылка.
Поскольку имя типа внешней обработки генерируется динамически, мы можем определить его на лету при первом запуске или обратиться к нему напрямую. Разберем по шагам, как это реализовать:
&НаСервере
Процедура ВыполнитьЗагрузкуСУказаниемТипа()
// Определяем точное имя типа нашей внешней обработки
// Оно имеет вид "ВнешняяОбработкаОбъект.ИмяВашейОбработки"
ИмяТипаВнешнейОбработки = "ВнешняяОбработкаОбъект." + ЭтотОбъект.Метаданные().Имя;
ТипОбъекта = Тип(ИмяТипаВнешнейОбработки);
// Передаем полученный тип во второй параметр метода
ОбъектВЗначение = РеквизитФормыВЗначение("Объект", ТипОбъекта);
// Выполняем алгоритм
ОбъектВЗначение.ПолучитьТаблицу();
ЗначениеВРеквизитФормы(ОбъектВЗначение, "Объект");
ОбъектВЗначение = Неопределено;
КонецПроцедуры
Этот способ является крайне надежным, так как жестко привязывает десериализацию к конкретному описанию типа из метаданных конфигурации или зарегистрированных во временном кэше внешних обработок.
Наиболее красивым и профессиональным решением с точки зрения архитектуры является полный отказ от передачи контекста всей формы на сервер. Вместо контекстного вызова &НаСервере, который гоняет по сети всю форму (со всеми ее элементами управления, оформлением и тяжелыми реквизитами), мы можем использовать легкий внеконтекстный метод &НаСервереБезКонтекста.
В этом случае мы передаем на сервер только сам реквизит Объект (структуру данных) и преобразуем его с помощью метода ДанныеФормыВЗначение. Давайте проанализируем этот паттерн:
&НаКлиенте
Процедура ЗагрузитьВТаблицу(Команда)
// Вызываем внеконтекстный серверный метод, передавая только данные реквизита "Объект"
РезультатТаблица = ПолучитьТаблицуНаСервереБезКонтекста(Объект);
// Обрабатываем возвращенный результат на клиенте (например, загружаем в ТЧ формы)
Объект.ТабличнаяЧасть.Загрузить(РезультатТаблица);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьТаблицуНаСервереБезКонтекста(ДанныеОбъекта)
// Преобразуем переданные данные формы в объект.
// Для этого обязательно указываем точный тип внешней обработки
ИмяТипа = "ВнешняяОбработкаОбъект.ИмяВашейОбработки"; // Замените на реальное имя вашей обработки
ОбработкаОбъект = ДанныеФормыВЗначение(ДанныеОбъекта, Тип(ИмяТипа));
// Вызываем экспортную функцию из модуля объекта внешней обработки
Возврат ОбработкаОбъект.ПолучитьТаблицуДанных();
КонецФункции
Преимущества данного подхода очевидны: скорость работы возрастает в разы, трафик между клиентом и сервером минимизируется, а платформа 1С гарантированно не столкнется с ошибками повторного кэширования контекста формы, так как самого контекста формы на сервере просто нет.
Если экспортный метод вашей обработки выполняет вспомогательные или чисто расчетные функции (например, парсинг Excel-файла или расчет показателей) и при этом ему не требуются все специфические данные, заполненные пользователем на форме, вы можете отказаться от преобразования текущего объекта формы.
Вместо этого мы можем программно создать в памяти «чистый» экземпляр этой же внешней обработки, передать в него только нужные параметры, получить результат и уничтожить этот объект. Посмотрим на пример реализации такого сценария:
&НаСервере
Процедура ЗагрузитьИзФайлаПрограммно(ИмяФайла, Лист)
// Получаем двоичные данные или путь к текущей запущенной обработке
// Например, если она подключена в справочнике ДополнительныеОтчетыИОбработки:
СсылкаНаОбработку = РеквизитФормыВЗначение("Объект").Ссылка;
// Или, если обработка запускается локально из файла, можно получить ее двоичные данные.
// Создаем чистый экземпляр обработки через менеджер внешних обработок:
ДвоичныеДанные = ПолучитьДвоичныеДанныеОбработки(СсылкаНаОбработку);
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
ИмяПодключеннойОбработки = ВнешниеОбработки.Подключить(АдресХранилища);
НовыйОбъект = ВнешниеОбработки.Создать(ИмяПодключеннойОбработки);
// Переносим настройки из нашей текущей формы в новый чистый объект
ЗаполнитьЗначенияСвойств(НовыйОбъект, Объект);
// Вызываем нужный метод
ТабЗнач = НовыйОбъект.ЗагрузитьМетодом_EXCEL1C(ИмяФайла, Лист);
// Загружаем полученные результаты обратно в реквизиты формы
Объект.ТабличнаяЧасть.Загрузить(ТабЗнач);
КонецПроцедуры
Этот паттерн полностью изолирует выполнение серверного кода от состояния текущей формы и на 100% страхует от любых ошибок сериализации и удержания ссылок в памяти.
Самый простой и быстрый способ, к которому прибегают разработчики в условиях жесткого дефицита времени — это перенос всех экспортных процедур и функций из модуля объекта внешней обработки непосредственно в модуль управляемой формы (с директивой компиляции &НаСервере).
При таком подходе вам вообще не придется использовать вызовы РеквизитФормыВЗначение и ДанныеФормыВЗначение. Все реквизиты обработки будут доступны вам напрямую через обращение к реквизиту формы Объект (например, Объект.ПутьКФайлу, Объект.ТабличнаяЧасть).
Плюсы переноса кода в форму:
Минусы переноса кода в форму:
Мы подробно проанализировали природу ошибки Недопустимое значение параметра (параметр номер '1') при вызове функции РеквизитФормыВЗначение во внешних обработках 1С. Основной корень проблемы кроется в динамической типизации внешних объектов платформой 1С и сложностях сопоставления сложных типов данных (таких как Любая ссылка) при повторных контекстных вызовах.
В качестве долгосрочного и профессионального решения мы рекомендуем использовать безконтекстные серверные вызовы (&НаСервереБезКонтекста) с явным указанием целевого типа данных в методе ДанныеФормыВЗначение. Это не только навсегда решит проблему повторных вызовов, но и значительно ускорит работу вашей внешней обработки.