Una llamada de JavaScript a .NET permite notificar a código C# un evento producido por una API o biblioteca JavaScript.
Por ejemplo, un clic sobre un marcador de Leaflet ocurre en JavaScript, pero puede necesitar actualizar el estado de un componente Blazor.
Necesitamos que JavaScript pueda llamar a métodos de C#. Blazor nos ofrece dos formas de hacerlo:
- Métodos Estáticos: Llamadas globales que no dependen de ningún componente.
- Métodos de Instancia: Llamadas dirigidas a un objeto o componente específico en memoria.
| Característica | Método Estático | Método de Instancia |
|---|---|---|
| Atributo | [JSInvokable] | [JSInvokable] |
| Acceso a Estado | No (solo lo que le pases por argumentos) | Sí (accede a variables del componente, servicios, etc.) |
| Uso en JS | DotNet.invokeMethodAsync(...) | miReferencia.invokeMethodAsync(...) |
| Complejidad | Baja | Media (requiere pasar referencia y Dispose) |
| Caso de uso | Helpers puros, Cálculos, Factorías | Eventos de UI, Callbacks de librerías, Notificaciones |
Llamar a métodos estáticos
Esta es la forma más sencilla. Se usa para funciones de utilidad que no necesitan acceder al estado de la aplicación (variables, inyección de dependencias, etc.).
Debemos marcar el método con el atributo [JSInvokable]. Además, el método debe ser static.
using Microsoft.JSInterop;
public class Utilidades
{
[JSInvokable]
public static string CalcularSuma(int a, int b)
{
return $"La suma es {a + b} (calculado en .NET)";
}
}En JS, usamos el objeto global DotNet.
El método clave es DotNet.invokeMethodAsync. Requiere:
- El nombre del ensamblado (nombre de tu proyecto .dll).
- El nombre del método.
- Los argumentos.
// Script en tu index.html o app.razor
function pedirCalculoANet() {
// 'MiProyectoBlazor' es el nombre de tu ensamblado
DotNet.invokeMethodAsync('MiProyectoBlazor', 'CalcularSuma', 10, 20)
.then(resultado => {
console.log(resultado); // "La suma es 30..."
});
}Limitación: Al ser estático, dentro de CalcularSuma no puedes acceder a this, ni a StateHasChanged, ni a los servicios inyectados en un componente. Es código aislado.
Llamar a métodos de instancia con DotNetObjectReference
En este caso, un evento gestionado por JS debe actualizar una instancia concreta de un componente.
Para ello pasamos a JavaScript una referencia administrada mediante DotNetObjectReference.
Flujo de trabajo
C#: Creamos un DotNetObjectReference apuntando a this (el componente actual).
C#: Se lo enviamos a una función JS de inicialización.
JS: Guarda esa referencia.
JS: Cuando ocurre el evento, usa la referencia para llamar al método C#.
Implementación
1. El código JavaScript (El escuchador)
// window.miHelperJS
window.miHelperJS = {
suscribirseAEvento: function (dotNetHelper) {
// Simulamos un evento que ocurre cada 2 segundos
return setInterval(() => {
// Llamamos al método "ActualizarHora" de la instancia que nos han pasado
dotNetHelper.invokeMethodAsync('ActualizarHora');
}, 2000);
},
cancelarSuscripcion: function (intervalId) {
clearInterval(intervalId);
}
};2. El Componente Blazor (El receptor)
@implements IAsyncDisposable
@inject IJSRuntime JS
<h3>Hora actual: @hora</h3>
@code {
private string hora = "Esperando...";
// Guardamos la referencia para poder limpiarla después
private DotNetObjectReference<MiComponente>? objRef;
private int? intervalId;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// 1. Creamos la referencia apuntando a 'this'
objRef = DotNetObjectReference.Create(this);
// 2. Pasamos la referencia a JS
intervalId = await JS.InvokeAsync<int>(
"miHelperJS.suscribirseAEvento", objRef);
}
}
// 3. Este es el método que JS llamará
[JSInvokable]
public Task ActualizarHora()
{
hora = DateTime.Now.ToLongTimeString();
// La llamada no es un evento Razor, así que notificamos el cambio
return InvokeAsync(StateHasChanged);
}
public async ValueTask DisposeAsync()
{
try
{
if (intervalId is not null)
{
await JS.InvokeVoidAsync(
"miHelperJS.cancelarSuscripcion", intervalId);
}
}
catch (JSDisconnectedException)
{
// El circuito ya se ha desconectado
}
finally
{
objRef?.Dispose();
}
}
}Gestión de memoria y Dispose
Este es el punto donde se cometen errores graves.
Cuando creas DotNetObjectReference.Create(this), estás diciéndole al Garbage Collector (GC) de .NET: “¡Ey! No borres este objeto, porque JavaScript tiene una referencia a él”.
Si el componente se retira y no liberas objRef, la tabla de referencias de interop puede seguir reteniéndolo y provocar una fuga de memoria. También debes cancelar temporizadores o listeners de JavaScript antes de desechar la referencia.
Si conservas un DotNetObjectReference, libera la referencia cuando termine su uso. Si antes necesitas desregistrar recursos JS, implementa IAsyncDisposable como en el ejemplo.