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