Работа с прикрепленными файлами — одна из самых частых задач при разработке на платформе 1С:Предприятие 8.3. Однако из-за разделения контекста выполнения кода на клиентский и серверный программисты часто сталкиваются со сложностями. Типичные симптомы неправильной реализации — зависание интерфейса, ошибки доступа к локальным путям на сервере или критическая ошибка веб-сервера "HTTP: Not acceptable Ошибка при работе с ресурсом /e1cib/files?cmd=GetInfoBaseFileURL".
В этой статье мы подробно разберем, почему возникают подобные проблемы, как устроена работа с временным хранилищем и как реализовать надежный, современный механизм загрузки и открытия файлов в управляемых формах 1С.
Прежде чем перейти к правильному коду, проанализируем ситуацию, описанную в сообщениях форума. Автор темы пытался передать на сервер локальный путь к каталогу документов, получить там файл по имени, поместить его во временное хранилище и вернуть на клиент. В процессе возникла ошибка веб-сервера HTTP: Not acceptable.
Эта ошибка указывает на то, что клиентское приложение (особенно при работе через веб-клиент) попыталось обратиться к временному хранилищу по некорректному или пустому адресу. Разберем цепочку действий, которая привела к сбою:
"C:\Users\Username\Documents\").СтруктураФайла = Справочники.ХранилищеФайлов.НайтиПоНаименованию(ИмяФайла);. Но вместо имени файла туда попал путь к каталогу!Файлы у пустой ссылки система вернула Неопределено.Неопределено было помещено во временное хранилище. Адрес этого пустого хранилища вернулся на клиент.НачатьПолучениеФайлов() попытался скачать данные по этому некорректному адресу, веб-сервер выдал ошибку 406 Not Acceptable, так как запрашиваемый ресурс не содержал валидного файла.Кроме того, старые методы работы с файлами (такие как НачатьПолучениеФайлов, НачатьПомещениеФайла) требуют громоздких описаний оповещений и ручного контроля временных папок. Рассмотрим, как решить эту задачу изящно и современно.
Начиная с версии платформы 1С 8.3.15, разработчикам доступен оптимизированный метод асинхронного помещения файлов на сервер. Вместо ручного создания двоичных данных на клиенте и их записи во временные файлы, мы будем использовать метод НачатьПомещениеФайлаНаСервер().
Рассмотрим по шагам правильную схему загрузки файла в справочник-хранилище.
Шаг 1. Клиентская часть. Вызов диалога выбора файла
На клиенте мы инициируем интерактивный выбор файла. Платформа самостоятельно считает файл с диска пользователя и поместит его в безопасное временное хранилище на сервере, вернув нам лишь адрес.
&НаКлиенте
Процедура ЗагрузитьФайлВыполнить(Команда)
// Настраиваем параметры диалога выбора файла
ПараметрыПомещения = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыПомещения.МножественныйВыбор = Ложь;
ПараметрыПомещения.Заголовок = "Выберите файл для загрузки в хранилище";
Оповещение = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
// Запускаем асинхронное помещение файла на сервер
НачатьПомещениеФайлаНаСервер(Оповещение, , , "", ПараметрыПомещения, УникальныйИдентификатор);
КонецПроцедуры
Шаг 2. Клиентская часть. Обработка результата выбора
После того как пользователь выбрал файл и платформа загрузила его во временное хранилище, срабатывает процедура оповещения. Обратите внимание: мы не передаем на сервер пути к файлам — только адрес во временном хранилище и имя файла.
&НаКлиенте
Процедура ПослеВыбораФайла(РезультатПомещения, ДополнительныеПараметры) Экспорт
// Если пользователь закрыл диалог выбора файла
Если РезультатПомещения = Неопределено Тогда
Возврат;
КонецЕсли;
// Извлекаем адрес во временном хранилище и имя исходного файла
АдресХранилища = РезультатПомещения.Адрес;
ИмяВыбранногоФайла = РезультатПомещения.СсылкаНаФайл.Имя;
// Вызываем серверную процедуру для записи файла в базу данных
СоздатьЭлементХранилищаНаСервере(АдресХранилища, ИмяВыбранногоФайла);
Модифицированность = Истина;
ПоказатьОповещениеПользователя("Успех", , "Файл успешно загружен в базу данных!");
КонецПроцедуры
Шаг 3. Серверная часть. Создание элемента справочника
На сервере мы извлекаем двоичные данные из временного хранилища и записываем их в реквизит с типом ХранилищеЗначения. Хранить данные лучше в чистом виде (как ДвоичныеДанные), а имя файла и его расширение записывать в отдельные текстовые реквизиты справочника.
&НаСервере
Процедура СоздатьЭлементХранилищаНаСервере(АдресХранилища, ИмяФайла)
// Извлекаем двоичные данные из временного хранилища
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ТипЗнч(ДвоичныеДанныеФайла) <> Тип("ДвоичныеДанные") Тогда
ВызватьИсключение "Не удалось получить корректные данные файла.";
КонецЕсли;
// Создаем новый элемент справочника для хранения файлов
НовыйФайл = Справочники.ХранилищеФайлов.СоздатьЭлемент();
НовыйФайл.Наименование = ИмяФайла;
НовыйФайл.ИмяФайла = ИмяФайла;
НовыйФайл.ВладелецФайла = Объект.Ссылка;
// Помещаем двоичные данные в ХранилищеЗначения
НовыйФайл.ДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));
НовыйФайл.Записать();
КонецПроцедуры
Для открытия сохраненного файла нам не нужно вручную скачивать его на диск во временные папки и использовать НачатьЗапускПриложения. В платформе 1С предусмотрен мощный универсальный механизм — получение файла по навигационной ссылке.
Если реквизит справочника имеет тип ХранилищеЗначения, мы можем получить на него прямую навигационную ссылку и передать ее стандартной процедуре получения файлов. Платформа сама определит тип клиента, скачает файл во временный каталог операционной системы и запустит ассоциированное с ним приложение (например, Microsoft Word, Excel или просмотрщик PDF).
Ниже представлены два варианта реализации открытия файла: для современных платформ (8.3.18+) с использованием механизма Асинх / Ждать и для более ранних версий через обратные вызовы (Оповещения).
Использование конструкции Ждать позволяет писать понятный линейный код без создания дополнительных процедур-оповещений.
&НаКлиенте
Асинх Процедура СписокФайловВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
ТекущиеДанные = Элементы.СписокФайлов.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Получаем навигационную ссылку на реквизит "ДанныеФайла" конкретного элемента справочника
СсылкаНаФайлВБД = ПолучитьНавигационнуюСсылку(ТекущиеДанные.Ссылка, "ДанныеФайла");
ПараметрыПолучения = Новый ПараметрыДиалогаПолученияФайлов;
// Метод скачает файл на клиентский компьютер и запустит программу просмотра
Ждать ПолучитьФайлССервераАсинх(СсылкаНаФайлВБД, ТекущиеДанные.ИмяФайла, ПараметрыПолучения);
КонецПроцедуры
Если вы работаете на более старых версиях платформы 8.3, используйте метод НачатьПолучениеФайлаССервера() с обратным вызовом.
&НаКлиенте
Процедура СписокФайловВыборАльтернативный(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
ТекущиеДанные = Элементы.СписокФайлов.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
СсылкаНаФайлВБД = ПолучитьНавигационнуюСсылку(ТекущиеДанные.Ссылка, "ДанныеФайла");
ПараметрыПолучения = Новый ПараметрыДиалогаПолученияФайлов;
Оповещение = Новый ОписаниеОповещения("ПослеПолученияФайлаССервера", ЭтотОбъект);
НачатьПолучениеФайлаССервера(Оповещение, СсылкаНаФайлВБД, ТекущиеДанные.ИмяФайла, ПараметрыПолучения);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПолученияФайлаССервера(РезультатПолучения, ДополнительныеПараметры) Экспорт
// Файл был успешно загружен и запущен на клиенте
// Здесь можно выполнить дополнительные действия, если необходимо
КонецПроцедуры
Хотя хранение двоичных данных непосредственно в базе данных (в реквизитах типа ХранилищеЗначения) реализовать проще всего, этот подход имеет серьезные недостатки при росте объема базы данных. Рассмотрим несколько важных советов по улучшению архитектуры вашего решения:
РаботаСФайламиКлиент.ОткрытьФайл(ПараметрыФайла);.Применение современных асинхронных вызовов платформы 1С:Предприятие 8.3 позволяет полностью избежать зависания интерфейса у пользователей и гарантирует корректную работу приложения как на тонком клиенте, так и при публикации базы на веб-сервере под управлением Apache или IIS.