Condividi tramite


Interoperabilità JavaScript [JSImport]/[JSExport]

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Questo articolo illustra come eseguire .NET da JavaScript (JS) usando/JS[JSImport][JSExport] l'interoperabilità.

Per altre indicazioni, vedere le indicazioni sulla configurazione e l'hosting di applicazioni WebAssembly .NET nel repository GitHub .NET Runtime (dotnet/runtime).

Le app esistenti JS possono usare il supporto WebAssembly lato client espanso per riutilizzare le librerie .NET da JS o per creare un nuovo . App e framework basati su NET.

Nota

Questo articolo è incentrato sull'esecuzione di .NET da JS app senza alcuna dipendenza da Blazor. Per indicazioni sull'uso dell'interoperabilità nelle app, vedere Interoperabilità di importazione/JSesportazione JavaScript JScon ASP.NET CoreBlazor.Blazor WebAssembly [JSImport]/[JSExport]

Questi approcci sono appropriati quando si prevede di eseguire solo in WebAssembly (WASM). Le librerie possono eseguire un controllo di runtime per determinare se l'app è in esecuzione WASM chiamando OperatingSystem.IsBrowser.

Prerequisiti

.NET SDK (versione più recente)

Installare il wasm-tools carico di lavoro in una shell dei comandi amministrativa, che include le destinazioni MSBuild correlate:

dotnet workload install wasm-tools

Gli strumenti possono essere installati anche tramite il programma di installazione di Visual Studio nel carico di lavoro ASP.NET e sviluppo Web nel programma di installazione di Visual Studio. Selezionare l'opzione Strumenti di compilazione WebAssembly .NET dall'elenco dei componenti facoltativi.

Facoltativamente, installare il wasm-experimental carico di lavoro, che contiene modelli di progetto sperimentali per iniziare a usare .NET in WebAssembly in un'app browser (App WebAssembly Browser) o in un'app console basata su Node.js (app console WebAssembly). Questo carico di lavoro non è necessario se si prevede di integrare JS[JSExport][JSImport]/l'interoperabilità in un'app esistente.JS

dotnet workload install wasm-experimental

I modelli possono essere installati anche dal Microsoft.NET.Runtime.WebAssembly.Templates pacchetto NuGet con il comando seguente:

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

Per altre informazioni, vedere la sezione Modelli di progetto e carico di lavoro sperimentale.

Spazio dei nomi

L'API JS di interoperabilità descritta in questo articolo è controllata dagli attributi nello spazio dei System.Runtime.InteropServices.JavaScript nomi .

Configurazione del progetto

Per configurare un progetto (.csproj) per abilitare JS l'interoperabilità:

  • Impostare il moniker del framework di destinazione ({TARGET FRAMEWORK} segnaposto):

    <TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
    

    È supportato .NET 7 (net7.0) o versione successiva.

  • Abilitare la AllowUnsafeBlocks proprietà , che consente al generatore di codice nel compilatore Roslyn di usare i puntatori per JS l'interoperabilità:

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

    Avviso

    L'API di interoperabilità richiede l'abilitazione JS di AllowUnsafeBlocks. Prestare attenzione quando si implementa codice non sicuro nelle app .NET, che possono introdurre rischi per la sicurezza e la stabilità. Per altre informazioni, vedere Codice unsafe, tipi di puntatore e puntatori a funzione.

Di seguito è riportato un file di progetto di esempio (.csproj) dopo la configurazione. Il {TARGET FRAMEWORK} segnaposto è il framework di destinazione:

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

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

</Project>
  • Impostare il moniker del framework di destinazione:

    <TargetFramework>net7.0</TargetFramework>
    

    È supportato .NET 7 (net7.0) o versione successiva.

  • Specificare browser-wasm per l'identificatore di runtime:

    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
    
  • Specificare un tipo di output eseguibile:

    <OutputType>Exe</OutputType>
    
  • Abilitare la AllowUnsafeBlocks proprietà , che consente al generatore di codice nel compilatore Roslyn di usare i puntatori per JS l'interoperabilità:

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

    Avviso

    L'API di interoperabilità richiede l'abilitazione JS di AllowUnsafeBlocks. Prestare attenzione quando si implementa codice non sicuro nelle app .NET, che possono introdurre rischi per la sicurezza e la stabilità. Per altre informazioni, vedere Codice unsafe, tipi di puntatore e puntatori a funzione.

  • Specificare WasmMainJSPath per puntare a un file su disco. Questo file viene pubblicato con l'app, ma l'uso del file non è necessario se si sta integrando .NET in un'app esistente JS .

    Nell'esempio seguente il JS file su disco è main.js, ma qualsiasi JS nome file è consentito:

    <WasmMainJSPath>main.js</WasmMainJSPath>
    

File di progetto di esempio (.csproj) dopo la configurazione:

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

Interoperabilità JavaScript in WASM

Le API nell'esempio seguente vengono importate da dotnet.js. Queste API consentono di configurare moduli denominati che possono essere importati nel codice C# e chiamare i metodi esposti dal codice .NET, incluso Program.Main.

Importante

"Importazione" ed "esportazione" in questo articolo vengono definiti dal punto di vista di .NET:

  • Un'app importa metodi JS in modo che possano essere chiamati da .NET.
  • L'app esporta metodi .NET in modo che possano essere chiamati da JS.

Nell'esempio seguente :

  • Il dotnet.js file viene usato per creare e avviare il runtime .NET WebAssembly. dotnet.js viene generato come parte dell'output di compilazione dell'app.

    Importante

    Per eseguire l'integrazione con un'app esistente, copiare il contenuto della cartella di output di pubblicazione† negli asset di distribuzione dell'app esistente in modo che possa essere servita insieme al resto dell'app. Per le distribuzioni di produzione, pubblicare l'app con il dotnet publish -c Release comando in una shell dei comandi e distribuire il contenuto della cartella di output con l'app.

    †La cartella di output di pubblicazione è il percorso di destinazione del profilo di pubblicazione. Il valore predefinito per un Release profilo in .NET 8 o versione successiva è bin/Release/{TARGET FRAMEWORK}/publish, dove il {TARGET FRAMEWORK} segnaposto è il framework di destinazione , ad esempio net8.0.

  • dotnet.create() configura il runtime .NET WebAssembly.

  • setModuleImports associa un nome a un modulo di JS funzioni per l'importazione in .NET. Il JS modulo contiene una dom.setInnerText funzione, che accetta e il selettore di elementi e il tempo per visualizzare l'ora del controllo di arresto corrente nell'interfaccia utente. Il nome del modulo può essere qualsiasi stringa (non deve essere un nome di file), ma deve corrispondere al nome usato con ( JSImportAttribute illustrato più avanti in questo articolo). La dom.setInnerText funzione viene importata in C# e chiamata dal metodo SetInnerTextC# . Il SetInnerText metodo viene illustrato più avanti in questa sezione.

  • exports.StopwatchSample.Reset() chiama in .NET (StopwatchSample.Reset) da JS. Il Reset metodo C# riavvia il controllo di arresto se è in esecuzione o lo reimposta se non è in esecuzione. Il Reset metodo viene illustrato più avanti in questa sezione.

  • exports.StopwatchSample.Toggle() chiama in .NET (StopwatchSample.Toggle) da JS. Il Toggle metodo C# avvia o arresta il controllo di arresto a seconda che sia in esecuzione o meno. Il Toggle metodo viene illustrato più avanti in questa sezione.

  • runMain() esegue Program.Main.

  • setModuleImports associa un nome a un modulo di JS funzioni per l'importazione in .NET. Il JS modulo contiene una window.location.href funzione che restituisce l'indirizzo di pagina corrente (URL). Il nome del modulo può essere qualsiasi stringa (non deve essere un nome di file), ma deve corrispondere al nome usato con ( JSImportAttribute illustrato più avanti in questo articolo). La window.location.href funzione viene importata in C# e chiamata dal metodo GetHRefC# . Il GetHRef metodo viene illustrato più avanti in questa sezione.

  • exports.MyClass.Greeting() chiama in .NET (MyClass.Greeting) da JS. Il Greeting metodo C# restituisce una stringa che include il risultato della chiamata alla window.location.href funzione. Il Greeting metodo viene illustrato più avanti in questa sezione.

  • dotnet.run() esegue Program.Main.

JS modulo:

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

Per importare una JS funzione in modo che possa essere chiamata da C#, usare il nuovo JSImportAttribute oggetto in una firma del metodo corrispondente. Il primo parametro di JSImportAttribute è il nome della JS funzione da importare e il secondo parametro è il nome del modulo.

Nell'esempio seguente la dom.setInnerText funzione viene chiamata dal modulo quando SetInnerText viene chiamato il main.js metodo :

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

Nell'esempio seguente la window.location.href funzione viene chiamata dal modulo quando GetHRef viene chiamato il main.js metodo :

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

Nella firma del metodo importato è possibile usare i tipi .NET per i parametri e i valori restituiti, che vengono eseguiti automaticamente dal runtime. Usare JSMarshalAsAttribute<T> per controllare la modalità di marshalling dei parametri del metodo importato. Ad esempio, è possibile scegliere di effettuare il marshalling di System.Runtime.InteropServices.JavaScript.JSType.Number come long o System.Runtime.InteropServices.JavaScript.JSType.BigInt. È possibile passare Action/Func<TResult> i callback come parametri, che vengono marshallati come funzioni chiamabili JS . È possibile passare JS riferimenti a oggetti gestiti e vengono sottoposto a marshalling come oggetti proxy, mantenendo attivo l'oggetto oltre il limite fino a quando il proxy non viene sottoposto a Garbage Collection. È anche possibile importare ed esportare metodi asincroni con un Task risultato, di cui viene eseguito il marshalling come JS promesse. La maggior parte dei tipi con marshalling funziona in entrambe le direzioni, come parametri e come valori restituiti, sia nei metodi importati che in quello esportato.

Le funzioni accessibili nello spazio dei nomi globale possono essere importate usando il globalThis prefisso nel nome della funzione e usando l'attributo [JSImport] senza specificare un nome di modulo. Nell'esempio seguente, console.log è preceduto da globalThis. La funzione importata viene chiamata dal metodo C# Log , che accetta un messaggio stringa C# (message) e esegue il marshalling della stringa C# in un JSString per console.log:

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

Per esportare un metodo .NET in modo che possa essere chiamato da JS, usare .JSExportAttribute

Nell'esempio seguente ogni metodo viene esportato in JS e può essere chiamato da JS funzioni:

  • Il Toggle metodo avvia o arresta il controllo di arresto a seconda dello stato di esecuzione.
  • Il Reset metodo riavvia il controllo di arresto se è in esecuzione o lo reimposta se non è in esecuzione.
  • Il IsRunning metodo indica se il controllo di arresto è in esecuzione.
[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;

Nell'esempio seguente il Greeting metodo restituisce una stringa che include il risultato della chiamata al GetHRef metodo . Come illustrato in precedenza, il GetHref metodo C# chiama per JS la window.location.href funzione dal main.js modulo. window.location.href restituisce l'indirizzo di pagina corrente (URL):

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

Carichi di lavoro sperimentali e modelli di progetto

Per illustrare la JS funzionalità di interoperabilità e ottenere JS modelli di progetto di interoperabilità, installare il wasm-experimental carico di lavoro:

dotnet workload install wasm-experimental

Il wasm-experimental carico di lavoro contiene due modelli di progetto: wasmbrowser e wasmconsole. Questi modelli sono sperimentali in questo momento, il che significa che il flusso di lavoro per gli sviluppatori per i modelli è in continua evoluzione. Tuttavia, le API e JS .NET usate nei modelli sono supportate in .NET 8 e forniscono una base per l'uso di .NET in WASM da JS.

I modelli possono essere installati anche dal Microsoft.NET.Runtime.WebAssembly.Templates pacchetto NuGet con il comando seguente:

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

App browser

È possibile creare un'app browser con il wasmbrowser modello dalla riga di comando, che crea un'app Web che illustra l'uso di .NET e JS insieme in un browser:

dotnet new wasmbrowser

In alternativa, in Visual Studio è possibile creare l'app usando il modello di WebAssembly Browser App progetto.

Compilare l'app da Visual Studio o usando l'interfaccia della riga di comando di .NET:

dotnet build

Compilare ed eseguire l'app da Visual Studio o usando l'interfaccia della riga di comando di .NET:

dotnet run

In alternativa, installare e usare il dotnet serve comando :

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

Nell'esempio precedente il {TARGET FRAMEWORK} segnaposto è il moniker del framework di destinazione.

Node.js console app (App console Node.js)

È possibile creare un'app console con il wasmconsole modello , che crea un'app eseguita WASM in come app console Node.js o V8 :

dotnet new wasmconsole

In alternativa, in Visual Studio è possibile creare l'app usando il modello di WebAssembly Console App progetto.

Compilare l'app da Visual Studio o usando l'interfaccia della riga di comando di .NET:

dotnet build

Compilare ed eseguire l'app da Visual Studio o usando l'interfaccia della riga di comando di .NET:

dotnet run

In alternativa, avviare qualsiasi file server statico dalla directory di output di pubblicazione che contiene il main.mjs file:

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

Nell'esempio precedente il {TARGET FRAMEWORK} segnaposto è il moniker del framework di destinazione e il {PATH} segnaposto è il percorso del main.mjs file.

Risorse aggiuntive