Почему не работает копирование файлов на сервер в веб-клиенте 1С и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке функционала по загрузке файлов в систему 1С программисты часто сталкиваются с ситуацией, когда код идеально работает в тонком клиенте, но при запуске через веб-клиент (в браузере) файлы просто не копируются, при этом система не выдает никаких видимых ошибок. Рассмотрим подробнее, почему так происходит, и разберем правильную архитектуру процесса передачи файлов в современных версиях платформы 1С:Предприятие 8.3.

Анализ проблемы: почему ПередЗаписью — это плохой выбор

Проанализируем типичный сценарий: программист пытается поместить файл во временное хранилище в обработчике ПередЗаписью, чтобы при сохранении документа файл сразу «улетел» на сервер. В веб-клиенте это часто приводит к фиаско. Причина кроется в особенностях работы браузеров. Событие ПередЗаписью инициирует транзакцию записи на стороне сервера. Методы семейства НачатьПомещение... являются асинхронными (используют callback-модель). Браузер, находясь в процессе отправки данных формы, может блокировать или игнорировать параллельные асинхронные запросы к файловой системе, которые не были инициированы явным действием пользователя (например, нажатием кнопки).

В тонком клиенте 1С имеет больше контроля над потоками выполнения, а в веб-клиенте мы жестко ограничены событийным циклом (Event Loop) браузера. Выясним причину: если асинхронная операция загрузки файла не завершится до того, как форма начнет закрываться или переходить в режим чтения после записи, соединение может быть разорвано.

Рассмотрим исходный (проблемный) код

Посмотрим на пример кода, который вызывал затруднения. Здесь используется устаревающий метод НачатьПомещениеФайловНаСервер внутри процедуры ПередЗаписью:


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

Решение №1: Перенос логики на пользовательское действие

Самый надежный способ исправить ситуацию в веб-клиенте — разделить процесс записи документа и процесс загрузки файлов. Разберем по шагам, как это реализовать:

  1. Добавим на форму отдельную команду (кнопку) "Загрузить файлы" или "Прикрепить файл".
  2. В обработчике этой команды инициируем выбор файла и его немедленную отправку во временное хранилище.
  3. На сервере сохраняем данные уже после того, как мы получили адрес во временном хранилище.

Рассмотрим пример реализации через кнопку, который гарантированно работает в веб-клиенте:


&НаКлиенте
Процедура КомандаЗагрузитьФайл(Команда)
    // Сначала убеждаемся, что расширение для работы с файлами подключено
    ОписаниеОповещения = Новый ОписаниеОповещения("ПослеПодключенияРасширения", ЭтотОбъект);
    НачатьПодключениеРасширенияРаботыСФайлами(ОписаниеОповещения);
КонецПроцедуры

&НаКлиенте
Процедура ПослеПодключенияРасширения(Подключено, ДополнительныеПараметры) Экспорт
    Если Подключено Тогда
        Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
        ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
        Диалог.Показать(ОписаниеОповещения);
    Иначе
        Сообщить("Для загрузки файлов в веб-клиенте необходимо установить расширение браузера.");
    КонецЕсли;
КонецПроцедуры

Решение №2: Использование современных асинхронных методов (Async/Await)

Начиная с версии платформы 8.3.15, рекомендуется отказываться от ОписаниеОповещения в пользу методов с постфиксом Асинх. Это делает код более читаемым и стабильным. Проанализируем, как переписать загрузку файлов на новый лад. Использование Ждать (await) позволяет писать асинхронный код так, будто он синхронный.


&НаКлиенте
Асинх Процедура ПрикрепитьФайлыАсинх()
    
    // Проверяем расширение
    Установлено = Ждать ПодключитьРасширениеРаботыСФайламиАсинх();
    Если Не Установлено Тогда
        Ждать УстановитьРасширениеРаботыСФайламиАсинх();
    КонецЕсли;

    Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
    ВыбранныеФайлы = Ждать Диалог.ВыбратьАсинх();
    
    Если ВыбранныеФайлы <> Неопределено Тогда
        Для Каждого ИмяФайла Из ВыбранныеФайлы Цикл
            // Помещаем файл на сервер и получаем результат
            Результат = Ждать ПоместитьФайлНаСерверАсинх(,,, ИмяФайла, УникальныйИдентификатор);
            Если Результат <> Неопределено Тогда
                // Вызываем серверный метод для сохранения из временного хранилища в каталог
                СохранитьФайлНаДискеНаСервере(Результат.Адрес, Результат.СсылкаНаФайл.Имя);
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    
КонецПроцедуры

Разберем работу на стороне сервера

После того как файл попал во временное хранилище (объект АдресХранилища), его необходимо физически записать на диск сервера. Важно помнить, что в веб-клиенте серверный код выполняется от имени пользователя, под которым запущен веб-сервер (IIS или Apache), поэтому у него должны быть права на запись в целевой каталог.


&НаСервере
Процедура СохранитьФайлНаДискеНаСервере(АдресХранилища, ИмяФайла)
    ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
    Если ДвоичныеДанные <> Неопределено Тогда
        // Путь должен быть доступен пользователю веб-сервера!
        ПутьККаталогу = "C:\FilesStorage\"; 
        СоздатьКаталог(ПутьККаталогу);
        ПолныйПуть = ПутьККаталогу + ИмяФайла;
        
        Попытка
            ДвоичныеДанные.Записать(ПолныйПуть);
            УдалитьИзВременногоХранилища(АдресХранилища);
        Исключение
            ЗаписьЖурналаРегистрации("ЗагрузкаФайла", УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
            ВызватьИсключение "Ошибка при сохранении файла на сервере: " + ОписаниеОшибки();
        КонецПопытки;
    КонецЕсли;
КонецПроцедуры

Важные нюансы работы в веб-клиенте

Проанализируем дополнительные факторы, которые могут влиять на результат:

Таким образом, мы выяснили, что основная проблема заключалась в неудачном выборе события для инициации загрузки. Переход на явное управление процессом через кнопки и использование современного асинхронного стека Асинх/Ждать позволяет создавать надежные решения для работы с файлами в любых клиентах 1С.

← На главную