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


Лучшие практики производительности ASP.NET Core Blazor для взаимодействия с JavaScript (JS interoperability)

Замечание

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

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

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

Это важно

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

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

Передача вызовов между .NET и JavaScript требует дополнительных затрат по нескольким причинам.

  • Вызовы являются асинхронными.
  • Параметры и возвращаемые значения сериализуются в формате JSON, чтобы обеспечить простой механизм преобразования между типами .NET и JavaScript.

Кроме того, для серверных Blazor приложений эти вызовы передаются по сети.

Не используйте чрезмерно подробные вызовы

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

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    foreach (var item in items)
    {
        await JS.InvokeVoidAsync("localStorage.setItem", item.Id, 
            JsonSerializer.Serialize(item));
    }
}

В предшествующем примере для каждого элемента выполняется отдельный вызов для взаимодействия JS. Вместо этого можно ограничить взаимодействие с JS одним вызовом:

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    await JS.InvokeVoidAsync("storeAllInLocalStorage", items);
}

Соответствующая функция JavaScript сохраняет всю коллекцию элементов на клиенте.

function storeAllInLocalStorage(items) {
  items.forEach(item => {
    localStorage.setItem(item.id, JSON.stringify(item));
  });
}

Для приложений Blazor WebAssembly объединение вызовов взаимодействия JS в один вызов обычно позволяет значительно повысить производительность только в том случае, если компонент выполняет большое количество вызовов взаимодействия JS.

Рассмотрите возможность использования синхронных вызовов.

Вызов JavaScript из .NET

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

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

Если вы знаете, что ваш компонент выполняется только в WebAssembly, вы можете выполнять синхронные JS вызовы взаимодействия. Они создают чуть меньше нагрузки, чем асинхронные вызовы, и снижают число циклов отрисовки благодаря отсутствию промежуточного состояния на время ожидания результатов.

Чтобы сделать синхронный вызов из .NET на JavaScript в клиентском компоненте, приведите IJSRuntime к IJSInProcessRuntime, чтобы выполнить межъязыковой вызов JS.

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

При работе с IJSObjectReference компонентами на стороне клиента .NET 5 или более поздней версии вместо этого можно использовать IJSInProcessObjectReference синхронно. IJSInProcessObjectReference реализует IAsyncDisposable/IDisposable и должен быть корректно освобожден для сборки мусора, чтобы предотвратить утечку памяти, как показано в следующем примере:

@inject IJSRuntime JS
@implements IDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var jsInProcess = (IJSInProcessRuntime)JS;
            module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import", 
                "./scripts.js");
            var value = module.Invoke<string>("javascriptFunctionIdentifier");
        }
    }

    ...

    void IDisposable.Dispose()
    {
        if (module is not null)
        {
            await module.Dispose();
        }
    }
}

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

Вызов .NET из JavaScript

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

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

Если вы знаете, что ваш компонент выполняется только в WebAssembly, вы можете выполнять синхронные JS вызовы взаимодействия. Они создают чуть меньше нагрузки, чем асинхронные вызовы, и снижают число циклов отрисовки благодаря отсутствию промежуточного состояния на время ожидания результатов.

Чтобы сделать синхронный вызов из JavaScript в .NET в клиентском компоненте, используйте DotNet.invokeMethod вместо DotNet.invokeMethodAsyncнего.

Синхронные вызовы возможны, если соблюдаются следующие условия:

  • Компонент отображается только для выполнения в WebAssembly.
  • Вызываемая функция возвращает значение синхронно. Эта функция не является методом async и не возвращает объект Task .NET или Promise JavaScript.

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

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

Если вы знаете, что ваш компонент выполняется только в WebAssembly, вы можете выполнять синхронные JS вызовы взаимодействия. Они создают чуть меньше нагрузки, чем асинхронные вызовы, и снижают число циклов отрисовки благодаря отсутствию промежуточного состояния на время ожидания результатов.

Чтобы сделать синхронный вызов из .NET на JavaScript в клиентском компоненте, приведите IJSRuntime к IJSInProcessRuntime, чтобы выполнить межъязыковой вызов JS.

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

При работе с IJSObjectReference компонентами на стороне клиента .NET 5 или более поздней версии вместо этого можно использовать IJSInProcessObjectReference синхронно. IJSInProcessObjectReference реализует IAsyncDisposable/IDisposable и должен быть корректно освобожден для сборки мусора, чтобы предотвратить утечку памяти, как показано в следующем примере:

@inject IJSRuntime JS
@implements IDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var jsInProcess = (IJSInProcessRuntime)JS;
            module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import", 
                "./scripts.js");
            var value = module.Invoke<string>("javascriptFunctionIdentifier");
        }
    }

    ...

    void IDisposable.Dispose()
    {
        if (module is not null)
        {
            await module.Dispose();
        }
    }
}

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

Рассмотрите возможность использования демаршаллированных вызовов.

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

При работе в Blazor WebAssembly есть возможность выполнять демаршалированные вызовы из .NET к JavaScript. Так называются синхронные вызовы, которые не используют сериализацию в JSON для аргументов или возвращаемых значений. Все аспекты управления памятью и преобразования представлений для .NET и JavaScript остаются на усмотрение разработчика.

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

Хотя использование IJSUnmarshalledRuntime обеспечивает наименьшие издержки по сравнению с другими подходами к взаимодействию JS, интерфейсы API JavaScript, необходимые для взаимодействия с этими интерфейсами API, в настоящее время не документированы и могут подвернуться критическим изменениям в будущих выпусках.

function jsInteropCall() {
  return BINDING.js_to_mono_obj("Hello world");
}
@inject IJSRuntime JS

@code {
    protected override void OnInitialized()
    {
        var unmarshalledJs = (IJSUnmarshalledRuntime)JS;
        var value = unmarshalledJs.InvokeUnmarshalled<string>("jsInteropCall");
    }
}

Использование взаимодействия JavaScript [JSImport]/[JSExport]

Взаимодействие JavaScript [JSImport]/[JSExport] для Blazor WebAssembly приложений обеспечивает улучшенную производительность и стабильность API взаимодействия в JS выпусках платформ до ASP.NET Core в .NET 7.

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