Bagikan melalui


Jalankan .NET dari JavaScript

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Peringatan

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Artikel ini menjelaskan cara menjalankan .NET dari JavaScript (JS) menggunakan/JS[JSImport][JSExport] interop.

Untuk panduan tambahan, lihat panduan Mengonfigurasi dan menghosting aplikasi .NET WebAssembly di repositori GitHub .NET Runtime (dotnet/runtime).

Aplikasi yang JS ada dapat menggunakan dukungan WebAssembly sisi klien yang diperluas untuk menggunakan kembali pustaka .NET dari JS atau untuk membangun novel . Aplikasi dan kerangka kerja berbasis NET.

Catatan

Artikel ini berfokus pada menjalankan .NET dari JS aplikasi tanpa dependensi apa pun pada Blazor. Untuk panduan tentang menggunakan [JSImport][JSExport]/interop dalam Blazor WebAssembly aplikasi, lihat Interop Impor/JSEkspor JavaScript JSdengan ASP.NET Core Blazor.

Pendekatan ini sesuai ketika Anda hanya berharap untuk berjalan di WebAssembly (WASM). Pustaka dapat melakukan pemeriksaan runtime untuk menentukan apakah aplikasi sedang berjalan WASM dengan memanggil OperatingSystem.IsBrowser.

Prasyarat

Instal versi terbaru .NET SDK.

wasm-tools Instal beban kerja dalam shell perintah administratif, yang membawa target MSBuild terkait:

dotnet workload install wasm-tools

Alat ini juga dapat diinstal melalui alat penginstal Visual Studio di bawah ASP.NET dan beban kerja pengembangan web di alat penginstal Visual Studio. Pilih opsi alat build .NET WebAssembly dari daftar komponen opsional.

Secara opsional, instal wasm-experimental beban kerja, yang berisi templat proyek eksperimental untuk memulai dengan .NET di WebAssembly di aplikasi browser (Aplikasi Browser WebAssembly) atau di aplikasi konsol berbasis Node.js (WebAssembly Console App). Beban kerja ini tidak diperlukan jika Anda berencana untuk mengintegrasikan JS[JSExport]/[JSImport]interop ke dalam aplikasi yang adaJS.

dotnet workload install wasm-experimental

Templat juga dapat diinstal dari Microsoft.NET.Runtime.WebAssembly.Templates paket NuGet dengan perintah berikut:

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

Untuk informasi selengkapnya, lihat bagian Beban kerja eksperimental dan templat proyek.

Ruang nama

JS API interop yang dijelaskan dalam artikel ini dikontrol oleh atribut di System.Runtime.InteropServices.JavaScript namespace layanan.

Konfigurasi proyek

Untuk mengonfigurasi proyek (.csproj) untuk mengaktifkan JS interop:

  • Atur moniker kerangka kerja target ({TARGET FRAMEWORK} tempat penampung):

    <TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
    

    .NET 7 (net7.0) atau yang lebih baru didukung.

  • Aktifkan AllowUnsafeBlocks properti , yang mengizinkan generator kode di kompilator Roslyn untuk menggunakan pointer untuk JS interop:

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

    Peringatan

    JS API interop memerlukan pengaktifan AllowUnsafeBlocks. Berhati-hatilah saat menerapkan kode Anda sendiri yang tidak aman di aplikasi .NET, yang dapat menimbulkan risiko keamanan dan stabilitas. Untuk informasi selengkapnya, lihat Kode tidak aman, jenis penunjuk, dan penunjuk fungsi.

Berikut ini adalah contoh file proyek (.csproj) setelah konfigurasi. Tempat {TARGET FRAMEWORK} penampung adalah kerangka kerja target:

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

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

</Project>
  • Atur moniker kerangka kerja target:

    <TargetFramework>net7.0</TargetFramework>
    

    .NET 7 (net7.0) atau yang lebih baru didukung.

  • Tentukan browser-wasm untuk pengidentifikasi runtime:

    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
    
  • Tentukan jenis output yang dapat dieksekusi:

    <OutputType>Exe</OutputType>
    
  • Aktifkan AllowUnsafeBlocks properti , yang mengizinkan generator kode di kompilator Roslyn untuk menggunakan pointer untuk JS interop:

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

    Peringatan

    JS API interop memerlukan pengaktifan AllowUnsafeBlocks. Berhati-hatilah saat menerapkan kode Anda sendiri yang tidak aman di aplikasi .NET, yang dapat menimbulkan risiko keamanan dan stabilitas. Untuk informasi selengkapnya, lihat Kode tidak aman, jenis penunjuk, dan penunjuk fungsi.

  • Tentukan WasmMainJSPath untuk menunjuk ke file pada disk. File ini diterbitkan dengan aplikasi, tetapi penggunaan file tidak diperlukan jika Anda mengintegrasikan .NET ke dalam aplikasi yang ada JS .

    Dalam contoh berikut, JS file pada disk adalah main.js, tetapi nama file apa pun JS diizinkan:

    <WasmMainJSPath>main.js</WasmMainJSPath>
    

Contoh file proyek (.csproj) setelah konfigurasi:

<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>

Interop JavaScript aktif WASM

API dalam contoh berikut diimpor dari dotnet.js. API ini memungkinkan Anda menyiapkan modul bernama yang dapat diimpor ke dalam kode C# Anda dan memanggil metode yang diekspos oleh kode .NET Anda, termasuk Program.Main.

Penting

"Impor" dan "ekspor" di seluruh artikel ini ditentukan dari perspektif .NET:

  • Aplikasi mengimpor JS metode sehingga dapat dipanggil dari .NET.
  • Aplikasi ini mengekspor metode .NET sehingga dapat dipanggil dari JS.

Dalam contoh berikut:

  • File dotnet.js digunakan untuk membuat dan memulai runtime .NET WebAssembly. dotnet.js dihasilkan sebagai bagian dari output build aplikasi.

    Penting

    Untuk berintegrasi dengan aplikasi yang ada, salin konten folder output penerbitan† ke aset penyebaran aplikasi yang ada sehingga dapat dilayani bersama dengan aplikasi lainnya. Untuk penyebaran produksi, terbitkan aplikasi dengan dotnet publish -c Release perintah di shell perintah dan sebarkan konten folder output dengan aplikasi.

    † Folder output penerbitan adalah lokasi target profil publikasi Anda. Default untuk Release profil di .NET 8 atau yang lebih baru adalah bin/Release/{TARGET FRAMEWORK}/publish, di mana {TARGET FRAMEWORK} tempat penampung adalah kerangka kerja target (misalnya, net8.0).

  • dotnet.create() menyiapkan runtime .NET WebAssembly.

  • setModuleImports mengaitkan nama dengan modul JS fungsi untuk diimpor ke .NET. Modul JS berisi dom.setInnerText fungsi, yang menerima dan memilih elemen dan waktu untuk menampilkan waktu stopwatch saat ini di UI. Nama modul dapat berupa string apa pun (tidak perlu menjadi nama file), tetapi harus cocok dengan nama yang JSImportAttribute digunakan dengan (dijelaskan nanti dalam artikel ini). Fungsi ini dom.setInnerText diimpor ke C# dan dipanggil dengan metode SetInnerTextC# . Metode SetInnerText ini ditampilkan nanti di bagian ini.

  • exports.StopwatchSample.Reset() panggilan ke .NET (StopwatchSample.Reset) dari JS. Metode Reset C# memulai ulang stopwatch jika sedang berjalan atau mengatur ulang jika tidak berjalan. Metode Reset ini ditampilkan nanti di bagian ini.

  • exports.StopwatchSample.Toggle() panggilan ke .NET (StopwatchSample.Toggle) dari JS. Metode Toggle C# memulai atau menghentikan stopwatch tergantung pada apakah saat ini berjalan atau tidak. Metode Toggle ini ditampilkan nanti di bagian ini.

  • runMain()Program.Mainmenjalankan .

  • setModuleImports mengaitkan nama dengan modul JS fungsi untuk diimpor ke .NET. Modul JS berisi window.location.href fungsi, yang mengembalikan alamat halaman (URL) saat ini. Nama modul dapat berupa string apa pun (tidak perlu menjadi nama file), tetapi harus cocok dengan nama yang JSImportAttribute digunakan dengan (dijelaskan nanti dalam artikel ini). Fungsi ini window.location.href diimpor ke C# dan dipanggil dengan metode GetHRefC# . Metode GetHRef ini ditampilkan nanti di bagian ini.

  • exports.MyClass.Greeting() panggilan ke .NET (MyClass.Greeting) dari JS. Metode Greeting C# mengembalikan string yang menyertakan hasil pemanggilan window.location.href fungsi. Metode Greeting ini ditampilkan nanti di bagian ini.

  • dotnet.run()Program.Mainmenjalankan .

JS modul:

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();

Untuk mengimpor JS fungsi sehingga dapat dipanggil dari C#, gunakan yang baru JSImportAttribute pada tanda tangan metode yang cocok. Parameter pertama untuk JSImportAttribute adalah nama fungsi yang akan diimpor JS dan parameter kedua adalah nama modul.

Dalam contoh berikut, fungsi dipanggil dom.setInnerText dari main.js modul ketika SetInnerText metode dipanggil:

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

Dalam contoh berikut, fungsi dipanggil window.location.href dari main.js modul ketika GetHRef metode dipanggil:

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

Dalam tanda tangan metode yang diimpor, Anda dapat menggunakan jenis .NET untuk parameter dan mengembalikan nilai, yang dinamai secara otomatis oleh runtime. Gunakan JSMarshalAsAttribute<T> untuk mengontrol bagaimana parameter metode yang diimpor di-marshalled. Misalnya, Anda dapat memilih untuk marshal a long sebagai System.Runtime.InteropServices.JavaScript.JSType.Number atau System.Runtime.InteropServices.JavaScript.JSType.BigInt. Anda dapat meneruskan Action/Func<TResult> panggilan balik sebagai parameter, yang dinamai sebagai fungsi yang dapat JS dipanggil. Anda dapat meneruskan JS referensi objek dan terkelola, dan dinamai sebagai objek proksi, menjaga objek tetap hidup di seluruh batas sampai proksi dikumpulkan sampah. Anda juga dapat mengimpor dan mengekspor metode asinkron dengan Task hasil, yang dinamai sebagai JS janji. Sebagian besar jenis marshalled bekerja di kedua arah, sebagai parameter dan sebagai nilai pengembalian, pada metode yang diimpor dan diekspor.

Tabel berikut menunjukkan pemetaan jenis yang didukung.

.NET JavaScript Nullable Task➔ke Promise JSMarshalAs fakultatif Array of
Boolean Boolean Didukung Didukung Didukung Tidak didukung
Byte Number Didukung Didukung Didukung Didukung
Char String Didukung Didukung Didukung Tidak didukung
Int16 Number Didukung Didukung Didukung Tidak didukung
Int32 Number Didukung Didukung Didukung Didukung
Int64 Number Didukung Didukung Tidak didukung Tidak didukung
Int64 BigInt Didukung Didukung Tidak didukung Tidak didukung
Single Number Didukung Didukung Didukung Tidak didukung
Double Number Didukung Didukung Didukung Didukung
IntPtr Number Didukung Didukung Didukung Tidak didukung
DateTime Date Didukung Didukung Tidak didukung Tidak didukung
DateTimeOffset Date Didukung Didukung Tidak didukung Tidak didukung
Exception Error Tidak didukung Didukung Didukung Tidak didukung
JSObject Object Tidak didukung Didukung Didukung Didukung
String String Tidak didukung Didukung Didukung Didukung
Object Any Tidak didukung Didukung Tidak didukung Didukung
Span<Byte> MemoryView Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Span<Int32> MemoryView Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Span<Double> MemoryView Tidak didukung Tidak didukung Tidak didukung Tidak didukung
ArraySegment<Byte> MemoryView Tidak didukung Tidak didukung Tidak didukung Tidak didukung
ArraySegment<Int32> MemoryView Tidak didukung Tidak didukung Tidak didukung Tidak didukung
ArraySegment<Double> MemoryView Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Task Promise Tidak didukung Tidak didukung Didukung Tidak didukung
Action Function Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Action<T1> Function Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Action<T1, T2> Function Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Action<T1, T2, T3> Function Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Func<TResult> Function Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Func<T1, TResult> Function Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Func<T1, T2, TResult> Function Tidak didukung Tidak didukung Tidak didukung Tidak didukung
Func<T1, T2, T3, TResult> Function Tidak didukung Tidak didukung Tidak didukung Tidak didukung

Kondisi berikut berlaku untuk mengetik nilai pemetaan dan marshalled:

  • Kolom Array of menunjukkan apakah jenis .NET dapat dinamai sebagai JSArray. Contoh: C# int[] (Int32) dipetakan ke JSArray s Number.
  • Saat meneruskan JS nilai ke C# dengan nilai jenis yang salah, kerangka kerja melemparkan pengecualian dalam banyak kasus. Kerangka kerja tidak melakukan pemeriksaan jenis waktu kompilasi di JS.
  • JSObject, Exception, Task dan ArraySegment buat GCHandle dan proksi. Anda dapat memicu pembuangan dalam kode pengembang atau mengizinkan pengumpulan sampah .NET (GC) untuk membuang objek nanti. Jenis-jenis ini membawa overhead performa yang signifikan.
  • Array: Marshaling array membuat salinan array di JS atau .NET.
  • MemoryView
    • MemoryView adalah JS kelas untuk runtime .NET WebAssembly ke marshal Span dan ArraySegment.
    • Tidak seperti marshaling array, marshaling Span atau ArraySegment tidak membuat salinan memori yang mendasar.
    • MemoryView hanya dapat dibuat dengan benar oleh runtime .NET WebAssembly. Oleh karena itu, tidak dimungkinkan untuk mengimpor JS fungsi sebagai metode .NET yang memiliki parameter Span atau ArraySegment.
    • MemoryView dibuat untuk Span hanya valid selama durasi panggilan interop. Seperti Span yang dialokasikan pada tumpukan panggilan, yang tidak bertahan setelah panggilan interop, tidak dimungkinkan untuk mengekspor metode .NET yang mengembalikan Span.
    • MemoryView dibuat untuk ArraySegment bertahan hidup setelah panggilan interop dan berguna untuk berbagi buffer. Memanggil dispose() pada yang MemoryView ArraySegment dibuat untuk membuang proksi dan melepaskan array .NET yang mendasar. Sebaiknya panggil dispose() di try-finally blok untuk MemoryView.

Fungsi yang dapat diakses pada namespace global dapat diimpor dengan menggunakan globalThis awalan dalam nama fungsi dan dengan menggunakan [JSImport] atribut tanpa memberikan nama modul. Dalam contoh berikut, console.log diawali dengan globalThis. Fungsi yang diimpor dipanggil oleh metode C# Log , yang menerima pesan string C# (message) dan marshalls string C# ke JSString untuk console.log:

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

Untuk mengekspor metode .NET sehingga dapat dipanggil dari JS, gunakan JSExportAttribute.

Dalam contoh berikut, setiap metode diekspor ke JS dan dapat dipanggil dari JS fungsi:

  • Metode Toggle ini memulai atau menghentikan stopwatch tergantung pada status berjalannya.
  • Metode Reset memulai ulang stopwatch jika sedang berjalan atau mengatur ulang jika tidak berjalan.
  • Metode ini IsRunning menunjukkan apakah stopwatch berjalan.
[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;

Dalam contoh berikut, Greeting metode mengembalikan string yang menyertakan hasil pemanggilan GetHRef metode. Seperti yang ditunjukkan GetHref sebelumnya, metode C# memanggil fungsi JS window.location.href dari main.js modul. window.location.href mengembalikan alamat halaman (URL) saat ini:

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

Beban kerja eksperimental dan templat proyek

Untuk menunjukkan JS fungsionalitas interop dan mendapatkan JS templat proyek interop, instal wasm-experimental beban kerja:

dotnet workload install wasm-experimental

Beban wasm-experimental kerja berisi dua templat proyek: wasmbrowser dan wasmconsole. Templat ini bersifat eksperimental saat ini, yang berarti alur kerja pengembang untuk templat berkembang. Namun, .NET dan JS API yang digunakan dalam templat didukung di .NET 8 dan menyediakan fondasi untuk menggunakan .NET aktif WASM dari JS.

Templat juga dapat diinstal dari Microsoft.NET.Runtime.WebAssembly.Templates paket NuGet dengan perintah berikut:

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

Aplikasi browser

Anda dapat membuat aplikasi browser dengan wasmbrowser templat dari baris perintah, yang membuat aplikasi web yang menunjukkan menggunakan .NET dan JS bersama-sama di browser:

dotnet new wasmbrowser

Atau di Visual Studio, Anda dapat membuat aplikasi menggunakan WebAssembly Browser App templat proyek.

Buat aplikasi dari Visual Studio atau dengan menggunakan .NET CLI:

dotnet build

Buat dan jalankan aplikasi dari Visual Studio atau dengan menggunakan .NET CLI:

dotnet run

Atau, instal dan gunakan dotnet serve perintah :

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

Dalam contoh sebelumnya, {TARGET FRAMEWORK} tempat penampung adalah moniker kerangka kerja target.

Aplikasi konsol Node.js

Anda dapat membuat aplikasi konsol dengan wasmconsole templat, yang membuat aplikasi yang berjalan di bawah WASM sebagai aplikasi konsol Node.js atau V8 :

dotnet new wasmconsole

Atau di Visual Studio, Anda dapat membuat aplikasi menggunakan WebAssembly Console App templat proyek.

Buat aplikasi dari Visual Studio atau dengan menggunakan .NET CLI:

dotnet build

Buat dan jalankan aplikasi dari Visual Studio atau dengan menggunakan .NET CLI:

dotnet run

Atau, mulai server file statis apa pun dari direktori output penerbitan yang berisi main.mjs file:

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

Dalam contoh sebelumnya, {TARGET FRAMEWORK} tempat penampung adalah moniker kerangka kerja target, dan {PATH} tempat penampung adalah jalur ke main.mjs file.

Sumber Daya Tambahan: