Interop JavaScript [JSImport]
/[JSExport]
com um projeto de aplicativo de navegador WebAssembly
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, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 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.
Este artigo explica como configurar um projeto de aplicativo de navegador WebAssembly para executar o .NET a partir do JavaScript (JS) usando interop JS[JSImport]
/[JSExport]
. Para obter informações adicionais e exemplos, consulte Interop de `[JSImport]`/`[JSExport]` do JavaScript no .NET WebAssembly.
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 interoperabilidade [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 que o aplicativo Blazor execute 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 adiciona os seguintes modelos de projeto experimental:
- Aplicativo de navegador WebAssembly para começar a usar o .NET no WebAssembly em um aplicativo de navegador.
- Aplicativo de Console WebAssembly para introdução em um aplicativo de console baseado em Node.js.
Depois de instalar a carga de trabalho, esses novos modelos podem ser selecionados ao criar um novo projeto. 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 restante 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.
Para obter informações e exemplos adicionais de mapeamento de tipo, consulte Interop de `[JSImport]`/`[JSExport]` do JavaScript no .NET WebAssembly.
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
- Interop '[JSImport]'/'[JSExport]' do JavaScript no .NET WebAssembly
- Interop de JSImport/JSExport do JavaScript com ASP.NET Core Blazor
- Documentação da API
- No repositório do GitHub
dotnet/runtime
: - Use o .NET de qualquer aplicativo JavaScript no .NET 7