Довольно часто встречаются задачи, связанные с записью файлов на основе информации, полученной из информационной базы. Для примера возьмем запись содержимого элемента справочника в файл, выбранный пользователем.
В те благословенные времена, когда о тонком клиенте и управляемых формах и не слышали В толстом клиенте на обычных формах эта задача решается довольно просто:
Диалог = новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
Если Диалог.Выбрать() Тогда
Запись = новый ЗаписьXML;
Запись.ОткрытьФайл(Диалог.ПолноеИмяФайла);
СериализаторXDTO.ЗаписатьXML(Запись, Ссылка.ПолучитьОбъект());
Запись.Закрыть();
КонецЕсли;
Здесь Ссылка - ссылка на элемент справочника, данные которого записываем в файл. СериализаторXDTO - специальный объект платформы, с помощью которого объект ИБ трансформируется в файл формата XML.
Все изменилось с появлением тонкого клиента и управляемых форм. Во-первых, на клиенте стала недоступна работа с объектами ИБ, а сервер, в свою очередь, ничего не знает о действиях пользователя, а во-вторых, использование веб-клиента породило необходимость отказаться от модальности и использовать асинхронные вызовы многих методов. Таким образом, решение приведенной выше задачи стало достаточно непростым.
Общая схема решения:
Рассмотрим каждый шаг более подробно.
1 шаг. Пользователь выбирает данные для файла (в данном случае - ссылку справочника) и хочет выбрать файл для записи. Использовать синхронные методы вроде Диалог.Выбрать() теперь стало нельзя. Вместо синхронных методов появились их асинхронные аналоги, в данном случае Диалог.Показать(Оповещение).
&НаКлиенте
Процедура Записать(Команда)
Диалог = новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
Оповещение = новый ОписаниеОповещения("ОповещениеПослеВыбораФайлаДляЗаписи", ЭтотОбъект, Ссылка);
Диалог.Показать(Оповещение);
КонецПроцедуры
Ключевой особенностью асинхронных методов является то, что результат их работы мы можем получить не сразу после вызова метода в том же участке кода, а с помощью отдельного метода (callback, метод обратного вызова), который будет вызван платформой после завершения асинхронного метода. В данном случае методом обратного вызова является ОповещениеПослеВыбораФайлаДляЗаписи, описание которого указывается в параметрах открытия диалога. Он будет вызван автоматически после закрытия диалога выбора файла вне зависимости от результата выбора.
2а шаг.
&НаКлиенте
Процедура ОповещениеПослеВыбораФайлаДляЗаписи(ВыбранныеФайлы, ДопПараметры)Экспорт
Если ТипЗнч(ВыбранныеФайлы) = Тип("Массив") Тогда
АдресХранилища = ФормированиеФайлаНаСервере(ДопПараметры, ЭтаФорма.УникальныйИдентификатор);
Оповещение = новый ОписаниеОповещения("ОповещениеПослеПолученияФайла", ЭтотОбъект);
ОписаниеФайла = новый ОписаниеПередаваемогоФайла();
ОписаниеФайла.Хранение = АдресХранилища;
ОписаниеФайла.Имя = ВыбранныеФайлы[0];
ПолучаемыеФайлы = Новый Массив;
ПолучаемыеФайлы.Добавить(ОписаниеФайла);
НачатьПолучениеФайлов(Оповещение, ПолучаемыеФайлы,, Ложь);
КонецЕсли;
КонецПроцедуры
После закрытия диалога выбора файла, и проверки, что пользователь действительно не отказался от выбора (в первом параметре метода будет находится массив выбранных файлов), мы можем продолжить процесс. И здесь программиста подстерегает возможность совершить трудноуловимую ошибку. Дело в том, что в веб-клиенте во время показа пользователю диалога выбора файла выполнение кода не останавливается! Существует вероятность, что за время между первым и вторым шагом изменятся данные, которые пользователь выбрал на первом шаге (в нашем примере - Ссылка). Чтобы этого избежать, данные надо где-то сохранить, а еще лучше - передать в качестве параметра при открытии диалога, так, чтобы этот параметр попал на второй шаг без изменений. В нашем примере это реализовано указанием ссылки в качестве третьего параметра при создании оповещения на первом шаге. Эта же ссылка будет получена вторым параметром метода ОповещениеПослеВыбораФайлаДляЗаписи. Итак мы имеем все необходимые данные (имя файла и ссылку) для записи. Напомню: пока все то, что происходит, происходит на стороне клиента. Но на стороне клиента недоступны данные ИБ, поэтому нам необходимо передать управление на сервер, вызвав серверный метод и передав в него ссылку.
3 шаг
На этом шаге нам необходимо сформировать содержимое файла и подготовить его для передачи на сторону клиента, поместив в специальную область - временное хранилище. Временное хранилище располагается на сервере, доступ к нему осуществляется с помощью специального адреса. Временное хранилище "живет" непродолжительное время, в нашем случае - пока живет форма, для этого при создании хранилища указывается идентификатор формы. В хранилище будем помещать ДвоичныеДанные - содержимое нашего будущего файла. Как правило, использование двоичных данных связано с формированием промежуточного временного файла, но в нашем случае мы можем обойтись без этого. В последних версиях платформы появились новые объекты - Поток, ПотокВПамяти, которые позволят нам обойтись без дополнительных файлов.
&НаСервереБезКонтекста
Функция ФормированиеФайлаНаСервере(Ссылка, ИдентификаторФормы)
Запись = новый ЗаписьXML;
Поток = новый ПотокВПамяти;
Запись.ОткрытьПоток(Поток);
СериализаторXDTO.ЗаписатьXML(Запись, Ссылка.ПолучитьОбъект());
Запись.Закрыть();
ДвоичныеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанные, ИдентификаторФормы);
Возврат АдресХранилища;
КонецФункции
В конечном итоге на этом шаге мы получим адрес хранилища, в котором находится содержимое нашего файла.
2б шаг
Вызов серверной функции на втором шаге был обычным, не асинхронным, поэтому после выполнения этого метода мы продолжаем выполнение второго шага. Итак, имеем: имя выбранного пользователем файла и адрес временного хранилища на сервере, где хранится его содержимое. Необходимо вызвать метод, который получит содержимое с сервера и запишет в файл на клиенте. Этот метод - НачатьПолучениеФайлов. Не буду останавливаться на подробном описании его параметров, скажу лишь, что в него передается имя файла и адрес временного хранилища. Метод НачатьПолучениеФайлов является асинхронным, окончание его работы запустит метод обратного вызова ОповещениеПослеПолученияФайла, в котором мы узнаем результат. Разумеется, описание метода ОповещениеПослеПолученияФайла мы должны указать при вызове НачатьПолучениеФайлов.
4 шаг
Этот шаг - завершающий. На этом шаге мы можем проанализировать результат получения файла.
&НаКлиенте
Процедура ОповещениеПослеПолученияФайла(ПолученныеФайлы, ДопПараметры)Экспорт
Если ТипЗнч(ПолученныеФайлы) = Тип("Массив") Тогда
Сообщить("Файл " + ПолученныеФайлы[0].Имя + " записан");
КонецЕсли;
КонецПроцедуры //
Аналогичным образом можно решить обратную задачу: загрузить данные из файла в информационную базу
1 шаг. Открываем асинхронный диалог выбора файла, передав в него все необходимые клиентские данные (в нашем случае - Ссылка)
&НаКлиенте
Процедура Прочитать(Команда)
Диалог = новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
Оповещение = новый ОписаниеОповещения("ОповещениеПослеВыбораФайлаДляЧтения", ЭтотОбъект, Ссылка);
Диалог.Показать(Оповещение);
КонецПроцедуры
2 шаг. Если пользователь выбрал файл, то инициируем асинхронную передачу файла на сервер с помощью НачатьПомещениеФайлов. Платформа автоматически создаст на сервере временное хранилище и поместит в него содержимое файла.
&НаКлиенте
Процедура ОповещениеПослеВыбораФайлаДляЧтения(ВыбранныеФайлы, ДопПараметры)Экспорт
Если ТипЗнч(ВыбранныеФайлы) = Тип("Массив") Тогда
ОписаниеФайла = новый ОписаниеПередаваемогоФайла();
ОписаниеФайла.Имя = ВыбранныеФайлы[0];
ПомещаемыеФайлы = новый Массив;
ПомещаемыеФайлы.Добавить(ОписаниеФайла);
Оповещение = новый ОписаниеОповещения("ОповещениеПомещениеФайла", ЭтотОбъект, ДопПараметры);
НачатьПомещениеФайлов(Оповещение, ПомещаемыеФайлы,, Ложь, ЭтаФорма.УникальныйИдентификатор);
КонецЕсли;
КонецПроцедуры
3а шаг. После завершения операции помещения файла платформа вызовет клиентскую процедуру, в которую будут переданы данные о помещенных на сервер файлах. Нас будет интересовать адрес хранилища ПомещенныеФайлы[0].Хранение, в которое записано содержимое файла. Для обработки этого содержимого вызовем серверную функцию, передав в нее этот адрес и данные, выбранные клиентом на первом шаге. Еще раз подчеркну, что эти клиентские данные (в нашем случае - Ссылка) передаются по цепочке оповещений, что гарантирует их неизменность во время асинхронного выполнения нескольких методов.
&НаКлиенте
Процедура ОповещениеПомещениеФайла(ПомещенныеФайлы, ДопПараметры)Экспорт
Если ТипЗнч(ПомещенныеФайлы) = Тип("Массив") Тогда
Сообщить("Файл " + ПомещенныеФайлы[0].Имя + " отправлен на сервер");
Ссылка = ОбработкаПереданногоФайлаНаСервере(ПомещенныеФайлы[0].Хранение, ДопПараметры);
ЭтаФорма.ОтобразитьИзменениеДанных(Ссылка,ВидИзмененияДанных.Изменение);
КонецЕсли;
КонецПроцедуры
4 шаг. Получив на сервере адрес временного хранилища, прочитаем его содержимое в ДвоичныеДанные. Так же, как и в предыдущем примере, чтобы не создавать дополнительных временных файлов, воспользуемся потоками. С помощью СериализаторXDTO преобразуем данные (они представляют собой XML текст) в объект для дальнейшей обработки.
&НаСервереБезКонтекста
Функция ОбработкаПереданногоФайлаНаСервере(Знач АдресХранилища, Знач Ссылка)
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
Поток = ДвоичныеДанные.ОткрытьПотокДляЧтения();
Чтение = новый ЧтениеXML;
Чтение.ОткрытьПоток(Поток);
Объект = СериализаторXDTO.ПрочитатьXML(Чтение);
Если ЗначениеЗаполнено(Ссылка) Тогда
ОбъектСсылки = Ссылка.ПолучитьОбъект();
ЗаполнитьЗначенияСвойств(ОбъектСсылки, Объект, "Наименование"); //Заполняем нужные реквизиты
ОбъектСсылки.Записать();
Иначе
Объект.Записать();
Ссылка = Объект.Ссылка;
КонецЕсли;
Чтение.Закрыть();
Поток.Закрыть();
Возврат Ссылка;
КонецФункции
3б шаг. После завершения серверной функции, управление передается обратно в клиентский метод (вызов был не асинхронный), где мы можем выполнить все оставшиеся действия (в нашем случае - обновить интерфейс после загрузки данных)
Как видим, работа с файлами в тонком клиенте существенно сложнее, чем в толстом. К сожалению, это - та плата, которую вынужден платить разработчик при разработке современной системы.
Работа с файлами в тонком клиенте не ограничивается вышеприведенными примерами. Для загрузки файлов (например картинок) непосредственно в информационную базу рекомендую публикации //infostart.ru/public/195003/, //infostart.ru/public/396459/
Ниже прилагается внешняя обработка с примерами из статьи.