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


Взаимодействие JavaScript [JSImport]/[JSExport] с проектом приложения браузера WebAssembly

Примечание.

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

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

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

Внимание

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

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

В этой статье объясняется, как настроить проект приложения браузера WebAssembly для запуска .NET из JavaScript (JS) с помощью JS[JSImport]/[JSExport] взаимодействия. Дополнительные сведения и примеры см. в статье JavaScript "[JSImport]'/[JSExport]" interop in .NET WebAssembly.

Дополнительные рекомендации см. в руководстве по настройке и размещению приложений .NET WebAssembly в репозитории GitHub .NET Runtime (dotnet/runtime).

Существующие JS приложения могут использовать расширенную поддержку WebAssembly на стороне клиента для повторного использования библиотек .NET из JS или для создания романа. Приложения и платформы на основе NET.

Примечание.

В этой статье рассматривается запуск .NET из JS приложений без каких-либо зависимостей Blazor. Рекомендации по использованию [JSImport]/[JSExport] взаимодействия в приложениях см. в Blazor WebAssembly разделе взаимодействия JavaScript JSImport/JSExport с ASP.NET Core.Blazor

Эти подходы подходы подходят только при ожидании Blazor запуска приложения в WebAssembly (WASM). Библиотеки могут проверить среду выполнения, чтобы определить, работает WASM ли приложение путем вызова OperatingSystem.IsBrowser.

Необходимые компоненты

Пакет SDK для .NET (последняя версия)

Установите рабочую нагрузку wasm-tools в командной оболочке администрирования, которая приводит к соответствующим целевым объектам MSBuild:

dotnet workload install wasm-tools

Средства также можно установить с помощью установщика Visual Studio в ASP.NET и рабочей нагрузке веб-разработки в установщике Visual Studio. Выберите параметр средств сборки .NET WebAssembly из списка необязательных компонентов.

При необходимости установите рабочую нагрузку wasm-experimental , которая добавляет следующие экспериментальные шаблоны проектов:

  • Приложение браузера WebAssembly для начала работы с .NET в WebAssembly в приложении браузера.
  • Консольное приложение WebAssembly для начала работы с узлом.jsконсольное приложение на основе.

После установки рабочей нагрузки эти новые шаблоны можно выбрать при создании нового проекта. Эта рабочая нагрузка не требуется, если планируется интегрировать JS[JSExport][JSImport]/взаимодействие в существующее JS приложение.

dotnet workload install wasm-experimental

Шаблоны также можно установить из пакета NuGet с помощью следующей Microsoft.NET.Runtime.WebAssembly.Templates команды:

dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates

Дополнительные сведения см. в разделе " Экспериментальная рабочая нагрузка и шаблоны проектов ".

Пространство имен

API взаимодействия, описанный JS в этой статье, управляется атрибутами в System.Runtime.InteropServices.JavaScript пространстве имен.

Конфигурация проекта

Чтобы настроить проект (.csproj) для включения JS взаимодействия:

  • Задайте моникер целевой платформы ({TARGET FRAMEWORK}заполнитель):

    <TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
    

    Поддерживается .NET 7 (net7.0) или более поздней версии.

  • AllowUnsafeBlocks Включите свойство, которое позволяет генератору кода в компиляторе Roslyn использовать указатели для JS взаимодействия:

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

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

    JS API взаимодействия требует включенияAllowUnsafeBlocks. Будьте осторожны при реализации собственного небезопасного кода в приложениях .NET, что может привести к рискам безопасности и стабильности. Дополнительные сведения см. в разделе "Небезопасный код", "типы указателей" и указатели функций.

Ниже приведен пример файла проекта (.csproj) после настройки. Заполнитель {TARGET FRAMEWORK} — это целевая платформа:

<Project Sdk="Microsoft.NET.Sdk.WebAssembly">

  <PropertyGroup>
    <TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>

</Project>
  • Задайте моникер целевой платформы:

    <TargetFramework>net7.0</TargetFramework>
    

    Поддерживается .NET 7 (net7.0) или более поздней версии.

  • Укажите browser-wasm идентификатор среды выполнения:

    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
    
  • Укажите тип выходных данных исполняемого файла:

    <OutputType>Exe</OutputType>
    
  • AllowUnsafeBlocks Включите свойство, которое позволяет генератору кода в компиляторе Roslyn использовать указатели для JS взаимодействия:

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

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

    JS API взаимодействия требует включенияAllowUnsafeBlocks. Будьте осторожны при реализации собственного небезопасного кода в приложениях .NET, что может привести к рискам безопасности и стабильности. Дополнительные сведения см. в разделе "Небезопасный код", "типы указателей" и указатели функций.

  • Укажите WasmMainJSPath , чтобы указать файл на диске. Этот файл публикуется с приложением, но использование файла не требуется, если вы интегрируете .NET в существующее JS приложение.

    В следующем примере JS файл на диске доступен main.js, но любое JS имя файла является допустимым:

    <WasmMainJSPath>main.js</WasmMainJSPath>
    

Пример файла проекта (.csproj) после настройки:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
    <OutputType>Exe</OutputType>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <WasmMainJSPath>main.js</WasmMainJSPath>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

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

API в следующем примере импортируются из dotnet.js. Эти API позволяют настраивать именованные модули, которые можно импортировать в код C# и вызывать методы, предоставляемые кодом .NET, в том числе Program.Main.

Внимание

Импорт и экспорт в этой статье определяются с точки зрения .NET:

  • Методы импорта JS приложения, чтобы их можно было вызывать из .NET.
  • Приложение экспортирует методы .NET, чтобы их можно было вызывать из JS.

В следующем примере :

  • Файл dotnet.js используется для создания и запуска среды выполнения .NET WebAssembly. dotnet.js создается в рамках выходных данных сборки приложения.

    Внимание

    Чтобы интегрироваться с существующим приложением, скопируйте содержимое выходной папки публикации† в ресурсы развертывания существующего приложения, чтобы его можно было обслуживать вместе с rest приложением. Для рабочих развертываний опубликуйте приложение с dotnet publish -c Release помощью команды в командной оболочке и разверните содержимое выходной папки с помощью приложения.

    † Папка вывода публикации — это целевое расположение профиля публикации. По умолчанию для Release профиля в .NET 8 или более поздней версии используется bin/Release/{TARGET FRAMEWORK}/publish{TARGET FRAMEWORK} заполнитель — целевая платформа (например, net8.0).

  • dotnet.create() настраивает среду выполнения .NET WebAssembly.

  • setModuleImports связывает имя с модулем функций для импорта JS в .NET. Модуль JS содержит dom.setInnerText функцию, которая принимает и селектор элементов и время для отображения текущего времени стоп-часов в пользовательском интерфейсе. Имя модуля может быть любой строкой (она не должна быть именем файла), но она должна соответствовать имени, используемому с JSImportAttribute (описано далее в этой статье). Функция dom.setInnerText импортируется в C# и вызывается методом SetInnerTextC#. Этот SetInnerText метод показан далее в этом разделе.

  • exports.StopwatchSample.Reset() вызовы в .NET (StopwatchSample.Reset) из JS. Метод Reset C# перезапускает стоп-часы, если он запущен или сбрасывает его, если он не запущен. Этот Reset метод показан далее в этом разделе.

  • exports.StopwatchSample.Toggle() вызовы в .NET (StopwatchSample.Toggle) из JS. Метод Toggle C# запускает или останавливает стоп-часы в зависимости от того, запущен ли он в данный момент или нет. Этот Toggle метод показан далее в этом разделе.

  • runMain() выполняется Program.Main.

  • setModuleImports связывает имя с модулем функций для импорта JS в .NET. Модуль JS содержит функцию, которая возвращает текущий window.location.href адрес страницы (URL-адрес). Имя модуля может быть любой строкой (она не должна быть именем файла), но она должна соответствовать имени, используемому с JSImportAttribute (описано далее в этой статье). Функция window.location.href импортируется в C# и вызывается методом GetHRefC#. Этот GetHRef метод показан далее в этом разделе.

  • exports.MyClass.Greeting() вызовы в .NET (MyClass.Greeting) из JS. Метод Greeting C# возвращает строку, содержащую результат вызова window.location.href функции. Этот Greeting метод показан далее в этом разделе.

  • dotnet.run() выполняется Program.Main.

JS модуль:

import { dotnet } from './_framework/dotnet.js'

const { setModuleImports, getAssemblyExports, getConfig, runMain } = await dotnet
  .withApplicationArguments("start")
  .create();

setModuleImports('main.js', {
  dom: {
    setInnerText: (selector, time) => 
      document.querySelector(selector).innerText = time
  }
});

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);

document.getElementById('reset').addEventListener('click', e => {
  exports.StopwatchSample.Reset();
  e.preventDefault();
});

const pauseButton = document.getElementById('pause');
pauseButton.addEventListener('click', e => {
  const isRunning = exports.StopwatchSample.Toggle();
  pauseButton.innerText = isRunning ? 'Pause' : 'Start';
  e.preventDefault();
});

await runMain();
import { dotnet } from './_framework/dotnet.js'

const { setModuleImports, getAssemblyExports, getConfig } = await dotnet
  .withDiagnosticTracing(false)
  .withApplicationArgumentsFromQuery()
  .create();

setModuleImports('main.js', {
  window: {
    location: {
      href: () => globalThis.window.location.href
    }
  }
});

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const text = exports.MyClass.Greeting();
console.log(text);

document.getElementById('out').innerHTML = text;
await dotnet.run();
import { dotnet } from './dotnet.js'

const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);

const { setModuleImports, getAssemblyExports, getConfig } = 
  await dotnet.create();

setModuleImports("main.js", {
  window: {
    location: {
      href: () => globalThis.window.location.href
    }
  }
});

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const text = exports.MyClass.Greeting();
console.log(text);

document.getElementById("out").innerHTML = text;
await dotnet.run();

Чтобы импортировать JS функцию, чтобы ее можно было вызвать из C#, используйте новую JSImportAttribute сигнатуру соответствующего метода. Первым параметром JSImportAttribute является имя функции для JS импорта, а второй параметр — имя модуля.

В следующем примере dom.setInnerText функция вызывается из main.js модуля при SetInnerText вызове метода:

[JSImport("dom.setInnerText", "main.js")]
internal static partial void SetInnerText(string selector, string content);

В следующем примере window.location.href функция вызывается из main.js модуля при GetHRef вызове метода:

[JSImport("window.location.href", "main.js")]
internal static partial string GetHRef();

В импортированной сигнатуре метода можно использовать типы .NET для параметров и возвращаемых значений, которые автоматически маршалируются средой выполнения. Используется JSMarshalAsAttribute<T> для управления маршаллами импортированных параметров метода. Например, можно выбрать маршалировать как long System.Runtime.InteropServices.JavaScript.JSType.Number или System.Runtime.InteropServices.JavaScript.JSType.BigInt. Обратные вызовы можно передавать Action/Func<TResult> в качестве параметров, которые маршалируются как вызываемые JS функции. Вы можете передавать JS как ссылки на управляемые объекты, так и маршалируются как прокси-объекты, сохраняя объект в живых по границе, пока прокси-сервер не собирает мусор. Вы также можете импортировать и экспортировать асинхронные методы с результатом Task , которые маршалируются в качестве JS обещаний. Большинство маршаллированных типов работают в обоих направлениях в качестве параметров и возвращаемых значений в импортированных и экспортированных методах.

Дополнительные сведения о сопоставлении типов и примеры см . в статье JavaScript "[JSImport]"/[JSExport]" interop in .NET WebAssembly.

Функции, доступные в глобальном пространстве имен, можно импортировать с помощью globalThis префикса в имени функции и с помощью атрибута [JSImport] без предоставления имени модуля. В следующем примере console.log префикс с префиксом globalThis. Импортированная функция вызывается методом C#Log, который принимает строковое сообщение C# (message) и маршалирует строку C# дляStringJSconsole.log:

[JSImport("globalThis.console.log")]
internal static partial void Log([JSMarshalAs<JSType.String>] string message);

Чтобы экспортировать метод .NET, чтобы его можно было вызвать из JS, используйте метод JSExportAttribute.

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

  • Метод Toggle запускает или останавливает секундомер в зависимости от состояния выполнения.
  • Метод Reset перезапускает стоп-часы, если он запущен или сбрасывает его, если он не запущен.
  • Метод IsRunning указывает, запущен ли стоп-часы.
[JSExport]
internal static bool Toggle()
{
    if (stopwatch.IsRunning)
    {
        stopwatch.Stop();
        return false;
    }
    else
    {
        stopwatch.Start();
        return true;
    }
}

[JSExport]
internal static void Reset()
{
    if (stopwatch.IsRunning)
        stopwatch.Restart();
    else
        stopwatch.Reset();

    Render();
}

[JSExport]
internal static bool IsRunning() => stopwatch.IsRunning;

В следующем примере Greeting метод возвращает строку, содержащую результат вызова GetHRef метода. Как показано ранее, GetHref метод C# вызывает JS window.location.href функцию из main.js модуля. window.location.href возвращает текущий адрес страницы (URL-адрес):

[JSExport]
internal static string Greeting()
{
    var text = $"Hello, World! Greetings from {GetHRef()}";
    Console.WriteLine(text);
    return text;
}

Экспериментальная рабочая нагрузка и шаблоны проектов

Чтобы продемонстрировать функциональные JS возможности взаимодействия и получить JS шаблоны проектов взаимодействия, установите рабочую нагрузку wasm-experimental :

dotnet workload install wasm-experimental

wasm-experimental Рабочая нагрузка содержит два шаблона проекта: wasmbrowser и wasmconsole. Эти шаблоны являются экспериментальными в настоящее время, что означает, что рабочий процесс разработчика для шаблонов развивается. Однако api и .NET JS , используемые в шаблонах, поддерживаются в .NET 8 и предоставляют основу для использования .NET в WASM JS.

Шаблоны также можно установить из пакета NuGet с помощью следующей Microsoft.NET.Runtime.WebAssembly.Templates команды:

dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates

Приложение браузера

Вы можете создать приложение браузера с wasmbrowser шаблоном из командной строки, которое создает веб-приложение, демонстрирующее использование .NET и JS вместе в браузере:

dotnet new wasmbrowser

Кроме того, в Visual Studio можно создать приложение с помощью WebAssembly Browser App шаблона проекта.

Создайте приложение из Visual Studio или с помощью .NET CLI:

dotnet build

Создайте и запустите приложение из Visual Studio или с помощью .NET CLI:

dotnet run

Кроме того, установите и используйте dotnet serve команду:

dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/publish

В предыдущем примере {TARGET FRAMEWORK} заполнитель — это моникер целевой платформы.

Узел.js консольное приложение

Консольное приложение можно создать с wasmconsole помощью шаблона, которое создает приложение, которое выполняется в WASM качестве узла или консольного приложения версии 8:js

dotnet new wasmconsole

Кроме того, в Visual Studio можно создать приложение с помощью WebAssembly Console App шаблона проекта.

Создайте приложение из Visual Studio или с помощью .NET CLI:

dotnet build

Создайте и запустите приложение из Visual Studio или с помощью .NET CLI:

dotnet run

Кроме того, запустите любой статический файловый сервер из выходного каталога публикации, содержащего main.mjs файл:

node bin/$(Configuration)/{TARGET FRAMEWORK}/{PATH}/main.mjs

В предыдущем примере {TARGET FRAMEWORK} заполнитель — это моникер целевой платформы, а {PATH} заполнитель — путь к файлу main.mjs .

Дополнительные ресурсы