Interoperabilidade [JSImport]
/[JSExport]
JavaScript
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para a versão atual, consulte a versão .NET 9 deste artigo.
Esse artigo explica como executar o .NET do JavaScript (JS) usando JS[JSImport]
/[JSExport]
interoperabilidade.
Para obter orientações adicionais, confira as diretrizes de Configuração e hospedagem de aplicativos WebAssembly do .NET no repositório GitHub do .NET Runtime (dotnet/runtime
).
Os aplicativos existentes JS podem usar o suporte do WebAssembly do lado do cliente expandido para reutilizar bibliotecas .NET de JS ou para compilar novas estruturas e aplicativos baseados em .NET.
Observação
Esse artigo se concentra na execução do .NET de aplicativos JS sem nenhuma dependência no Blazor. Para obter diretrizes sobre como usar interop [JSImport]
/[JSExport]
em aplicativos Blazor WebAssembly, consulte Interop de JSImport/JSExport do JavaScript com ASP.NET Core Blazor.
Essas abordagens são apropriadas quando você espera apenas executar no WebAssembly (WASM). As bibliotecas podem fazer uma verificação de runtime para determinar se o aplicativo está em execução no WASM chamando OperatingSystem.IsBrowser.
Pré-requisitos
SDK do .NET (versão mais recente)
Instale a carga de trabalho wasm-tools
em um shell de comando administrativo, que traz os destinos do MSBuild relacionados:
dotnet workload install wasm-tools
As ferramentas também podem ser instaladas por meio do instalador do Visual Studio na carga de trabalho de ASP.NET e desenvolvimento da Web no instalador do Visual Studio. Selecione a opção ferramentas de build do .NET WebAssembly na lista de componentes opcionais.
Opcionalmente, instale a carga de trabalho wasm-experimental
, que contém modelos de projeto experimentais para começar a usar o .NET no WebAssembly em um aplicativo de navegador (aplicativo navegador WebAssembly) ou em um aplicativo de console baseado em Node.js (aplicativo de console WebAssembly). Essa carga de trabalho não será necessária se você planeja integrar JS[JSImport]
/[JSExport]
a interoperabilidade a um aplicativo existente JS.
dotnet workload install wasm-experimental
Os modelos também podem ser instalados no pacote NuGet Microsoft.NET.Runtime.WebAssembly.Templates
com o seguinte comando:
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
Para obter mais informações, consulte a seção Modelos experimentais de carga de trabalho e projeto.
Namespace
A API de interoperabilidade JS descrita neste artigo é controlada por atributos no namespace System.Runtime.InteropServices.JavaScript.
Configuração do projeto
Para configurar um projeto (.csproj
) para habilitar a JS interoperabilidade:
Defina o moniker da estrutura de destino (espaço reservado
{TARGET FRAMEWORK}
):<TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
Há suporte para .NET 7 (
net7.0
) ou posterior.Habilite a propriedade AllowUnsafeBlocks, que permite que o gerador de código no compilador Roslyn use ponteiros para JS interoperabilidade:
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Aviso
A API de interoperabilidade JS requer a habilitação de AllowUnsafeBlocks. Tenha cuidado ao implementar seu próprio código não seguro em aplicativos .NET, o que pode introduzir riscos de segurança e estabilidade. Para obter mais informações, consulte Código não seguro, tipos de ponteiro e ponteiros de função.
A seguir há um exemplo de arquivo de projeto (.csproj
) após a configuração. O espaço reservado {TARGET FRAMEWORK}
é a estrutura de destino:
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
<PropertyGroup>
<TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
Defina o moniker da estrutura de destino:
<TargetFramework>net7.0</TargetFramework>
Há suporte para .NET 7 (
net7.0
) ou posterior.Especifique
browser-wasm
para o identificador de runtime:<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
Especifique um tipo de saída executável:
<OutputType>Exe</OutputType>
Habilite a propriedade AllowUnsafeBlocks, que permite que o gerador de código no compilador Roslyn use ponteiros para JS interoperabilidade:
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Aviso
A API de interoperabilidade JS requer a habilitação de AllowUnsafeBlocks. Tenha cuidado ao implementar seu próprio código não seguro em aplicativos .NET, o que pode introduzir riscos de segurança e estabilidade. Para obter mais informações, consulte Código não seguro, tipos de ponteiro e ponteiros de função.
Especifique
WasmMainJSPath
para apontar para um arquivo no disco. Esse arquivo é publicado com o aplicativo, mas o uso do arquivo não é necessário se você estiver integrando o .NET a um aplicativo existente JS.No exemplo a seguir, o arquivo JS no disco é
main.js
, mas qualquer nome de arquivo JS é permitido:<WasmMainJSPath>main.js</WasmMainJSPath>
Exemplo de arquivo de projeto (.csproj
) após a configuração:
<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>
Interoperabilidade do JavaScript em WASM
As APIs no exemplo a seguir são importadas de dotnet.js
. Essas APIs permitem configurar módulos nomeados que podem ser importados para o código C# e chamar em métodos expostos pelo código .NET, incluindo Program.Main
.
Importante
"Importar" e "exportar" ao longo deste artigo são definidos da perspectiva do .NET:
- Um aplicativo importa métodos JS para que eles possam ser chamados do .NET.
- O aplicativo exporta métodos .NET para que eles possam ser chamados do JS.
No exemplo a seguir:
O arquivo
dotnet.js
é usado para criar e iniciar o runtime do WebAssembly do .NET.dotnet.js
é gerado como parte da saída de build do aplicativo.Importante
Para se integrar a um aplicativo existente, copie o conteúdo da pasta de saída† de publicação para os ativos de implantação do aplicativo existente para que ele possa ser atendido com o rest do aplicativo. Para implantações de produção, publique o aplicativo com o comando
dotnet publish -c Release
em um shell de comando e implante o conteúdo da pasta de saída com o aplicativo.†A pasta de saída de publicação é o local de destino do seu perfil de publicação. O padrão para um perfil de Release no .NET 8 ou posterior é
bin/Release/{TARGET FRAMEWORK}/publish
, em que o espaço reservado{TARGET FRAMEWORK}
é a estrutura de destino (por exemplo,net8.0
).dotnet.create()
configura o runtime do WebAssembly do .NET.
setModuleImports
associa um nome a um módulo de funções JS para importação para o .NET. O módulo JS contém uma funçãodom.setInnerText
, que aceita um seletor de elemento e tempo para exibir o tempo atual do cronômetro na interface do usuário. O nome do módulo pode ser qualquer cadeia de caracteres (ele não precisa ser um nome de arquivo), mas deve corresponder ao nome usado com oJSImportAttribute
(explicado posteriormente neste artigo). A funçãodom.setInnerText
é importada para C# e chamada pelo método C#SetInnerText
. O métodoSetInnerText
é mostrado posteriormente nesta seção.exports.StopwatchSample.Reset()
chama o .NET (StopwatchSample.Reset
) de JS. O método C#Reset
reiniciará o cronômetro se ele estiver em execução ou redefini-lo se ele não estiver em execução. O métodoReset
é mostrado posteriormente nesta seção.exports.StopwatchSample.Toggle()
chama o .NET (StopwatchSample.Toggle
) de JS. O método C#Toggle
inicia ou interrompe o cronômetro, dependendo se ele estiver em execução ou não. O métodoToggle
é mostrado posteriormente nesta seção.runMain()
executaProgram.Main
.
setModuleImports
associa um nome a um módulo de funções JS para importação para o .NET. O módulo JS contém uma funçãowindow.location.href
, que retorna o endereço da página atual (URL). O nome do módulo pode ser qualquer cadeia de caracteres (ele não precisa ser um nome de arquivo), mas deve corresponder ao nome usado com oJSImportAttribute
(explicado posteriormente neste artigo). A funçãowindow.location.href
é importada para C# e chamada pelo método C#GetHRef
. O métodoGetHRef
é mostrado posteriormente nesta seção.exports.MyClass.Greeting()
chama o .NET (MyClass.Greeting
) de JS. O método C#Greeting
retorna uma cadeia de caracteres que inclui o resultado da chamada da funçãowindow.location.href
. O métodoGreeting
é mostrado posteriormente nesta seção.dotnet.run()
executaProgram.Main
.
Módulo 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();
Para importar uma função JS para que ela possa ser chamada de C#, use o novo JSImportAttribute em uma assinatura de método correspondente. O primeiro parâmetro para o JSImportAttribute é o nome da função JS a ser importada e o segundo parâmetro é o nome do módulo.
No exemplo a seguir, a função dom.setInnerText
é chamada do módulo main.js
quando o método SetInnerText
é chamado:
[JSImport("dom.setInnerText", "main.js")]
internal static partial void SetInnerText(string selector, string content);
No exemplo a seguir, a função window.location.href
é chamada do módulo main.js
quando o método GetHRef
é chamado:
[JSImport("window.location.href", "main.js")]
internal static partial string GetHRef();
Na assinatura do método importado, você pode usar tipos .NET para parâmetros e valores retornados, que sofrem realização de marshal automaticamente pelo runtime. Use JSMarshalAsAttribute<T> para controlar como os parâmetros do método importado sofrem realização de marshal. Por exemplo, você pode optar por realizar marshaling de um long
como System.Runtime.InteropServices.JavaScript.JSType.Number ou System.Runtime.InteropServices.JavaScript.JSType.BigInt. Você pode passar Action/Func<TResult> retornos de chamada como parâmetros, que sofrem realização de marshal como funções chamáveis JS. Você pode passar referências de JS e objeto gerenciado e elas sofrem realização de marshal como objetos proxy, mantendo o objeto ativo no limite até que o proxy seja coletado. Você também pode importar e exportar métodos assíncronos com um resultado Task, que sofrem realização de marshal como JS promessas. A maioria dos tipos que sofrem realização de marshal funciona em ambas as direções, como parâmetros e como valores retornados, em métodos importados e exportados.
As funções acessíveis no namespace global podem ser importadas usando o prefixo globalThis
no nome da função e o atributo [JSImport]
sem fornecer um nome de módulo. No exemplo a seguir, console.log
é prefixado com globalThis
. A função importada é chamada pelo método C# Log
, que aceita uma mensagem de cadeia de caracteres C# (message
) e realiza marshaling da cadeia de caracteres C# em um JSString
para console.log
:
[JSImport("globalThis.console.log")]
internal static partial void Log([JSMarshalAs<JSType.String>] string message);
Para exportar um método .NET para que ele possa ser chamado do JS, use o JSExportAttribute.
No exemplo a seguir, cada método é exportado para JS e pode ser chamado de funções JS:
- O método
Toggle
inicia ou interrompe o cronômetro dependendo de seu estado de execução. - O método
Reset
reiniciará o cronômetro se ele estiver em execução ou redefini-lo se ele não estiver em execução. - O método
IsRunning
indica se o cronômetro está em execução.
[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;
No exemplo a seguir, o método Greeting
retorna uma cadeia de caracteres que inclui o resultado da chamada do método GetHRef
. Conforme mostrado anteriormente, o método C# GetHref
chama JS para a função window.location.href
do módulo main.js
. window.location.href
retorna o endereço da página atual (URL):
[JSExport]
internal static string Greeting()
{
var text = $"Hello, World! Greetings from {GetHRef()}";
Console.WriteLine(text);
return text;
}
Modelos experimentais de carga de trabalho e projeto
Para demonstrar a funcionalidade de interoperabilidade JS e obter modelos JS de projeto de interoperabilidade, instale a carga de trabalho wasm-experimental
:
dotnet workload install wasm-experimental
A carga de trabalho wasm-experimental
contém dois modelos de projeto: wasmbrowser
e wasmconsole
. Esses modelos são experimentais no momento, o que significa que o fluxo de trabalho do desenvolvedor para os modelos está evoluindo. No entanto, o .NET e as APIs JS usadas nos modelos têm suporte no .NET 8 e fornecem uma base para o uso do .NET em WASM do JS.
Os modelos também podem ser instalados no pacote NuGet Microsoft.NET.Runtime.WebAssembly.Templates
com o seguinte comando:
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
Aplicativo de navegador
Você pode criar um aplicativo de navegador com o modelo wasmbrowser
na linha de comando, que cria um aplicativo Web que demonstra como usar o .NET e JS juntos em um navegador:
dotnet new wasmbrowser
Como alternativa no Visual Studio, você pode criar o aplicativo usando o modelo de projeto WebAssembly Browser App.
Compile o aplicativo do Visual Studio ou use a CLI do .NET:
dotnet build
Compile e execute o aplicativo do Visual Studio ou use a CLI do .NET:
dotnet run
Como alternativa, instale e use o dotnet serve
comando:
dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/publish
No exemplo anterior, o espaço reservado {TARGET FRAMEWORK}
é o moniker da estrutura de destino.
Aplicativo de console do Node.js
Você pode criar um aplicativo de console com o modelo wasmconsole
, que cria um aplicativo executado em WASM como um aplicativo de console Node.js ou V8:
dotnet new wasmconsole
Como alternativa no Visual Studio, você pode criar o aplicativo usando o modelo de projeto WebAssembly Console App.
Compile o aplicativo do Visual Studio ou use a CLI do .NET:
dotnet build
Compile e execute o aplicativo do Visual Studio ou use a CLI do .NET:
dotnet run
Como alternativa, inicie qualquer servidor de arquivos estático no diretório de saída de publicação que contenha o arquivo main.mjs
:
node bin/$(Configuration)/{TARGET FRAMEWORK}/{PATH}/main.mjs
No exemplo anterior, o espaço reservado {TARGET FRAMEWORK}
é o moniker da estrutura de destino, e o espaço reservado {PATH}
é o caminho para o arquivo main.mjs
.
Recursos adicionais
- Configuração e hospedagem de aplicativos WebAssembly do .NET
- Documentação da API
- Interop de JSImport/JSExport do JavaScript com ASP.NET Core Blazor
- No repositório do GitHub
dotnet/runtime
: - Use o .NET de qualquer aplicativo JavaScript no .NET 7