Рассмотрим частую бизнес-задачу: необходимо реализовать прикрепление файлов к документу в 1С. Основное требование — файл должен храниться не в виде локального пути (например, "C:\Docs\file.pdf"), а непосредственно в информационной базе. Это позволит открывать его с любого рабочего места, подключенного к системе, без необходимости настраивать общие сетевые папки.
В этой инструкции мы по шагам разберем весь процесс: от выбора файла пользователем до его сохранения в базе данных и последующего открытия. Мы также проанализируем типичные ошибки, с которыми сталкиваются разработчики, и покажем правильные методы их решения.
Для начала подготовим нашу форму. Предположим, у нас есть документ, в котором должна быть табличная часть для хранения списка файлов. Назовем ее Список.
В этой табличной части нам понадобятся как минимум два реквизита (колонки):
Название (Тип: Строка) — для хранения имени файла, которое будет видеть пользователь.Файл (Тип: ХранилищеЗначения) — для хранения двоичных данных самого файла.На форму документа добавим две кнопки: "Выбрать файл" и "Открыть файл". Первая будет инициировать процесс добавления файла, вторая — его открытие.
Процесс добавления файла начинается на клиенте. Пользователь нажимает кнопку "Выбрать файл", после чего должен открыться стандартный диалог выбора файла. После выбора файла его необходимо передать на сервер для дальнейшей обработки и сохранения.
Важный момент: передавать большие объемы двоичных данных напрямую в параметрах серверных вызовов — плохая практика. Это сильно нагружает сеть и сервер. Правильный подход — использовать временное хранилище платформы 1С.
Рассмотрим код для клиентских процедур.
&НаКлиенте
Процедура ВыбратьФайл(Команда)
// Создаем описание для обратного вызова после выбора файла.
// Платформа выполнит процедуру "ЗавершениеОбратныйВызов", когда файл будет помещен во временное хранилище.
ЗавершениеОбратныйВызов = Новый ОписаниеОповещения("ЗавершениеОбратныйВызов", ЭтотОбъект);
// Запускаем асинхронный механизм помещения файла на сервер.
// Файл будет помещен во временное хранилище, а мы получим адрес на него.
НачатьПомещениеФайлаНаСервер(ЗавершениеОбратныйВызов,,,,,);
КонецПроцедуры
&НаКлиенте
Процедура ЗавершениеОбратныйВызов(ОписаниеПомещенногоФайла, ДополнительныеПараметры) Экспорт
// Проверяем, что файл был успешно выбран и помещен.
Если ОписаниеПомещенногоФайла = Неопределено Тогда
Возврат; // Пользователь отменил выбор файла.
КонецЕсли;
// Вызываем серверную процедуру для сохранения файла в базе.
// Передаем адрес во временном хранилище и имя файла.
ПоместитьВХранилищеЗначений(ОписаниеПомещенногоФайла.Адрес, ОписаниеПомещенногоФайла.Имя);
// После выполнения серверной процедуры обновляем данные на форме, чтобы увидеть новую строку.
ЭтаФорма.Прочитать();
КонецПроцедуры
Проанализируем этот код: НачатьПомещениеФайлаНаСервер — это асинхронный метод. Он не блокирует интерфейс пользователя. Как только файл будет загружен во временное хранилище на сервере, платформа вызовет процедуру, указанную в ОписаниеОповещения, — в нашем случае это ЗавершениеОбратныйВызов. В эту процедуру будет передан объект ОписаниеПомещенногоФайла, из которого мы берем адрес файла во временном хранилище (.Адрес) и его исходное имя (.Имя).
Теперь реализуем серверную процедуру ПоместитьВХранилищеЗначений. Здесь происходит основная работа: получение двоичных данных из временного хранилища и их запись в реквизит объекта.
Здесь возникает первая распространенная ошибка. При работе с управляемой формой на сервере переменная Объект (основной реквизит формы) является типом ДанныеФормыСтруктура, а не самим объектом базы данных. Реквизиты типа ХранилищеЗначения в этой структуре недоступны для записи. Чтобы получить реальный объект базы данных, нужно использовать метод РеквизитФормыВЗначение("Объект").
&НаСервере
Процедура ПоместитьВХранилищеЗначений(АдресВременногоХранилища, ИмяФайла)
// 1. Получаем реальный объект базы данных из данных формы.
ВременныйОбъект = РеквизитФормыВЗначение("Объект");
// 2. Получаем двоичные данные файла из временного хранилища по адресу.
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресВременногоХранилища);
// 3. Добавляем новую строку в табличную часть объекта.
НоваяСтрока = ВременныйОбъект.Список.Добавить();
НоваяСтрока.Название = ИмяФайла;
// 4. Помещаем двоичные данные в ХранилищеЗначения с использованием сжатия.
НоваяСтрока.Файл = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));
// 5. Записываем измененный объект в базу данных.
ВременныйОбъект.Записать();
// 6. Обновляем данные формы, чтобы они соответствовали записанному объекту.
ЗначениеВРеквизитФормы(ВременныйОбъект, "Объект");
КонецПроцедуры
Важное примечание: Обновление данных на форме после записи — тонкий момент. Простого ЭтаФорма.Прочитать() на клиенте (как мы сделали в коде выше) может быть недостаточно, особенно если документ новый (еще не был записан). После первой записи сервер может "потерять" связь с формой. Более надежный способ — после записи объекта на сервере преобразовать его обратно в данные формы с помощью ЗначениеВРеквизитФормы(). Это гарантирует, что форма будет отображать актуальное состояние объекта, включая его новую ссылку и версию.
Процесс открытия файла — это обратная операция. Нам нужно получить двоичные данные из базы, передать их на клиент, сохранить во временный файл на локальном компьютере и запустить его с помощью ассоциированной программы.
Начнем с клиентской процедуры, которая вызывается по кнопке "Открыть файл".
&НаКлиенте
Процедура ОткрытьФайл(Команда)
// Получаем идентификатор текущей выделенной строки в таблице.
ИдентификаторТекущейСтроки = Элементы.Список.ТекущаяСтрока;
Если ИдентификаторТекущейСтроки = Неопределено Тогда
ПоказатьПредупреждение(,"Не выбрана строка с файлом!");
Возврат;
КонецЕсли;
// Получаем имя файла из данных текущей строки на клиенте.
ТекущиеДанныеСтроки = Элементы.Список.ТекущиеДанные;
ИмяФайла = ТекущиеДанныеСтроки.Название;
// Вызываем серверную функцию, чтобы получить адрес файла во временном хранилище.
Адрес = ПолучитьАдресФайлаНаСервере(ИдентификаторТекущейСтроки);
// Получаем двоичные данные с сервера.
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(Адрес);
// Создаем временный файл на диске клиента.
ИмяВременногоФайла = ПолучитьИмяВременногоФайла(РасширениеИзИмениФайла(ИмяФайла));
ДвоичныеДанныеФайла.Записать(ИмяВременногоФайла);
// Запускаем файл.
ЗапуститьПриложение(ИмяВременногоФайла);
КонецПроцедуры
Теперь разберем серверную функцию ПолучитьАдресФайлаНаСервере. Здесь мы столкнемся со второй распространенной ошибкой: попыткой обратиться к данным строки таблицы через элемент формы на сервере.
Конструкция Элементы.Список.ТекущаяСтрока, переданная на сервер, будет содержать не данные строки, а ее уникальный идентификатор (число). Попытка написать Элементы.Список.ТекущаяСтрока.Файл приведет к ошибке "Значение не является значением объектного типа".
Правильный путь — получить на сервере реальный объект, а затем найти в его табличной части нужную строку по переданному с клиента идентификатору.
&НаСервере
Функция ПолучитьАдресФайлаНаСервере(ИдентификаторСтроки)
// 1. Получаем данные объекта, которые сейчас находятся на форме.
// Нам не нужно читать его из базы, так как данные уже есть.
ДанныеОбъекта = РеквизитФормыВЗначение("Объект");
// 2. Находим нужную строку в табличной части по ее идентификатору.
НайденнаяСтрока = ДанныеОбъекта.Список.НайтиПоИдентификатору(ИдентификаторСтроки);
Если НайденнаяСтрока = Неопределено Тогда
// Такого быть не должно, но лучше предусмотреть.
Возврат "";
КонецЕсли;
// 3. Извлекаем двоичные данные из ХранилищаЗначения.
ДвоичныеДанные = НайденнаяСтрока.Файл.Получить();
// 4. Помещаем эти данные во временное хранилище и возвращаем на клиент адрес.
Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
Возврат Адрес;
КонецФункции
Таким образом, мы построили полный и надежный цикл работы с файлами: асинхронная загрузка на сервер через временное хранилище, корректное сохранение в объект базы данных и безопасное получение данных на клиенте для открытия.
Рассмотренный метод с использованием ХранилищеЗначения является базовым. В зависимости от масштаба системы и требований, стоит рассмотреть и другие варианты:
Придерживаясь описанных принципов разделения клиентского и серверного кода, правильной работы с данными формы и использования временного хранилища, вы сможете реализовать надежный и масштабируемый функционал для работы с прикрепленными файлами в ваших конфигурациях.