Бөлісу құралы:


Запуск .NET из JavaScript

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

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

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

Примечание.

В этой статье рассматривается запуск .NET из JS приложений без каких-либо зависимостей Blazor. Рекомендации по использованию [JSImport][JSExport]/взаимодействия в приложениях см. в Blazor WebAssembly разделе "Импорт иJS экспорт JavaScriptJS" с ASP.NET Core.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 рабочую нагрузку, содержащую экспериментальные шаблоны проектов для начала работы с .NET в WebAssembly в приложении браузера (приложение webAssembly Browser) или в консольном приложении на основе Node.js (консольное приложение WebAssembly). Эта рабочая нагрузка не требуется, если планируется интегрировать 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 создается в рамках выходных данных сборки приложения.

    Внимание

    Чтобы интегрироваться с существующим приложением, скопируйте содержимое выходной папки публикации† в ресурсы развертывания существующего приложения, чтобы его можно было обслуживать вместе с остальной частью приложения. Для рабочих развертываний опубликуйте приложение с 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 обещаний. Большинство маршаллированных типов работают в обоих направлениях в качестве параметров и возвращаемых значений в импортированных и экспортированных методах.

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

.NET JavaScript Nullable Task➔Кому Promise 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) сопоставлен с JSArray Numbers.
  • При передаче JS значения в C# со значением неправильного типа платформа создает исключение в большинстве случаев. Платформа не выполняет проверку JSтипа во время компиляции.
  • JSObjectTask, Exceptionи создание GCHandle и ArraySegment прокси-сервер. Вы можете активировать удаление в коде разработчика или разрешить сборку мусора .NET (GC) удалять объекты позже. Эти типы несут значительные затраты на производительность.
  • Array: маршалинг массива создает копию массива в JS или .NET.
  • MemoryView
    • MemoryViewJS— это класс среды выполнения .NET WebAssembly для маршалирования Span и ArraySegment.
    • В отличие от маршалинга массива, маршалинг или Span ArraySegment не создает копию базовой памяти.
    • MemoryView может быть правильно создано средой выполнения .NET WebAssembly. Поэтому невозможно импортировать JS функцию как метод .NET, имеющий параметр Span или ArraySegment.
    • MemoryView создается только для Span срока вызова взаимодействия. Как Span и в стеке вызовов, который не сохраняется после вызова взаимодействия, невозможно экспортировать метод .NET, который возвращает Span.
    • MemoryView создано для ArraySegment выживания после вызова взаимодействия и полезно для совместного использования буфера. Вызов dispose() созданного MemoryView ArraySegment для удаления прокси-сервера и открепляет базовый массив .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.

В следующем примере каждый метод экспортируется 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} заполнитель — это моникер целевой платформы.

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

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

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 .

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