Почему метод ДокументСсылка.Метаданные.Имя вызывает ошибку 'Метод объекта не обнаружен (Метаданные)' в расширении 1С и как это исправить?

Программист 1С v8.3 (Управляемые формы) 1С:ERP Управление предприятием Управленческий учет Торговля и дистрибуция
← На главную

При разработке в системе 1С:Предприятие, особенно при работе с расширениями, мы часто сталкиваемся с задачами получения информации об объектах конфигурации. Одним из таких типовых запросов является получение имени документа по его ссылке, например, с использованием конструкции ДокументСсылка.Метаданные.Имя. Однако, иногда эта, казалось бы, простая операция внезапно начинает вызывать ошибку "Метод объекта не обнаружен (Метаданные)". Давайте подробно разберем, почему возникает эта проблема и как ее эффективно устранить.

Суть проблемы: Доступ к метаданным и контекст выполнения

Как мы выяснили из обсуждения на форуме, основной причиной возникновения ошибки "Метод объекта не обнаружен (Метаданные)" является попытка обращения к свойству Метаданные объекта ДокументСсылка из клиентского контекста. Система 1С:Предприятие строго разделяет код, который выполняется на клиенте (на компьютере пользователя), и код, который выполняется на сервере (на сервере 1С:Предприятия или в серверном контексте тонкого/веб-клиента).

Объекты метаданных, такие как Метаданные.Имя, Метаданные.ПолноеИмя и другие свойства, содержащие описание структуры конфигурации, доступны для чтения и манипуляции только на сервере. Это сделано для оптимизации производительности и безопасности. Если мы попытаемся получить доступ к этим свойствам на клиенте, система выдаст ошибку, аналогичную той, что была в нашем случае:


Ошибка при вызове метода контекста (ОткрытьФорму)
{ОбщийМодуль.СозданиеНаОснованииУТКлиент.Модуль(1306)}:ОткрытьФорму(ДопПараметры.ДополнительныеПараметры.ИмяФормы,
{ОбщаяФорма.СозданиеНаОснованииУточнениеЗаказываемогоКоличества.Форма(100)}:Закрыть(Результат);
по причине: Ошибка при выполнении обработчика - 'ОбработкаЗаполнения'
по причине: Метод объекта не обнаружен (Метаданные)

Мы видим, что ошибка возникла в обработчике ОбработкаЗаполнения, который, судя по трассировке стека вызовов, был инициирован из клиентского модуля (ОбщийМодуль.СозданиеНаОснованииУТКлиент.Модуль). Это четко указывает на клиентский контекст выполнения.

Давайте подробнее рассмотрим проблемный код, который был представлен в обсуждении:


// ...
Функция ТаблицаОстатковКЗаказу(ДокументСсылка, Параметры) Экспорт
//Такая же как стандартная "ОбеспечениеСервер.ТаблицаОстатковКЗаказу(ЗаказНаПотребление, ПараметрыТаблицыТовары)", но без склада в запросе
// ...
ИмяДокумента = ДокументСсылка.Метаданные.Имя;
// ...
КонецФункции

Несмотря на то, что функция ТаблицаОстатковКЗаказу, возможно, изначально предназначалась для выполнения на сервере (о чем косвенно свидетельствует ссылка на ОбеспечениеСервер), при использовании в расширении или при вызове из клиентского кода она могла потерять свой серверный контекст. Ключевой момент — это строка ИмяДокумента = ДокументСсылка.Метаданные.Имя;, которая требует серверного выполнения.

Как исправить проблему: Переносим логику на сервер

Для устранения этой ошибки нам необходимо убедиться, что код, обращающийся к свойству Метаданные, всегда выполняется в серверном контексте. Мы рассмотрим несколько эффективных способов решения этой задачи.

1. Создание серверной экспортной функции (Рекомендуемый способ)

Это наиболее правильный и гибкий подход. Мы создадим отдельную экспортную функцию в серверном общем модуле, которая будет получать ссылку на документ и возвращать его имя метаданных. Затем эту серверную функцию мы сможем вызывать из любого клиентского кода.

  1. Создаем или используем существующий серверный общий модуль: Если у вас еще нет общего модуля, предназначенного для серверных вызовов из расширения, создайте его. Убедитесь, что для модуля установлены свойства "Сервер" и "Вызов сервера" (или "Клиент (обычное приложение)" и "Сервер"). В нашем случае, это, вероятно, будет какой-то новый или уже существующий модуль расширения с установленными серверными флагами.

  2. Создаем экспортную серверную функцию: В этом модуле мы объявим экспортную функцию, помеченную директивой &НаСервере или &НаСервереБезКонтекста. Она будет принимать ссылку на документ и возвращать его имя метаданных.

    Пример кода серверной функции:

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

    Различия между &НаСервере и &НаСервереБезКонтекста:

    • &НаСервере: Позволяет использовать контекст формы (т.е. реквизиты формы, свойства) внутри серверного вызова.
    • &НаСервереБезКонтекста: Выполняется на сервере полностью без доступа к контексту формы. Это более легковесный вызов, который предпочтителен, если вам не нужны данные формы. Для нашей задачи получения имени документа подходит любой из них, но &НаСервереБезКонтекста часто является более чистым решением, если функция не зависит от формы.
  3. Вызываем серверную функцию из клиентского кода: Теперь, вместо прямого обращения к ДокументСсылка.Метаданные.Имя, мы вызываем нашу новую серверную функцию из клиентского кода.

    Пример вызова из клиентского кода (например, из обработчика ОбработкаЗаполнения или любой другой клиентской процедуры):

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

    В данном примере МоеРасширение_Сервер — это имя вашего серверного общего модуля, где определена функция ПолучитьИмяДокументаНаСервере.

2. Передача имени с сервера (Если имя документа известно заранее на сервере)

Если в какой-то точке вашего серверного кода вы уже знаете имя документа и передаете данные на клиент (например, при открытии формы или инициализации других клиентских данных), вы можете просто передать это имя как параметр. Этот способ подходит, если вам не нужно получать имя документа в динамике на клиенте, а оно может быть получено один раз на сервере и затем использовано.


// Пример на сервере при открытии формы
// В процедуре &НаСервере Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

    Если ЗначениеЗаполнено(ДокументСсылкаОснование) Тогда
        ПараметрыФормы.Добавить("ИмяДокументаОснования", ДокументСсылкаОснование.Метаданные.Имя);
    КонецЕсли;

// На клиенте
// В процедуре &НаКлиенте Процедура ПриОткрытии(Отказ)
    ИмяДокумента = Параметры.ИмяДокументаОснования;
// ...

Этот подход менее универсален, так как требует явной передачи имени на клиент из того места, где оно было получено на сервере. В нашем случае, когда ошибка происходит глубоко в обработке заполнения, первый метод с экспортной серверной функцией является более подходящим.

3. Использование метода ДокументСсылка.Вид() (С ограничениями)

Метод Вид(), доступный на клиенте, позволяет получить строковое представление вида объекта. Например, для ДокументСсылка.ЗаказКлиента он вернет строку "Документ.ЗаказКлиента".


&НаКлиенте
Процедура МояКлиентскаяПроцедура()
    // Предположим, ДокументСсылка - это ссылка на документ
    ВидДокументаСтрока = ДокументСсылка.Вид(); // Вернет, например, "Документ.ЗаказКлиента"
    // Из этой строки можно извлечь имя:
    МассивПодстрок = СтрРазделить(ВидДокументаСтрока, ".", Ложь);
    ИмяДокумента = МассивПодстрок[МассивПодстрок.Количество() - 1]; // Получим "ЗаказКлиента"
КонецПроцедуры

Этот способ может показаться удобным, но имеет существенные ограничения:

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

Поэтому, если вам нужно именно имя метаданных объекта, а не просто его строковое представление, предпочтительнее использовать серверный вызов.

Особенности работы с расширениями

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

При возникновении подобных ошибок всегда следует проверять:

  1. Контекст исходного модуля/процедуры: Является ли он клиентским или серверным?
  2. Контекст вашего кода в расширении: Если вы перехватываете клиентскую процедуру, то ваш код &После или &Перед будет клиентским.
  3. Директивы компиляции: Используете ли вы &НаКлиенте, &НаСервере или &НаСервереБезКонтекста правильно.

Итог и рекомендации

Проблема с методом ДокументСсылка.Метаданные.Имя в клиентском контексте является классической для разработки в 1С:Предприятии. Ее решение всегда сводится к одному принципу: доступ к метаданным должен осуществляться в серверном контексте.

Мы настоятельно рекомендуем использовать подход с созданием отдельной экспортной серверной функции. Это обеспечивает чистоту кода, правильное разделение ответственности между клиентом и сервером и минимизирует риски возникновения подобных ошибок в будущем.

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

← На главную