Partilhar via


Interoperabilidade em ASP.NET Core Blazor com JavaScript (interoperabilidadeJS)

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Um aplicativo Blazor pode invocar funções JavaScript (JS) de métodos .NET e métodos .NET de funções JS. Esses cenários são chamados de interoperabilidade JavaScript (JS interop).

Outras orientações de JS interoperabilidade são fornecidas nos seguintes artigos:

Observação

API de interoperabilidade JavaScript [JSImport]/[JSExport] está disponível para componentes do lado do cliente no ASP.NET Core em .NET 7 ou posterior.

Para obter mais informações, consulte Interoperabilidade JSImport/JSExport JavaScript com ASP.NET Core Blazor.

Compactação para componentes de servidor interativos com dados não confiáveis

Com a compactação, que é habilitada por padrão, evite criar componentes interativos seguros (autenticados/autorizados) do lado do servidor que renderizam dados de fontes não confiáveis. As fontes não confiáveis incluem parâmetros de rota, cadeias de caracteres de consulta, dados de interoperabilidade JS e qualquer outra fonte de dados que um usuário de terceiros possa controlar (bancos de dados, serviços externos). Para obter mais informações, consulte orientação do ASP.NET Core BlazorSignalR e Orientação de mitigação de ameaças para ASP.NET Core Blazor renderização interativa do lado do servidor.

Pacote de abstrações e recursos de interoperabilidade do JavaScript

O pacote @microsoft/dotnet-js-interop (npmjs.com) (Microsoft.JSInterop pacote NuGet) fornece abstrações e recursos para interoperabilidade entre o código .NET e JavaScript (JS). A fonte de referência está disponível no repositório dotnet/aspnetcore GitHub (pasta/src/JSInterop). Para obter mais informações, consulte o arquivo README.md do repositório GitHub.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa de ramificações ou tags . Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Recursos adicionais para escrever scripts de interoperabilidade JS no TypeScript:

Interação com o DOM

Apenas altere o DOM com JavaScript (JS) quando o objeto não interagir com Blazor. Blazor mantém representações do DOM e interage diretamente com objetos DOM. Se um elemento renderizado por Blazor for modificado externamente usando JS diretamente ou por meio de JS Interoperabilidade, o DOM pode não corresponder mais à representação interna do Blazor, o que pode resultar em comportamento indefinido. O comportamento indefinido pode apenas interferir com a apresentação de elementos ou suas funções, mas também pode introduzir riscos de segurança para o aplicativo ou servidor.

Esta orientação não se aplica apenas ao seu próprio código de interoperabilidade JS, mas também a quaisquer bibliotecas JS que o aplicativo usa, incluindo qualquer coisa fornecida por uma estrutura de terceiros, como Bootstrap JS e jQuery.

Em alguns exemplos de documentação, utiliza-se JS interop para alterar um elemento puramente para fins de demonstração. Nesses casos, aparece um aviso no texto.

Para obter mais informações, veja Executar funções JavaScript a partir de métodos .NET no ASP.NET Core Blazor.

Classe JavaScript com uma função de campo de tipo

Uma classe JavaScript com um campo do tipo função não é suportada pela interoperabilidade BlazorJS. Use funções Javascript em classes.

Sem suporte:GreetingHelpers.sayHello na seguinte classe, um campo do tipo função não é identificado pela interoperabilidade de Blazor com o JS e não pode ser executado a partir do código C#:

export class GreetingHelpers {
  sayHello = function() {
    ...
  }
}

suportados:GreetingHelpers.sayHello é suportada como uma função na seguinte classe:

export class GreetingHelpers {
  sayHello() {
    ...
  }
}

As funções de seta também são suportadas:

export class GreetingHelpers {
  sayHello = () => {
    ...
  }
}

Evite manipuladores de eventos embutidos

Uma função JavaScript pode ser invocada diretamente de um manipulador de eventos embutido. No exemplo a seguir, alertUser é uma função JavaScript chamada quando o botão é selecionado pelo usuário:

<button onclick="alertUser">Click Me!</button>

No entanto, o uso de manipuladores de eventos embutidos é uma má escolha de design para a chamada de funções JavaScript:

Recomendamos evitar manipuladores de eventos embutidos em favor de abordagens que atribuem manipuladores em JavaScript com addEventListener, como demonstra o exemplo a seguir:

AlertUser.razor.js:

export function alertUser() {
  alert('The button was selected!');
}

export function addHandlers() {
  const btn = document.getElementById("btn");
  btn.addEventListener("click", alertUser);
}

AlertUser.razor:

@page "/alert-user"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Alert User</h1>

<p>
    <button id="btn">Click Me!</button>
</p>

@code {
    private IJSObjectReference? module;

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./Components/Pages/AlertUser.razor.js");

            await module.InvokeVoidAsync("addHandlers");
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

No exemplo anterior, JSDisconnectedException fica preso durante a desativação do módulo, caso o circuito Blazor de SignalRseja perdido. Se o código anterior for usado numa aplicação Blazor WebAssembly, não há conexão SignalR a perder, por isso pode-se remover o bloco de try-catch e deixar a linha que descarta o módulo (await module.DisposeAsync();). Para obter mais informações, consulte ASP.NET Core Blazor interoperabilidade do JavaScript (JS interop).

Para obter mais informações, consulte os seguintes recursos:

Chamadas JavaScript assíncronas

JS As chamadas de interoperabilidade são assíncronas, independentemente de o código chamado ser assíncrono ou síncrono. As chamadas são assíncronas para garantir que os componentes sejam compatíveis entre modelos de renderização do lado do servidor e do lado do cliente. Ao adotar a renderização do lado do servidor, as chamadas de interop JS devem ser assíncronas porque são enviadas por uma conexão de rede. Para aplicações que adotam exclusivamente a renderização do lado do cliente, são suportadas chamadas de interoperabilidade síncronas JS.

Para obter mais informações, consulte os seguintes artigos:

Para obter mais informações, veja Executar funções JavaScript a partir de métodos .NET no ASP.NET Core Blazor.

Serialização de objetos

Blazor usa System.Text.Json para serialização com os seguintes requisitos e comportamentos padrão:

  • Os tipos devem ter um construtor padrão, get/set acessadores devem ser públicos e os campos nunca são serializados.
  • A serialização padrão global não é personalizável para evitar a quebra de bibliotecas de componentes existentes, impactos no desempenho e na segurança e reduções na confiabilidade.
  • A serialização de nomes de membros do .NET resulta em nomes de chave JSON minúsculos.
  • JSON é desserializado como JsonElement instâncias C#, que permitem caixa mista. A conversão interna para atribuição a propriedades de modelo C# funciona conforme o esperado, apesar de quaisquer diferenças de caso entre nomes de chave JSON e nomes de propriedade C#.
  • Tipos de estrutura complexos podem ser cortados pelo IL Trimmer na publicação e não estão presentes para JS interoperabilidade ou serialização/desserialização JSON. Recomendamos a criação de tipos personalizados para tipos que a ferramenta IL Trimmer remove.
  • Blazor sempre depende de reflexão para a serialização em JSON, incluindo quando se usa a geração de código-fonte em C# . Definir JsonSerializerIsReflectionEnabledByDefault para false no arquivo de projeto do aplicativo resulta em um erro quando a serialização é tentada.

JsonConverter API está disponível para serialização personalizada. As propriedades podem ser anotadas com um atributo [JsonConverter] para substituir a serialização padrão de um tipo de dados existente.

Para obter mais informações, consulte os seguintes recursos na documentação do .NET:

Blazor suporta interoperabilidade de matriz de bytes otimizada que evita a codificação/decodificação de matrizes de bytes em Base64. O aplicativo pode aplicar serialização personalizada e passar os bytes resultantes. Para obter mais informações, veja Executar funções JavaScript a partir de métodos .NET no ASP.NET Core Blazor.

Blazor oferece suporte a interoperabilidade JS sem empacotamento quando um grande volume de objetos .NET é rapidamente serializado ou quando grandes ou muitos objetos .NET devem ser serializados. Para obter mais informações, veja Executar funções JavaScript a partir de métodos .NET no ASP.NET Core Blazor.

Tarefas de limpeza do DOM durante o descarte de componentes

Não execute o código de interoperabilidade JS para tarefas de limpeza de DOM durante a eliminação de componentes. Em vez disso, use o padrão MutationObserver em JavaScript (JS) no cliente pelos seguintes motivos:

  • O componente pode ter sido removido do DOM no momento em que o código de limpeza é executado no Dispose{Async}.
  • Durante a renderização do lado do servidor, o renderizador de Blazor pode ter sido descartado pelo framework no momento em que o código de finalização é executado no Dispose{Async}.

O padrão MutationObserver permite executar uma função quando um elemento é removido do DOM.

No exemplo a seguir, o componente DOMCleanup:

  • Contém um <div> com um id de cleanupDiv. O elemento <div> é removido do DOM junto com o restante da marcação DOM do componente quando o componente é removido do DOM.
  • Carrega a classe DOMCleanupJS do ficheiro DOMCleanup.razor.js e chama a sua função createObserver para configurar a chamada de retorno MutationObserver. Essas tarefas são realizadas no método de ciclo de vida OnAfterRenderAsync.

DOMCleanup.razor:

@page "/dom-cleanup"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>DOM Cleanup Example</h1>

<div id="cleanupDiv"></div>

@code {
    private IJSObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./Components/Pages/DOMCleanup.razor.js");

            await module.InvokeVoidAsync("DOMCleanup.createObserver");
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

No exemplo anterior, JSDisconnectedException fica preso durante a desativação do módulo, caso o circuito Blazor de SignalRseja perdido. Se o código anterior for usado numa aplicação Blazor WebAssembly, não há conexão SignalR a perder, por isso pode-se remover o bloco de try-catch e deixar a linha que descarta o módulo (await module.DisposeAsync();). Para obter mais informações, consulte ASP.NET Core Blazor interoperabilidade do JavaScript (JS interop).

No exemplo a seguir, o retorno de chamada MutationObserver é executado sempre que ocorre uma alteração no DOM. Execute o seu código de limpeza quando a instrução if confirmar que o elemento alvo (cleanupDiv) foi removido (if (targetRemoved) { ... }). É importante desconectar e excluir o MutationObserver para evitar um vazamento de memória após a execução do código de limpeza.

DOMCleanup.razor.js colocado lado a lado com o componente precedente DOMCleanup:

export class DOMCleanup {
  static observer;

  static createObserver() {
    const target = document.querySelector('#cleanupDiv');

    this.observer = new MutationObserver(function (mutations) {
      const targetRemoved = mutations.some(function (mutation) {
        const nodes = Array.from(mutation.removedNodes);
        return nodes.indexOf(target) !== -1;
      });

      if (targetRemoved) {
        // Cleanup resources here
        // ...

        // Disconnect and delete MutationObserver
        this.observer && this.observer.disconnect();
        delete this.observer;
      }
    });

    this.observer.observe(target.parentNode, { childList: true });
  }
}

window.DOMCleanup = DOMCleanup;

As abordagens anteriores anexam o MutationObserver ao target.parentNode, que funciona até que parentNode em si seja removido do DOM. Esse é um cenário comum, por exemplo, ao navegar para uma nova página, o que faz com que todo o componente da página seja removido do DOM. Nesses casos, os componentes filhos que observam alterações na página não são limpos corretamente.

Não assuma que observar document.body, em vez de target.parentNode, é um alvo melhor. Observar document.body tem implicações de desempenho porque a lógica de retorno de chamada é executada para todas as atualizações DOM, independentemente de terem ou não algo a ver com seu elemento. Use uma das seguintes abordagens:

  • Nos casos em que você pode identificar um nó ancestral adequado para observar, use MutationObserver com ele. Idealmente, esse ancestral tem como escopo as mudanças que você deseja observar, em vez de document.body.
  • Em vez de usar MutationObserver, considere usar um elemento personalizado e disconnectedCallback. O evento é sempre acionado quando o seu elemento personalizado é desconectado, independentemente de onde esteja localizado no DOM em relação às alterações no DOM.

Chamadas de interoperabilidade JavaScript sem circuito

Esta secção aplica-se apenas a aplicações do lado do servidor.

As chamadas de interoperabilidade JavaScript (JS) não podem ser emitidas depois de o circuito de Blazor do SignalRser desconectado. Na ausência de um circuito durante a eliminação do componente ou em qualquer outro momento em que um circuito não exista, as seguintes chamadas de método falham e registam uma mensagem indicando que o circuito está desconectado como um JSDisconnectedException:

Para evitar registar JSDisconnectedException ou registar informações personalizadas, capture a exceção numa instrução try-catch.

Para o seguinte exemplo de eliminação de componentes:

  • O componente do lado do servidor implementa IAsyncDisposable.
  • module é um IJSObjectReference para um módulo JS.
  • JSDisconnectedException é capturado e não registado.
  • Opcionalmente, você pode registrar informações personalizadas na instrução catch em qualquer nível de log que preferir. O exemplo a seguir não registra informações personalizadas porque pressupõe que o desenvolvedor não se importa com quando ou onde os circuitos são desconectados durante a eliminação de componentes.
async ValueTask IAsyncDisposable.DisposeAsync()
{
    try
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Se você precisar limpar seus próprios objetos JS ou executar outro código JS no cliente depois que um circuito for perdido em um aplicativo de Blazor do lado do servidor, use o padrão MutationObserver em JS no cliente. O padrão MutationObserver permite executar uma função quando um elemento é removido do DOM.

Para obter mais informações, consulte os seguintes artigos:

  • Manipular erros em aplicações ASP.NET Core Blazor: A seção de interoperação JavaScript discute o tratamento de erros em cenários de interoperação JS.
  • Eliminação de componentes do ASP.NET Core: O artigo descreve como implementar padrões de eliminação em componentes .

Arquivos JavaScript armazenados em cache

Arquivos JavaScript (JS) e outros ativos estáticos geralmente não são armazenados em cache nos clientes durante o desenvolvimento no ambiente Development. Durante o desenvolvimento, as solicitações de ativos estáticos incluem o cabeçalho Cache-Control com um valor de no-cache ou max-age com um valor de zero (0).

Durante a produção no ambiente Production, os arquivos JS geralmente são armazenados em cache pelos clientes.

Para desativar o cache do lado do cliente nos navegadores, os desenvolvedores geralmente adotam uma das seguintes abordagens:

Para mais informações, consulte:

Limites de tamanho em chamadas de interoperabilidade JavaScript

Esta seção só se aplica a componentes interativos em aplicativos do lado do servidor. Para componentes do lado do cliente, o framework não impõe um limite ao tamanho das entradas e saídas de interoperabilidade JavaScript (JS).

Para componentes interativos em aplicativos no lado do servidor, as chamadas JS de interoperabilidade que passam dados do cliente para o servidor são limitadas em tamanho pelo tamanho máximo da mensagem de entrada SignalR permitido para os métodos do hub, controlado por HubOptions.MaximumReceiveMessageSize (padrão: 32 KB). JS para .NET SignalR mensagens maiores que MaximumReceiveMessageSize geram um erro. A estrutura não impõe um limite ao tamanho de uma mensagem SignalR do hub para um cliente. Para obter mais informações sobre o limite de tamanho, mensagens de erro e orientações sobre como lidar com limites de tamanho de mensagem, consulte ASP.NET Core BlazorSignalR guidance.

Determinar onde o aplicativo está sendo executado

Se for relevante para o aplicativo saber onde o código para as chamadas de interoperabilidade JS está a ser executado, use OperatingSystem.IsBrowser para determinar se o componente está a ser executado no contexto do navegador em WebAssembly.