Поделиться через


Взаимодействие ASP.NET Core Blazor с JavaScript (JS интероп)

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см. версию этой статьи для .NET 9.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см. версию этой статьи для .NET 9.

Внимание

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

В текущем выпуске см. версию этой статьи для .NET 9.

Приложение Blazor может вызывать функции JavaScript (JS) из методов .NET и методы .NET из функций JS. Такой подход называется взаимодействием с JavaScript (JS интерапом).

Дополнительные рекомендации по взаимодействию JS представлены в следующих статьях:

Примечание.

API взаимодействия JavaScript [JSImport]/[JSExport] доступен для клиентских компонентов в ASP.NET Core в .NET 7 или более поздней версии.

Дополнительные сведения см. в статье JavaScript JSImport/JSExport interop with ASP.NET Core Blazor.

Сжатие для интерактивных компонентов сервера с ненадежными данными

С помощью сжатия, который включен по умолчанию, избегайте создания безопасных (прошедших проверку подлинности или авторизованных) интерактивных компонентов на стороне сервера, отрисовывающих данные из ненадежных источников. Ненадежные источники включают параметры маршрута, строки запроса, данные из обмена JS и любой другой источник данных, которые сторонний пользователь может контролировать (базы данных, внешние сервисы). Для получения дополнительной информации смотрите руководство по ASP.NET CoreBlazorSignalR и руководство по уменьшению угроз при интерактивной серверной обработке на стороне ASP.NET CoreBlazor.

Пакет абстракций взаимодействия и функций JavaScript

Пакет ( NuGet пакет) предоставляет абстракции и функции для взаимодействия между кодом .NET и JavaScript ( ). Справочный dotnet/aspnetcore источник доступен в репозитории GitHub (/src/JSInterop папка). Дополнительные сведения см. в файле репозитория README.md GitHub.

Примечание.

По ссылкам в документации на справочные материалы по .NET обычно загружается ветвь "по умолчанию" репозитория, которая представляет собой текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список «Переключение ветвей или тегов». Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Дополнительные ресурсы для написания JS interop-скриптов на TypeScript.

Взаимодействие с DOM

Изменить DOM с помощью JavaScript (JS) можно только в том случае, если объект не взаимодействует Blazor. Blazor поддерживает представления модели DOM и взаимодействует с объектами DOM напрямую. Если элемент, отображаемый Blazor, изменяется извне с помощью JS напрямую или путем взаимодействия с JS, модель DOM может больше не соответствовать внутреннему представлению Blazor, что может привести к неопределенному поведению. Неопределенное поведение может просто мешать представлению элементов или их функций, а может и представлять угрозу безопасности для приложения или сервера.

Это руководство применяется не только к вашему коду взаимодействия JS, но и к любым библиотекам JS, используемым приложением, включая все, что предоставляется сторонней платформой, например Bootstrap JS и jQuery.

В некоторых примерах документации для изменения элемента JS используется interop только в демонстрационных целях. В таких случаях в тексте появляется предупреждение.

Дополнительные сведения см. в статье Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Класс JavaScript с полем функции типа

Класс JavaScript с полем типа функция не поддерживается интероперабельностью BlazorJS. Используйте функции Javascript в классах.

Неподдерживаемый:GreetingHelpers.sayHello в следующем классе как поле типа функция не обнаруживается с помощью взаимодействия BlazorJS и не может быть выполнено из кода C#:

export class GreetingHelpers {
  sayHello = function() {
    ...
  }
}

Поддерживается:GreetingHelpers.sayHello в следующем классе в качестве функции поддерживается:

export class GreetingHelpers {
  sayHello() {
    ...
  }
}

Также поддерживаются функции со стрелками:

export class GreetingHelpers {
  sayHello = () => {
    ...
  }
}

Избегайте встроенных обработчиков событий

Функцию JavaScript можно вызывать непосредственно из встроенного обработчика событий. В следующем примере используется функция JavaScript, alertUser вызываемая при выборе кнопки пользователем:

<button onclick="alertUser">Click Me!</button>

Однако использование встроенных обработчиков событий является плохим вариантом проектирования для вызова функций JavaScript:

Рекомендуется избегать встроенных обработчиков событий в пользу подходов, которые назначают обработчики в JavaScript с помощью addEventListener, как показано в следующем примере.

AlertUser.razor.js:

export function alertUser() {
  alert('The button was selected!');
}

export function addHandlers() {
  const btn = document.getElementById("btn");
  btn.addEventListener("click", alertUser);
}

AlertUser.razor:

@page "/alert-user"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Alert User</h1>

<p>
    <button id="btn">Click Me!</button>
</p>

@code {
    private IJSObjectReference? module;

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./Components/Pages/AlertUser.razor.js");

            await module.InvokeVoidAsync("addHandlers");
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

В предыдущем примере JSDisconnectedException задерживается во время удаления модуля в случае, если цепь Blazor в SignalR потеряна. Если предыдущий код используется в Blazor WebAssembly приложении, нет подключения, которое можно потерять, поэтому можно удалить блок SignalRtry- и оставить строку, которая удаляет модуль (catch). Дополнительные сведения см. в взаимодействии JavaScript Blazor с ASP.NET Core (JS interop).

Дополнительные сведения см. на следующих ресурсах:

Асинхронные вызовы JavaScript

JS Вызовы взаимодействия являются асинхронными, независимо от того, является ли вызванный код синхронным или асинхронным. Вызовы являются асинхронными, чтобы обеспечить совместимость компонентов между серверными и клиентскими моделями отрисовки. При использовании рендеринга на стороне сервера вызовы interop должны быть асинхронными, поскольку они отправляются через сетевое подключение. Для приложений, использующих исключительно клиентскую отрисовку, поддерживаются синхронные JS вызовы межоперационного взаимодействия.

Дополнительные сведения см. в статье Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Сериализация объектов

Blazor использует System.Text.Json для сериализации в соответствии с приведенными ниже требованиями и поведениями по умолчанию.

  • Типы должны иметь конструктор по умолчанию, методы доступа get/set должны быть общедоступными, а поля никогда не сериализуются.
  • Глобальная сериализация по умолчанию не настраивается во избежание повреждения существующих библиотек компонентов, негативного влияния на производительность и безопасность и снижения уровня надежности.
  • Сериализация имен членов .NET приводит к именам ключей JSON в нижнем регистре.
  • JSON десериализируется как JsonElement экземпляры C#, что позволяет использовать смешанный регистр. Внутреннее приведение свойств модели C# работает должным образом, несмотря на различия между именами ключей JSON и именами свойств C#.
  • Сложные типы платформ, такие как KeyValuePair, могут быть обрезаны триммером IL при публикации и не присутствуют для JS взаимодействия или сериализации JSON/десериализации. Рекомендуем создавать собственные типы для тех типов, которые триммер IL обрезает.
  • Blazorвсегда полагается на рефлексию для JSON-сериализации, в том числе при использовании генерации кода на C#. Установка параметра в JsonSerializerIsReflectionEnabledByDefault значением false в файле проекта приложения приводит к ошибке при попытке сериализации.

Для пользовательской сериализации доступен API JsonConverter. Свойства могут быть помечены атрибутом [JsonConverter], чтобы переопределить дефолтную сериализацию существующего типа данных.

Дополнительные сведения см. в ресурсах в документации по .NET:

Blazor поддерживает оптимизированное взаимодействие с массивом байтов JS, которое позволяет избежать кодирования и декодирования массивов байтов в Base64. Приложение может применять пользовательскую сериализацию и передавать результирующие байты. Дополнительные сведения см. в статье Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

При быстрой сериализации большого количества объектов .NET, а также в ситуациях, когда необходимо сериализовать крупные или многочисленные объекты .NET, Blazor поддерживает демаршализованное взаимодействие с JS. Дополнительные сведения см. в статье Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Задачи очистки DOM во время удаления компонентов

Не выполняйте JS код взаимодействия для задач очистки DOM во время удаления компонентов. Вместо этого используйте MutationObserver шаблон в JavaScript (JS) на клиенте по следующим причинам:

  • Компонент, возможно, был удален из DOM к моменту выполнения вашего кода очистки в Dispose{Async}.
  • Во время серверной отрисовки средство отрисовки Blazor может быть удалено фреймворком к моменту выполнения вашего кода очистки в Dispose{Async}.

Шаблон MutationObserver позволяет запускать функцию при удалении элемента из DOM.

В следующем примере компонент DOMCleanup:

  • Содержит объект <div> с числом idcleanupDiv. Элемент <div> удаляется из DOM вместе с остальной разметкой компонента при удалении компонента из DOM.
  • Загружает класс DOMCleanupJS из DOMCleanup.razor.js файла, затем вызывает его createObserver функцию для настройки MutationObserver обратного вызова. Эти задачи выполняются в методе жизненного цикла.

DOMCleanup.razor:

@page "/dom-cleanup"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>DOM Cleanup Example</h1>

<div id="cleanupDiv"></div>

@code {
    private IJSObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./Components/Pages/DOMCleanup.razor.js");

            await module.InvokeVoidAsync("DOMCleanup.createObserver");
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

В предыдущем примере JSDisconnectedException задерживается во время удаления модуля в случае, если цепь Blazor в SignalR потеряна. Если предыдущий код используется в Blazor WebAssembly приложении, нет подключения, которое можно потерять, поэтому можно удалить блок SignalRtry- и оставить строку, которая удаляет модуль (catch). Дополнительные сведения см. в взаимодействии JavaScript Blazor с ASP.NET Core (JS interop).

В следующем примере MutationObserver обратный вызов выполняется каждый раз при изменении DOM. Выполните код очистки, когда if инструкция подтверждает, что целевой элемент (cleanupDiv) был удален (if (targetRemoved) { ... }). Важно отключить и удалить MutationObserver, чтобы избежать утечки памяти после выполнения кода очистки.

DOMCleanup.razor.js помещается параллельно с предыдущим DOMCleanup компонентом:

export class DOMCleanup {
  static observer;

  static createObserver() {
    const target = document.querySelector('#cleanupDiv');

    this.observer = new MutationObserver(function (mutations) {
      const targetRemoved = mutations.some(function (mutation) {
        const nodes = Array.from(mutation.removedNodes);
        return nodes.indexOf(target) !== -1;
      });

      if (targetRemoved) {
        // Cleanup resources here
        // ...

        // Disconnect and delete MutationObserver
        this.observer && this.observer.disconnect();
        delete this.observer;
      }
    });

    this.observer.observe(target.parentNode, { childList: true });
  }
}

window.DOMCleanup = DOMCleanup;

Предыдущие подходы присоединяют MutationObserver к target.parentNode, которая работает до тех пор, пока сам parentNode не будет удален из DOM. Это распространенный сценарий, например при переходе на новую страницу, что приводит к удалению всего компонента страницы из DOM. В таких случаях любые дочерние компоненты, наблюдающие изменения на странице, не удаляются должным образом.

Не предполагайте, что наблюдение за document.body, а не target.parentNode, является лучшей целью. Наблюдение за document.body влияет на производительность, так как логика обратного вызова выполняется для всех обновлений DOM, независимо от того, имеют ли они ничего общего с вашим элементом. Используйте любой из следующих подходов:

  • В случаях, когда возможно определить подходящий родительский узел для наблюдения, используйте MutationObserver с ним. Идеально, если этот предок ограничивается изменениями, которые вы хотите наблюдать, а не на document.body.
  • Вместо использования MutationObserverрассмотрите возможность использования пользовательского элемента и disconnectedCallback. Событие всегда срабатывает, когда ваш пользовательский элемент отключен, вне зависимости от его расположения в DOM относительно изменения DOM.

Взаимодействие JavaScript без цепи

Этот раздел применяется только к серверным приложениям.

Вызовы взаимодействия с JavaScriptJS не могут выполняться после отключения схемы BlazorSignalR. Без канала во время удаления компонентов или в любое другое время, когда канал не существует, следующий метод вызывает сбой и записывает сообщение о том, что канал отключен как:JSDisconnectedException

Чтобы избежать ведения журнала JSDisconnectedException или записи пользовательской информации, перехватите исключение в блоке try-catch.

Для примера утилизации компонентов:

  • Серверный компонент реализует IAsyncDisposable.
  • module — это IJSObjectReference для модуля JS.
  • JSDisconnectedException перехватывается и не регистрируется.
  • Кроме того, вы можете записывать пользовательские сведения в инструкцию catch на любом выбранном уровне журнала. В следующем примере не регистрируется пользовательская информация, так как предполагается, что разработчик не заботится о том, когда или где каналы отключены во время удаления компонентов.
async ValueTask IAsyncDisposable.DisposeAsync()
{
    try
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Если необходимо очистить собственные JS объекты или выполнить другой JS код на клиенте после потери канала в серверном Blazor приложении, используйте MutationObserver шаблон JS в клиенте. Шаблон MutationObserver позволяет запускать функцию при удалении элемента из DOM.

Дополнительные сведения см. в следующих статьях:

  • Обработка ошибок в приложениях ASP.NET Core: В разделе по JavaScript interop обсуждается обработка ошибок во взаимодействия сценариях.
  • ASP.NET Core Razor утилизация компонентов: в статье описывается, как реализовать шаблоны утилизации в компонентах Razor.

Кэшированные файлы JavaScript

Файлы JavaScript (JS) и другие статические ресурсы обычно не кэшируются на клиентах во время разработки в среде Development. Во время разработки запросы статических ресурсов содержат заголовок Cache-Control со значением no-cache или max-age со значением, равным нулю (0).

Во время производства в среде Production файлы JS обычно кэшируются клиентами.

Чтобы отключить кэширование на стороне клиента в браузерах, разработчики, как правило, используют один из следующих подходов:

  • Отключение кэширования, когда открыта консоль средств разработчика браузера. Руководство можно найти в документации по инструментам разработчика для каждого разработчика браузеров.
  • Перезагрузите вручную любую веб-страницу в браузере приложения Blazor, чтобы заново загрузить файлы JS с сервера. Промежуточное ПО кэширования HTTP в ASP.NET Core всегда принимает действительный заголовок no-cache, отправляемый клиентом.

Дополнительные сведения см. в разделе:

Ограничения на размер вызовов интеропа с JavaScript

Этот раздел относится только к интерактивным компонентам в серверных приложениях. Для компонентов на стороне клиента фреймворк не накладывает ограничений на размер входных и выходных данных для взаимодействия с JavaScript (JS).

Для интерактивных компонентов в серверных приложениях вызовы взаимодействия, передавающие данные от клиента на сервер, ограничены максимальным размером JS входящих сообщений, разрешенным для методов концентратора, что контролируется SignalR (по умолчанию: 32 КБ). Если размер сообщений JS (из SignalR в .NET) превышает MaximumReceiveMessageSize, возникает ошибка. Платформа не накладывает ограничение на размер сообщений SignalR от концентратора клиенту. Дополнительные сведения об ограничении размера, сообщениях об ошибках и рекомендациях по работе с ограничениями размера сообщений см. в руководстве по ASP.NET CoreBlazorSignalR.

Определение места выполнения приложения

Если для приложения важно знать, где выполняется код для JS взаимодействия, используйте OperatingSystem.IsBrowser для определения того, выполняется ли компонент в контексте браузера на WebAssembly.