JavaScript [JSImport]
/[JSExport]
と WebAssembly Browser App プロジェクトとの相互運用
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
この記事では、JS[JSImport]
/[JSExport]
相互運用機能を使用して JavaScript (JS) から .NET を実行するように WebAssembly Browser App プロジェクトをセットアップする方法について説明します。 詳細と例については、「.NET WebAssembly の JavaScript `[JSImport]`/`[JSExport]` 相互運用機能」をご覧ください。
その他のガイダンスについては、.NET ランタイム (dotnet/runtime
) GitHub リポジトリの .NET WebAssembly アプリケーションの構成とホストに関するガイダンスを参照してください。
既存の JS アプリでは、拡張されたクライアント側 WebAssembly サポートを使用して、JS から .NET ライブラリを再利用することや、新しい .NET ベースのアプリやフレームワークを構築することができます。
Note
この記事では、Blazor に依存することなく、JS アプリから .NET を実行することに重点を置いています。 Blazor WebAssembly アプリでの [JSImport]
/[JSExport]
相互運用機能の使用に関するガイダンスについては、「ASP.NET Core Blazor を使用した JavaScript JSImport/JSExport 相互運用」をご覧ください。
これらの方法は、WebAssembly (WASM) でのみ Blazor アプリを実行することを想定している場合に適しています。 ライブラリでは、OperatingSystem.IsBrowser を呼び出すことによって、アプリが WASM で実行されているかどうかを判断するランタイム チェックを行うことができます。
前提条件
.NET Core SDK (最新バージョン)
管理コマンド シェルで wasm-tools
ワークロードをインストールします。これにより、関連する MSBuild ターゲットを導入できます。
dotnet workload install wasm-tools
これらのツールをインストールするには、Visual Studio インストーラーの [ASP.NET と Web の開発] ワークロードにある Visual Studio のインストーラーを使用することもできます。 省略可能なコンポーネント一覧から [.NET WebAssembly ビルド ツール] オプションを選びます。
必要に応じて、次の試験的なプロジェクト テンプレートを追加する wasm-experimental
ワークロードをインストールします。
- ブラウザー アプリでの .NET on WebAssembly の使用を開始するための WebAssembly ブラウザー アプリ。
- Node.js ベースのコンソール アプリで作業を開始するための WebAssembly コンソール アプリ。
ワークロードをインストールした後、新しいプロジェクトを作成するときに、これらの新しいテンプレートを選択できます。 JS[JSImport]
/[JSExport]
相互運用を既存の JS アプリに統合する予定がある場合、このワークロードは必要ありません。
dotnet workload install wasm-experimental
次のコマンドを使用して、Microsoft.NET.Runtime.WebAssembly.Templates
NuGet パッケージからテンプレートをインストールすることもできます。
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
詳細については、「試験段階のワークロードとプロジェクト テンプレート」セクションを参照してください。
名前空間
この記事で説明する JS 相互運用 API は、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>
WASM での JavaScript 相互運用
次の例の API は dotnet.js
からインポートされます。 これらの API を使用すると、C# コードにインポートできる名前付きモジュールを設定し、Program.Main
など、.NET コードによって公開されるメソッドを呼び出すことができます。
重要
この記事全体の "インポート" と "エクスポート" は、.NET の観点から定義されています。
- アプリによって JS メソッドがインポートされ、.NET から呼び出せるようになります。
- アプリによって .NET メソッドがエクスポートされ、JS から呼び出せるようになります。
次に例を示します。
dotnet.js
ファイルは、.NET WebAssembly ランタイムの作成と起動に使用されます。dotnet.js
は、アプリのビルド出力の一部として生成されます。重要
既存のアプリと統合するには、発行出力フォルダー† の内容を既存のアプリのデプロイ資産にコピーして、アプリの rest と共に提供できるようにします。 運用環境のデプロイの場合は、コマンド シェルで
dotnet publish -c Release
コマンドを使用してアプリを発行し、出力フォルダーの内容をアプリと共にデプロイします。† 発行出力フォルダーは、発行プロファイルのターゲットの場所です。 .NET 8 以降の Release プロファイルの既定値は
bin/Release/{TARGET FRAMEWORK}/publish
です。この{TARGET FRAMEWORK}
プレースホルダーはターゲット フレームワークです (たとえばnet8.0
)。dotnet.create()
では、.NET WebAssembly ランタイムを設定します。
setModuleImports
では、.NET にインポートするための JS 関数のモジュールに名前を関連付けます。 JS モジュールにはdom.setInnerText
関数があります。これを使うと、要素セレクターと時間を受け取り、現在のストップウォッチ時間を UI に表示することができます。 モジュールの名前を任意の文字列にすることはできますが (ファイル名である必要はありません)、JSImportAttribute
で使用される名前と一致する必要があります (この記事の後半で説明します)。dom.setInnerText
関数は C# にインポートされ、C# メソッドSetInnerText
によって呼び出されます。SetInnerText
メソッドは、このセクションの後半に示されています。exports.StopwatchSample.Reset()
では、JS から .NET (StopwatchSample.Reset
) への呼び出しを行います。Reset
C# メソッドを使うと、ストップウォッチが実行中の場合は再起動し、実行されていない場合はリセットすることができます。Reset
メソッドは、このセクションの後半に示されています。exports.StopwatchSample.Toggle()
では、JS から .NET (StopwatchSample.Toggle
) への呼び出しを行います。Toggle
C# メソッドを使うと、現在実行中かどうかに応じてストップウォッチを開始または停止できます。Toggle
メソッドは、このセクションの後半に示されています。runMain()
ではProgram.Main
を実行します。
setModuleImports
では、.NET にインポートするための JS 関数のモジュールに名前を関連付けます。 JS モジュールには、現在のページ アドレス (URL) を返すwindow.location.href
関数が含まれています。 モジュールの名前を任意の文字列にすることはできますが (ファイル名である必要はありません)、JSImportAttribute
で使用される名前と一致する必要があります (この記事の後半で説明します)。window.location.href
関数は C# にインポートされ、C# メソッドGetHRef
によって呼び出されます。GetHRef
メソッドは、このセクションの後半に示されています。exports.MyClass.Greeting()
では、JS から .NET (MyClass.Greeting
) への呼び出しを行います。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();
C# から呼び出せるように JS 関数をインポートするには、一致するメソッド シグネチャで新しい JSImportAttribute を使用します。 JSImportAttribute の最初のパラメーターは、インポートする JS 関数の名前で、2 番目のパラメーターはモジュールの名前です。
次の例では、dom.setInnerText
関数は、SetInnerText
メソッドが呼び出されたときに main.js
モジュールから呼び出されます。
[JSImport("dom.setInnerText", "main.js")]
internal static partial void SetInnerText(string selector, string content);
次の例では、window.location.href
関数は、GetHRef
メソッドが呼び出されたときに main.js
モジュールから呼び出されます。
[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 Promise としてマーシャリングされます。 マーシャリングされた型のほとんどは、インポートされたメソッドとエクスポートされたメソッドの両方で、パラメーターおよび戻り値として、両方向で動作します。
その他の型マッピング情報と例については、「.NET WebAssembly の JavaScript `[JSImport]`/`[JSExport]` 相互運用」をご覧ください。
グローバル名前空間でアクセスできる関数をインポートするには、関数名に globalThis
プレフィックスを使い、モジュール名を指定せずに [JSImport]
属性を使います。 次の例では、console.log
の前に globalThis
を付けています。 インポートされた関数は C# Log
メソッドによって呼び出されます。C# 文字列メッセージ (message
) を受け取り、C# 文字列を console.log
の JSString
にマーシャリングします。
[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# メソッドでは、main.js
モジュールから window.location.href
関数の 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
の 2 つのプロジェクト テンプレートが含まれています。 これらのテンプレートは、現時点では試験段階にあります。これは、テンプレートの開発者ワークフローが進化していることを意味します。 ただし、テンプレートで使う .NET と JS の API は .NET 8 でサポートされており、JS から WASM 上で .NET を使用するための基盤となります。
次のコマンドを使用して、Microsoft.NET.Runtime.WebAssembly.Templates
NuGet パッケージからテンプレートをインストールすることもできます。
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
ブラウザー アプリ
コマンド ラインから wasmbrowser
テンプレートを使ってブラウザー アプリを作成できます。これにより、ブラウザーで .NET と JS を一緒に使用する例を示す Web アプリが作成されます。
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
テンプレートを使用してコンソール アプリを作成できます。その場合、 Node.js または V8 コンソール アプリとして WASM で実行されるアプリが作成されます。
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
ファイルのパスです。
その他のリソース
- .NET WebAssembly での JavaScript `[JSImport]`/`[JSExport]` 相互運用
- ASP.NET Core Blazor を使用した JavaScript JSImport/JSExport 相互運用
- API ドキュメント
dotnet/runtime
GitHub リポジトリ内:- .NET 7 で任意の JavaScript アプリから .NET を使用する
ASP.NET Core