Compartir vía


Interoperabilidad de JavaScript en Blazor de ASP.NET Core (interoperabilidad de JS)

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión de .NET 9 de este artículo.

Una aplicación Blazor puede invocar funciones de JavaScript (JS) desde métodos .NET y métodos .NET desde funciones de JS. Estos escenarios se denominan interoperabilidad de JavaScript (o interoperabilidad de JS).

En los siguientes artículos encontrará una guía adicional sobre la interoperabilidad de JS:

Nota:

La API de interoperabilidad de JavaScript[JSImport]/[JSExport]está disponible para los componentes del lado del cliente en ASP.NET Core en .NET 7 o posterior.

Para obtener más información, consulta Interoperabilidad JSImport/JSExport de JavaScript con ASP.NET Core Blazor.

Compresión de componentes de servidor interactivos con datos que no son de confianza

Con la compresión, que está habilitada de forma predeterminada, evite crear componentes interactivos del lado servidor (autenticados o autorizados) seguros que representen datos de orígenes que no sean de confianza. Los orígenes que no son de confianza incluyen parámetros de ruta, cadenas de consulta, datos de interoperabilidad JS y cualquier otro origen de datos que un usuario de terceros pueda controlar (bases de datos, servicios externos). Para obtener más información, consulte Guía de ASP.NETBlazorSignalR y Guía de mitigación de amenazas de representación interactiva del lado servidor para ASP.NET Core Blazor.

Paquete de funciones y abstracciones de interoperabilidad de JavaScript

El @microsoft/dotnet-js-interop paquete (npmjs.com) (Microsoft.JSInteroppaquete NuGet) proporciona abstracciones y características para la interoperabilidad entre código de .NET y JavaScript (JS). El código fuente de referencia está disponible en el dotnet/aspnetcore repositorio GitHub (/src/JSInterop carpeta). Para más información, consulte el archivo README.md del repositorio GitHub.

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Recursos adicionales para escribir scripts de interoperabilidad JS en TypeScript:

Interacción con el DOM

Solo mute el DOM con JavaScript (JS) cuando el objeto no interactúa con Blazor. Blazor mantiene representaciones de DOM e interactúa directamente con objetos DOM. Si un elemento representado por Blazor se modifica externamente mediante JS de forma directa o con la interoperabilidad de JS, DOM puede dejar de coincidir con la representación interna de Blazor, lo que puede provocar un comportamiento indefinido. Puede que el comportamiento indefinido interfiera solamente con la presentación de elementos o sus funciones, pero también puede presentar riesgos de seguridad para la aplicación o el servidor.

Esta guía no solo se aplica a su propio código de interoperabilidad de JS, sino también a todas las bibliotecas de JS que usa la aplicación, incluido todo lo proporcionado por un marco de terceros, como Bootstrap JS y jQuery.

En algunos ejemplos de la documentación, la interoperabilidad de JS se usa para mutar un elemento únicamente con fines de demostración como parte de un ejemplo. En esos casos, aparece una advertencia en el texto.

Para más información, vea Llamada a funciones de JavaScript desde métodos de .NET en Blazor de ASP.NET Core.

Clase JavaScript con un campo de función de tipo

Una clase de JavaScript con un campo de función de tipo no es compatible con interop BlazorJS. Usa funciones de Javascript en clases.

No compatible:GreetingHelpers.sayHello en la siguiente clase, como el interop de JS de Blazor no detecta un campo de función de tipo y no se puede ejecutar desde el código C#:

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

Compatible:GreetingHelpers.sayHello en la siguiente clase como una función es compatible con:

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

También es compatible con funciones de flecha:

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

Evitar los controladores de eventos en línea

Una función de JavaScript se puede invocar directamente desde un controlador de eventos insertado. En el ejemplo siguiente, alertUser es una función de JavaScript a la que se llama cuando el usuario selecciona el botón:

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

Sin embargo, el uso de controladores de eventos insertados es una opción de diseño deficiente para llamar a funciones de JavaScript:

Recomendamos evitar los controladores de eventos insertados en línea en favor de enfoques que asignen controladores en JavaScript con addEventListener, como se muestra en el siguiente ejemplo:

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 obtener más información, consulte los siguientes recursos:

Llamadas asincrónicas de JavaScript

Las llamadas de interop de JS son asincrónicas, independientemente de si el código al que se llama es sincrónico o asincrónico. Las llamadas son asincrónicas para garantizar que los componentes sean compatibles entre los modelos de representación del lado del servidor y del lado del cliente. Al adoptar el renderizado del lado del servidor, JS las llamadas interoperables deben ser asíncronas porque se envían a través de una conexión de red. Para las aplicaciones que adoptan exclusivamente la renderización del lado del cliente, se admiten las llamadas interoperativas síncronas JS.

Serialización de objetos

Blazor usa System.Text.Json para la serialización con los siguientes requisitos y comportamientos predeterminados:

  • Los tipos deben tener un constructor predeterminado, los get/set descriptores de acceso deben ser públicos y los campos nunca se serializan.
  • La serialización predeterminada global no es personalizable para evitar que se separen las bibliotecas de componentes existentes y que haya consecuencias en el rendimiento y la seguridad, además de una reducción de la fiabilidad.
  • La serialización de nombres de miembros de .NET da como resultado nombres de clave de JSON en minúsculas.
  • JSON se deserializa como instancias de C# JsonElement, lo que permite un uso mixto de mayúsculas y minúsculas. La conversión interna para la asignación de propiedades de modelos de C# funciona según lo previsto a pesar de las diferencias en el uso de mayúsculas y minúsculas en nombres de clave de JSON y nombres de propiedades de C#.
  • Los tipos de marcos complejos, como KeyValuePair, pueden recortarse mediante el optimizador de IL en la publicación y no están presentes para la interoperabilidad JS. Se recomienda crear tipos personalizados para los tipos que el recortador de IL recorta.
  • Blazor siempre se basa en la reflexión para la serialización JSON, incluso cuando se usa la generación de origen de C#. Al establecer JsonSerializerIsReflectionEnabledByDefault en false en el archivo de proyecto de la aplicación, se produce un error al intentar la serialización.

La API de JsonConverter está disponible para la serialización personalizada. Las propiedades se pueden anotar con un atributo de [JsonConverter] para invalidar la serialización predeterminada de un tipo de datos existente.

Para más información, consulte los recursos siguientes en la documentación de .NET:

Blazor admite la interoperabilidad de JS de matriz de bytes optimizada que evita la codificación o descodificación de matrices de bytes en Base64. La aplicación puede aplicar la serialización personalizada y pasar los bytes resultantes. Para más información, vea Llamada a funciones de JavaScript desde métodos de .NET en Blazor de ASP.NET Core.

Blazor admite interoperabilidad JS sin formato cuando un gran volumen de objetos .NET se serializa rápidamente o cuando se deben serializar objetos .NET grandes o muchos objetos .NET. Para más información, vea Llamada a funciones de JavaScript desde métodos de .NET en Blazor de ASP.NET Core.

Tareas de limpieza del DOM durante la eliminación de componentes

No ejecute código de interoperabilidad de JS para las tareas de limpieza de DOM durante la eliminación de componentes. En su lugar, utilice el patrón MutationObserver en JavaScript (JS) en el cliente por las siguientes razones:

  • Es posible que el componente se haya quitado del DOM en el momento en que se ejecute el código de limpieza en Dispose{Async}.
  • Durante la representación del lado del servidor, el framework puede haber eliminado la representación Blazor en el momento en que se ejecuta el código de limpieza en Dispose{Async}.

El patrón MutationObserver permite ejecutar una función cuando se quita un elemento del DOM.

En el siguiente ejemplo, el componente DOMCleanup:

  • Contiene un <div> con un id de cleanupDiv. El elemento <div> se elimina del DOM junto con el rest del marcado DOM del componente cuando este se elimina del DOM.
  • Carga la clase DOMCleanupJS desde el archivo DOMCleanup.razor.js y llama a su función createObserver para establecer la llamada de retorno MutationObserver. Estas tareas se llevan a cabo en el OnAfterRenderAsync método del 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();
        }
    }
}

En el siguiente ejemplo, la llamada MutationObserver de retorno se ejecuta cada vez que se produce un cambio en el DOM. Ejecute su código de limpieza cuando la declaraciónif confirme que el elemento de destino (cleanupDiv) ha sido eliminado (if (targetRemoved) { ... }). Es importante desconectar y borrar el MutationObserver para evitar una fuga de memoria después de que se ejecute el código de limpieza.

DOMCleanup.razor.js colocado junto al componente anterior 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;

Llamadas de interoperabilidad de JavaScript sin un circuito

Esta sección solo se aplica a las aplicaciones del lado del servidor.

Las llamadas de interoperabilidad de JavaScript (JS) no se pueden emitir después de desconectar un circuito SignalR. Sin un circuito durante la eliminación de componentes o en cualquier otro momento en que no exista un circuito, se produce un error en las siguientes llamadas de método y se registra un mensaje que indica que el circuito está desconectado como JSDisconnectedException:

Para evitar el registro de JSDisconnectedException o para registrar información personalizada, capture la excepción en una instrucción try-catch.

Para el ejemplo de eliminación de componentes siguiente:

  • El componente implementa IAsyncDisposable.
  • objInstance es una IJSObjectReference.
  • JSDisconnectedException se captura y no se registra.
  • Opcionalmente, puede registrar información personalizada en la instrucción catch en el nivel de registro que prefiera. En el ejemplo siguiente no se registra información personalizada porque se supone que al desarrollador no le importa cuándo o dónde se desconectan los circuitos durante la eliminación de componentes.
async ValueTask IAsyncDisposable.DisposeAsync()
{
    try
    {
        if (objInstance is not null)
        {
            await objInstance.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Si debe limpiar sus propios objetos JS o ejecutar código de JS adicional en el cliente después de perder un circuito, use el patrón MutationObserver en JS en el cliente. El patrón MutationObserver permite ejecutar una función cuando se quita un elemento del DOM.

Para más información, consulte los siguientes artículos.

Archivos JavaScript almacenados en caché

Los archivos de JavaScript (JS) y otros recursos estáticos no suelen almacenarse en caché en los clientes durante el desarrollo en el Developmententorno. Durante el desarrollo, las solicitudes de recursos estáticos incluyen el encabezado Cache-Control con un valor de no-cache o max-age con un valor de cero (0).

Durante la producción en el entorno Production, los clientes suelen almacenar en caché los archivos JS.

Para deshabilitar el almacenamiento en caché del lado cliente en los exploradores, los desarrolladores suelen adoptar uno de los enfoques siguientes:

  • Deshabilite el almacenamiento en caché cuando la consola de herramientas de desarrollo del explorador esté abierta. Puede encontrar instrucciones en la documentación de las herramientas de desarrollo de cada manteador del explorador:
  • Realice una actualización manual del explorador en cualquier página web de la aplicación Blazor para volver a cargar los archivos JS desde el servidor. Middleware de almacenamiento en caché HTTP de ASP.NET Core siempre respeta un encabezado Cache-Control no-cache válido enviado por un cliente.

Para más información, consulte:

Límites de tamaño en las llamadas de interoperabilidad de JavaScript

Esta sección solo se aplica a los componentes interactivos de las aplicaciones del lado del servidor. Para los componentes del lado del cliente, el framework no impone un límite al tamaño de las entradas y salidas de interoperabilidad de JavaScript (JS).

En el caso de los componentes interactivos de las aplicaciones del lado del servidor, el tamaño de las llamadas interoperativas JS que pasan datos del cliente al servidor está limitado por el tamaño máximo de los mensajes entrantes SignalR permitido para los métodos del centro, que se aplica mediante HubOptions.MaximumReceiveMessageSize (por defecto: 32 KB). Los mensajes de JS a .NET SignalR mayores que MaximumReceiveMessageSize producen un error. El marco no impone ningún límite de tamaño para un mensaje SignalR desde el concentrador a un cliente. Para obtener más información sobre el límite de tamaño, los mensajes de error y una guía para administrar los límites de tamaño de los mensajes, consulte la guía BlazorSignalR ASP.NET Core.

Determinar dónde se ejecuta la aplicación

Si es relevante que la aplicación sepa dónde se ejecuta código para las llamadas de interoperabilidad de JS, use OperatingSystem.IsBrowser para determinar si el componente se está ejecutando en el contexto del explorador en WebAssembly.