Interoperabilidade de JavaScript e ASP.NET Core Blazor (interoperabilidade de JS)

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 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 informações sobre a versão vigente, confira a Versão do .NET 8 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 entre e JavaScript (interoperabilidade de JS).

Outras diretrizes de interoperabilidade de JS são fornecidas nos seguintes artigos:

Observação

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

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

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

Com a compressão, que está 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 interoperação com 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 ASP.NET Core BlazorSignalRDiretrizes e Orientações de mitigação de ameaças para o ASP.NET Core Blazor renderização interativa do lado do servidor.

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

O pacote @microsoft/dotnet-js-interop (npmjs.com) (pacote Microsoft.JSInterop 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 dotnet/aspnetcore repositório do GitHub (pasta /src/JSInterop). Para obter mais informações, consulte o arquivo README.md do repositório do GitHub.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Recursos adicionais para gravar scripts de interoperabilidade JS no TypeScript:

Interação com o DOM

Altere o DOM com JavaScript (JS) somente 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 via interoperabilidade com JS, o DOM poderá não corresponder mais à representação interna de Blazor, o que poderá resultar em um comportamento indefinido. O comportamento indefinido pode apenas interferir na apresentação de elementos ou de suas funções, mas também pode introduzir riscos de segurança ao aplicativo ou servidor.

Essa diretriz não se aplica apenas ao seu código de interoperabilidade de JS, mas também a todas as 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, a interoperabilidade de JS é usada para alterar um elemento puramente para fins de demonstração como parte de um exemplo. Nesses casos, um aviso aparece no texto.

Para obter mais informações, consulte Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor.

Classe JavaScript com um campo do tipo função

Uma classe JavaScript com um campo do tipo função não é compatível com BlazorJS interop. Use funções Javascript nas classes.

Sem suporte:GreetingHelpers.sayHello na classe a seguir, uma vez que um campo do tipo função não é descoberto pela interop de Blazor JS e não pode ser executado a partir do código C#:

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

Com suporte:GreetingHelpers.sayHello na seguinte classe, uma vez que uma função é suportada:

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

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

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

Evitar manipuladores de eventos em linha

Uma função JavaScript pode ser invocada diretamente de um manipulador de eventos em linha. 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 em linha é uma opção de design ruim para chamar funções JavaScript:

Recomendamos evitar manipuladores de eventos em linha 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)
        {
            await module.DisposeAsync();
        }
    }
}

Para saber mais, consulte os recursos a seguir:

Chamadas de JavaScript assíncronas

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

Serialização de objeto

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 os acessadores de 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 sobre o desempenho e a segurança e reduções na confiabilidade.
  • A serialização de nomes de membros do .NET resulta em nomes de chave JSON minúsculos.
  • O JSON é desserializado como instâncias de C# JsonElement, que permitem a combinação de maiúsculas e minúsculas. A conversão interna para atribuição a propriedades do modelo C# funciona conforme o esperado, apesar de eventuais diferenças de maiúsculas e minúsculas entre nomes de chave JSON e nomes de propriedades C#.
  • Tipos de estrutura complexos, como KeyValuePair, podem ser cortados pelo IL Trimmer no momento da publicação e não estarem presentes para a interoperabilidade com o JS. Recomendamos a criação de tipos personalizados para tipos que o IL Trimmer corta.
  • Blazor sempre depende de reflexão para serialização JSON, inclusive ao usar geração de origem C#. Definir JsonSerializerIsReflectionEnabledByDefault como false no arquivo de projeto do aplicativo resulta em um erro ao tentar a serialização.

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

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

Blazor dá suporte à interoperabilidade de JS da matriz de bytes otimizada que evita codificar/decodificar matrizes de bytes em Base64. O aplicativo pode aplicar a serialização personalizada e passar os bytes resultantes. Para obter mais informações, consulte Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor.

Blazor dá suporte à interoperabilidade de JS sem marshaling quando um grande volume de objetos .NET é serializado rapidamente ou quando objetos .NET grandes ou muitos objetos .NET devem ser serializados. Para obter mais informações, consulte Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor.

Tarefas de limpeza do DOM no descarte de componentes

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

  • O componente pode ter sido removido do DOM no momento em que o código de limpeza foi executado no Dispose{Async}.
  • Durante a renderização do lado do cliente, o renderizador Blazor pode ter sido descartado pela estrutura no momento em que o código de limpeza foi executado em 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 rest da marcação do DOM do componente quando o componente é removido do DOM.
  • Carrega a classe DOMCleanupJS do arquivo DOMCleanup.razor.js e chama sua função createObserver para configurar o retorno de chamada MutationObserver. Essas tarefas são realizadas no OnAfterRenderAsync método de ciclo de vida.

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)
        {
            await module.DisposeAsync();
        }
    }
}

No exemplo a seguir, o retorno de chamada MutationObserver é executado sempre que ocorre uma alteração do DOM. Execute o código de limpeza quando a instrução if confirmar que o elemento de destino (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 DOMCleanup anterior:

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;

Chamadas de interoperabilidade do JavaScript sem um circuito

Esta seção só se aplica a aplicativos do lado do servidor.

As chamadas de interoperabilidade do JavaScript (JS) não podem ser emitidas depois que um circuito SignalR é desconectado. Sem um circuito durante o descarte de componentes ou em qualquer outro momento em que um circuito não exista, as chamadas de método a seguir falham e registram uma mensagem, informando que o circuito está desconectado como um JSDisconnectedException:

Para evitar o log de JSDisconnectedException ou de informações personalizadas, capture a exceção em uma instrução try-catch.

No seguinte exemplo de descarte de componentes:

  • O componente implementa IAsyncDisposable.
  • objInstance é um IJSObjectReference.
  • JSDisconnectedException é capturado e não registrado.
  • 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 o descarte de componentes.
async ValueTask IAsyncDisposable.DisposeAsync()
{
    try
    {
        if (objInstance is not null)
        {
            await objInstance.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, 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:

Arquivos JavaScript armazenados em cache

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

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

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

  • Desabilitar o cache quando o console de ferramentas de desenvolvedor do navegador estiver aberto. Diretrizes podem ser encontradas na documentação de ferramentas de desenvolvedor de cada mantenedor do navegador:
  • Execute uma atualização manual do navegador de qualquer página da Web do aplicativo Blazor para recarregar arquivos JS do servidor. O Middleware de Cache HTTP do ASP.NET Core sempre honra um cabeçalho Cache-Control no-cache enviado por um cliente.

Para saber mais, veja:

Limites de tamanho em chamadas de interoperabilidade do JavaScript

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

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

Determine em que local o aplicativo está sendo executado

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