Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 7 de julio de 2020
Blazor En esta serie sobre interoperación Javascript-Blazor hemos ido viendo cómo desde Blazor Server o WebAssembly podíamos llamar a funciones Javascript disponibles en el browser, y también cómo conseguir invocar métodos estáticos .NET desde Javascript.

Como recordaréis del post anterior, la invocación de métodos estáticos era bastante sencilla, porque básicamente desde Javascript sólo teníamos que conocer el nombre del ensamblado donde se encontraba el código y el nombre del método a ejecutar. El hecho de que el método fuera estático es una ventaja, pues no hay "piezas móviles" en el puzzle.

Sin embargo, si desde Javascript queremos invocar un método de instancia, la cosa se complica un poco porque tendremos que ayudar a Blazor a determinar de qué instancia se trata, y esto a priori no suena sencillo porque Javascript y Blazor viven en dos mundos diferentes (de hecho, en el caso de Blazor Server incluso están físicamente separados). Sin embargo, veremos que las herramientas que ofrece el framework son suficientes para llevarlo a cabo sin liarnos demasiado.

Esta última entrega la dedicaremos precisamente a esto: aprender cómo podemos llamar desde Javascript a métodos de instancia escritos en C#.

¿Cómo invocar métodos de instancia C# desde Javascript?

Como hemos dejado entrever, si queremos llamar desde Javascript a un método de instancia escrito en C#, tanto en Blazor Server como en WebAssembly, lo que debemos hacer es:
  1. Desde C#, obtener una referencia hacia la instancia sobre la que se ejecutará el método que queremos invocar desde Javascript.
     
  2. Hacer llegar a Javascript la referencia obtenida anteriormente. Lo habitual será enviársela mediante una llamada (interop) desde C# a Javascript.
     
  3. Ya desde el lado Javascript, utilizar la referencia para invocar al método deseado, que debe ser público y estar decorado con el atributo [JSInvokable] que ya conocemos.
     
  4. Cuando ya no sea necesaria, liberar la referencia utilizada anteriormente llamando a su método Dispose() para evitar fugas de memoria.
El proceso es exactamente igual para Blazor Server que para Blazor WebAssembly, por lo que no distinguiremos entre ambos.

Veamos cada uno de estos puntos.

1. Obtener la referencia hacia la instancia

La referencia a la instancia debemos crearla desde C# utilizando el método DotNetObjectReference.Create(), suministrándole la instancia que queremos referenciar. Por ejemplo, el siguiente código en el interior de un componente crearía una referencia hacia el mismo:
var reference = DotNetObjectReference.Create(this);
Pero en realidad, la referencia podríamos crearla de cualquier objeto que esté en memoria en ese momento. Puede ser el propio componente en el que estamos, o bien un helper (de hecho, en la documentación oficial de Microsoft es el ejemplo que aparece), o cualquier otra cosa. La cuestión es que esa variable reference contendrá una instancia de DotNetObjectReference<T>, siendo T el tipo de objeto referenciado.

Como más adelante tendremos que liberar la referencia, el ideal es dejarla almacenada en un campo para que luego podamos liberarla llamando a su método Dispose().
@code {
    DotNetObjectReference<MyClass> _reference;
    ...
    // En el interior de algún método asignamos el campo:
    _reference = DotNetObjectReference.Create(myObject);
    ...
}

2. Hacer llegar a Javascript la referencia

Para que el lado Javascript pueda invocar nuestro método, debemos hacerle llegar la referencia obtenida anteriormente, es decir, el objeto DotNetObjectReference<T>. Como hemos comentado, lo habitual será enviarla mediante interop desde C# a Javascript, algo que ya sabemos hacer :)

Por ejemplo, en el siguiente código invocamos una función personalizada interopDemo.initialize() de Javascript suministrándole la referencia al componente actual. Ya en el lado cliente, esa función podría emplear la referencia para invocar al método C# directamente o bien dejarla almacenada para usos posteriores:
@* File: Test.razor *@

@page "/test"
@inject IJSRuntime JsRuntime
...
@code {
    DotNetObjectReference<Test> _reference;

    async Task CreateAndSendReferenceAsync()
    {
        // Creamos la referencia al componente actual...
        _reference = DotNetObjectReference.Create(this);

        // ... y se la pasamos a una función Javascript
        await JsRuntime.InvokeVoidAsync("interopDemo.initialize", _reference);
    }
}
El envío de la referencia podemos hacerlo en el momento que más nos interese, que dependerá del escenario o funcionalidad que vayamos a implementar. Por ejemplo, podría realizarse justo al cargar la página para que la referencia se encuentre disponible en el browser en todo momento, o bien hacerlo cuando ocurra alguna condición determinada. A nuestro antojo.

Por ejemplo, si quisiéramos hacerlo cuando la página haya cargado totalmente, podríamos sobrescribir OnAfterRenderAsync() como se muestra a continuación:
@code {
    DotNetObjectReference<Test> _reference;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await CreateAndSendReferenceAsync();
        }
    }

    async Task CreateAndSendReferenceAsync() { ... } // Omitido por brevedad
}

3. Utilizar la referencia para invocar al método

Desde el código Javascript podríamos invocar muy fácilmente al método de la instancia referenciada ejecutando sobre ella invokeMethodAsync(), pasándole como parámetro el nombre del método de instancia a ejecutar.

El siguiente código script, que podría encontrarse dentro de un archivo .js incluido en la página, define un objeto llamado interopDemo en cuyo interior encontramos dos funciones. La primera de ella, initialize() es la que anteriormente hemos utilizado para enviar la referencia, que dejamos guardada en una variable local; la segunda, increment() es donde utilizamos dicha referencia para invocar al código C# suministrándole un parámetro:
window.interopDemo = (function () {
    var reference = null;
    return {
        initialize: function (ref) { reference = ref; },
        increment: function() {
            reference.invokeMethodAsync("Increment", 1);
        }
    };
})();
El método de instancia Increment() debe encontrarse en el objeto apuntado por la referencia, ser público y estar decorado con [JSInvokable], como el que sigue a continuación:
@code {
    int counter = 0;
    ... // Otros miembros omitidos

    [JSInvokable]
    public async Task Increment(int num)
    {
        counter += num;
        StateHaschanges(); // Fuerza el renderizado si hay cambios
    }
}
Un ejemplo de uso de la función Javascript increment() podría ser el siguiente HTML, donde vemos que introducimos la llamada en el evento onclick de un botón:
<h2>Interop demo</h2>
<button onclick="interopDemo.increment()">Increment</button>
<p>Counter: @counter</p>
Fijaos que, a diferencia de la página de ejemplo Counter.razor que encontramos en la plantilla por defecto, en este caso no estamos utilizando el sistema de bindings de Blazor para incrementar el contador: estamos usando Javascript puro en la página, e invocando a un método C# para realizar la operación y refrescar la interfaz.

4. Liberar la instancia

Es importante tener en cuenta que, para evitar fugas de memoria, esta referencia debe ser liberada llamando a su método Dispose() cuando deje de tener sentido.

Por ejemplo, si estamos en el contexto de un componente Blazor, un buen momento para hacerlo podría ser el propio Dispose() del componente. Para ello debemos hacerlo IDisposable usando la directiva @implements como en el siguiente ejemplo:
@page "/test"
@inject IJSRuntime JsRuntime
@implements IDisposable

...
@code {
    DotNetObjectReference<MyClass> _reference;
    int counter = 0;

    // Omitidos por brevedad
    protected override async Task OnAfterRenderAsync(bool firstRender) { ... }
    async Task CreateAndSendReferenceAsync() { ... }
    [JSInvokable] async Task Increment(int num) { ... }

    public void Dispose()
    {
        _reference?.Dispose();
    }
}
Como alternativa, se puede también liberar la referencia desde el lado Javascript, llamando al método dispose() de la misma.

El ejemplo completo

El siguiente bloque de código es el componente "Test.razor" que contiene la interfaz de usuario y el método Increment() que será consumido desde el lado cliente:
@page "/test"
@inject IJSRuntime JsRuntime
@implements IDisposable

<h1>Interop demo</h1>

<button onclick="interopDemo.increment()">Increment</button>
<p>Counter: @counter</p>

@code {
    int counter = 0;
    DotNetObjectReference<Test> _reference;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await CreateAndSendReferenceAsync();
        }
    }

    async Task CreateAndSendReferenceAsync()
    {
        _reference = DotNetObjectReference.Create(this);
        await JsRuntime.InvokeVoidAsync("interopDemo.initialize", _reference);
    }

    [JSInvokable]
    public async Task Increment(int num)
    {
        counter += num;
        StateHasChanged();
    }

    public void Dispose()
    {
        _reference?.Dispose();
    }
}
La parte Javascript que realiza el registo de la referencia y la invocación al método ya la hemos visto anteriormente. Obviamente, este script debe encontrarse en la página o en algún archivo .js referenciado por ella:
window.interopDemo = (function () {
    var reference = null;
    return {
        initialize: function (ref) { reference = ref; },
        increment: function() {
            reference.invokeMethodAsync("Increment", 1);
        }
    };
})();
Y con esto, creo que hemos terminado con la Blazor interop y, por tanto, esta mini-serie de tres capítulos en la que llevamos algunas semanas. Por supuesto, si véis que falta algo no dudéis en comentarlo :)

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!