При разработке интеграций и механизмов обмена часто возникает задача: вместо фонового формирования файла на сервере предоставить пользователю возможность вручную выбрать готовый файл на локальном компьютере и отправить его во внешнюю систему или обработать на стороне сервера. Разберем, как правильно реализовать этот сценарий в управляемом приложении 1С:Предприятие.
Проанализируем типичную ошибку начинающих разработчиков: открыть диалог выбора файла на клиенте с помощью объекта ДиалогВыбораФайла, получить локальный путь к файлу (например, C:\Users\User\Documents\export.csv) и передать эту строку параметром в серверную процедуру.
В клиент-серверном режиме работы (на сервере 1С:Предприятия) этот подход работать не будет по следующим причинам:
USR1CV8), у которой нет прав на чтение пользовательских каталогов на локальных машинах.ДиалогВыбораФайла.Выбрать() вызывает ошибку блокировки интерфейса в современных версиях платформы и полностью не поддерживается в веб-клиенте.Единственно верный способ передачи файла с клиентского компьютера на сервер — это чтение двоичных данных на клиенте, помещение их во временное хранилище и передача на сервер уникального адреса этого хранилища.
Начиная с версии платформы 8.3.18, наиболее удобным и лаконичным способом является вызов асинхронной функции ПоместитьФайлНаСерверАсинх. Она объединяет в себе вызов стандартного диалога выбора файла и автоматическую отправку выбранных данных во временное хранилище.
Рассмотрим реализацию на клиенте:
&НаКлиенте
Асинх Процедура ВыбратьИВыгрузитьТовары(Команда)
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыДиалога.Заголовок = "Выберите файл CSV для выгрузки";
ПараметрыДиалога.Фильтр = "Файлы CSV (*.csv)|*.csv";
ПараметрыДиалога.МножественныйВыбор = Ложь;
// Метод открывает диалог и возвращает объект с адресом во временном хранилище
РезультатПомещения = Ждать ПоместитьФайлНаСерверАсинх(,,, ПараметрыДиалога, УникальныйИдентификатор);
Если РезультатПомещения = Неопределено Тогда
// Пользователь отменил выбор файла
Возврат;
КонецЕсли;
// Передаем адрес временного хранилища и имя файла на сервер
ВыгрузитьТоварыНаСервере(РезультатПомещения.Адрес, РезультатПомещения.СсылкаНаФайл.Имя);
КонецПроцедуры
Обратим внимание на передачу параметра УникальныйИдентификатор формы в метод помещения файла. Это критически важно: если передать Неопределено, временное хранилище будет очищено сразу после первого серверного вызова, и при дальнейшей работе файл может оказаться недоступен.
Если в конфигурации не используются асинхронные функции через оператор Ждать, применим классический паттерн с объектом ОписаниеОповещения.
Посмотрим на пример клиентского кода:
&НаКлиенте
Процедура ВыбратьИВыгрузитьТовары(Команда)
Оповещение = Новый ОписаниеОповещения("ВыборФайлаЗавершение", ЭтотОбъект);
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыДиалога.Заголовок = "Выберите файл CSV для выгрузки";
ПараметрыДиалога.Фильтр = "Файлы CSV (*.csv)|*.csv";
ПараметрыДиалога.МножественныйВыбор = Ложь;
НачатьПомещениеФайлаНаСервер(Оповещение,,, ПараметрыДиалога, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ВыборФайлаЗавершение(РезультатПомещения, ДополнительныеПараметры) Экспорт
Если РезультатПомещения = Неопределено Тогда
Возврат;
КонецЕсли;
ВыгрузитьТоварыНаСервере(РезультатПомещения.Адрес, РезультатПомещения.СсылкаНаФайл.Имя);
КонецПроцедуры
Теперь разберем серверную процедуру. Получив адрес во временном хранилище, сервер извлекает объект ДвоичныеДанные с помощью функции ПолучитьИзВременногоХранилища. Если внешний обработчик или API требует обязательного наличия файла на диске сервера, мы сохраняем его во временный каталог сервера, отправляем и обязательно удаляем после завершения операции.
&НаСервере
Процедура ВыгрузитьТоварыНаСервере(АдресВХ, ИсходноеИмяФайла)
// Получаем двоичные данные из временного хранилища
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВХ);
Если ДвоичныеДанные = Неопределено Тогда
ВызватьИсключение "Не удалось прочитать файл из временного хранилища.";
КонецЕсли;
// Генерируем уникальное имя временного файла на сервере
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("csv");
КаталогВременныхФайловСервера = КаталогВременныхФайлов();
Попытка
// Сохраняем файл на диск сервера
ДвоичныеДанные.Записать(ИмяВременногоФайла);
// Проверяем факт создания файла на сервере
ФайлНаДиске = Новый Файл(ИмяВременногоФайла);
Если ФайлНаДиске.Существует() Тогда
ПараметрыОтправки = Новый Структура;
ПараметрыОтправки.Вставить("Режим", 0);
ПараметрыОтправки.Вставить("СерверПриемник", УбратьПротоколВАдресе(АдресСайтаPost));
ПараметрыОтправки.Вставить("APIKey", APIKey);
ПараметрыОтправки.Вставить("ИмяФайла", ИмяВременногоФайла);
ПараметрыОтправки.Вставить("ВремКаталог", КаталогВременныхФайловСервера);
ПараметрыОтправки.Вставить("ВерсияПлатформыAdvantShop", 1);
ОтправитьФайл(ПараметрыОтправки);
КонецЕсли;
Исключение
// Обязательно удаляем временный файл даже при возникновении ошибок
УдалитьФайлы(ИмяВременногоФайла);
УдалитьИзВременногоХранилища(АдресВХ);
ВызватьИсключение;
КонецПопытки;
// Очищаем временные ресурсы после успешной отправки
УдалитьФайлы(ИмяВременногоФайла);
УдалитьИзВременногоХранилища(АдресВХ);
КонецПроцедуры
НайтиФайлы() всегда возвращает массив (даже если файлов не найдено). Конструкция Если НайтиФайлы(...) <> Неопределено является ошибкой. Для проверки существования используйте объект Файл и его метод Существует() либо проверяйте НайтиФайлы(...).Количество() > 0.Попытка ... Исключение с вызовом УдалитьФайлы(), чтобы избежать переполнения дискового пространства сервера сессионными файлами.УдалитьИзВременногоХранилища(АдресВХ).