Запуск .NET из JavaScript

В этой статье объясняется, как запустить .NET из JavaScript (JS) с помощью/JS[JSImport][JSExport] взаимодействия.

Дополнительные рекомендации см. в руководстве по настройке и размещению приложений .NET WebAssembly в репозитории GitHub .NET Runtime (dotnet/runtime). Мы планируем обновить эту статью, чтобы включить новую информацию в кросс-связанные рекомендации в последней части 2023 или начале 2024 года.

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

Примечание.

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

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

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

Пакет SDK для .NET 7.0

Установите последнюю версию пакета SDK для .NET.

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

dotnet workload install wasm-tools

При необходимости установите wasm-experimental рабочую нагрузку, содержащую экспериментальные шаблоны проектов для начала работы с .NET в WebAssembly в приложении браузера (приложение webAssembly Browser) или в консольном приложении на основе Node.js (консольное приложение WebAssembly). Эта рабочая нагрузка не требуется, если планируется интегрировать JS[JSExport][JSImport]/взаимодействие в существующее JS приложение.

dotnet workload install wasm-experimental

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

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

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

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

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

  • Целевой net7.0 объект или более поздней версии:

    <TargetFramework>net7.0</TargetFramework>
    
  • Укажите 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 создается в рамках выходных данных сборки приложения и находится в папке AppBundle :

    bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle

    Заполнитель {BUILD CONFIGURATION} — это конфигурация сборки (например, Debug, Release), а {TARGET FRAMEWORK} заполнитель — целевая платформа (например, net7.0).

    Важно!

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

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

  • 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 './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 импорта, а второй параметр — имя модуля.

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

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

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

В следующей таблице указаны поддерживаемые сопоставления типов.

.NET JavaScript Nullable Task➔toPromise JSMarshalAs Дополнительные Array of
Boolean Boolean Поддерживается Поддерживается Поддерживается Не поддерживаются
Byte Number Поддерживается Поддерживается Поддерживается Поддерживается
Char String Поддерживается Поддерживается Поддерживается Не поддерживаются
Int16 Number Поддерживается Поддерживается Поддерживается Не поддерживаются
Int32 Number Поддерживается Поддерживается Поддерживается Поддерживается
Int64 Number Поддерживается Поддерживается Не поддерживаются Не поддерживаются
Int64 BigInt Поддерживается Поддерживается Не поддерживаются Не поддерживаются
Single Number Поддерживается Поддерживается Поддерживается Не поддерживаются
Double Number Поддерживается Поддерживается Поддерживается Поддерживается
IntPtr Number Поддерживается Поддерживается Поддерживается Не поддерживаются
DateTime Date Поддерживается Поддерживается Не поддерживаются Не поддерживаются
DateTimeOffset Date Поддерживается Поддерживается Не поддерживаются Не поддерживаются
Exception Error Не поддерживаются Поддерживается Поддерживается Не поддерживаются
JSObject Object Не поддерживаются Поддерживается Поддерживается Поддерживается
String String Не поддерживаются Поддерживается Поддерживается Поддерживается
Object Any Не поддерживаются Поддерживается Не поддерживаются Поддерживается
Span<Byte> MemoryView Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Span<Int32> MemoryView Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Span<Double> MemoryView Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
ArraySegment<Byte> MemoryView Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
ArraySegment<Int32> MemoryView Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
ArraySegment<Double> MemoryView Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Task Promise Не поддерживаются Не поддерживаются Поддерживается Не поддерживаются
Action Function Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Action<T1> Function Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Action<T1, T2> Function Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Action<T1, T2, T3> Function Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Func<TResult> Function Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Func<T1, TResult> Function Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Func<T1, T2, TResult> Function Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются
Func<T1, T2, T3, TResult> Function Не поддерживаются Не поддерживаются Не поддерживаются Не поддерживаются

Следующие условия применяются к сопоставлению типов и маршализированным значениям:

  • Столбец Array of указывает, можно ли маршалировать тип .NET в виде JSArray. Пример: C# int[] (Int32) сопоставлен с JSArrayNumbers.
  • При передаче JS значения в C# со значением неправильного типа платформа создает исключение в большинстве случаев. Платформа не выполняет тип времени компиляции проверка в JS.
  • JSObjectTask, Exceptionи создание GCHandle и ArraySegment прокси-сервер. Вы можете активировать удаление в коде разработчика или разрешить сборку мусора .NET (GC) удалять объекты позже. Эти типы несут значительные затраты на производительность.
  • Array: маршалинг массива создает копию массива в JS или .NET.
  • MemoryView
    • MemoryViewJS— это класс среды выполнения .NET WebAssembly для маршалирования Span и ArraySegment.
    • В отличие от маршалинга массива, маршалинг или SpanArraySegment не создает копию базовой памяти.
    • MemoryView может быть правильно создано средой выполнения .NET WebAssembly. Поэтому невозможно импортировать JS функцию как метод .NET, имеющий параметр Span или ArraySegment.
    • MemoryView создается только для Span срока вызова взаимодействия. Как Span и в стеке вызовов, который не сохраняется после вызова взаимодействия, невозможно экспортировать метод .NET, который возвращает Span.
    • MemoryView создано для ArraySegment выживания после вызова взаимодействия и полезно для совместного использования буфера. Вызов dispose() созданного MemoryViewArraySegment для удаления прокси-сервера и открепляет базовый массив .NET. Рекомендуется вызывать dispose() блок try-finally для MemoryView.

Функции, доступные в глобальном пространстве имен, можно импортировать с помощью 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.

В следующем примере Greeting метод возвращает строку, содержащую результат вызова GetHRef метода. Как показано ранее, GetHref метод C# вызывает JSwindow.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 7 и предоставляют основу для использования .NET в WASMJS.

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

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

dotnet new wasmbrowser

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

dotnet build

Встроенное приложение находится в каталоге bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle . Заполнитель {BUILD CONFIGURATION} — это конфигурация сборки (например, Debug, Release). Заполнитель {TARGET FRAMEWORK} — это моникер целевой платформы (например, net7.0).

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

dotnet run

Кроме того, запустите любой статический файловый сервер из AppBundle каталога:

dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle

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

Консольное приложение Node.js

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

dotnet new wasmconsole

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

dotnet build

Встроенное приложение находится в каталоге bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle . Заполнитель {BUILD CONFIGURATION} — это конфигурация сборки (например, Debug, Release). Заполнитель {TARGET FRAMEWORK} — это моникер целевой платформы (например, net7.0).

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

dotnet run

Кроме того, запустите любой статический файловый сервер из AppBundle каталога:

node bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle/main.mjs

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

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