1

How do I initiate call to DotNet method from JS.

  • My index.razor calls JS 'Create' function and passes json filename that need to be opened.
  • index.js opens the file and parses its content. As you could see that only after the file is parsed (after satifysing (data[i].name === "something") condition), window.sendName can return value to blazor.
  • So, I would like to know how JS(index.js) could initiate call to blazor by itself, rather than initiated from navbar.razor.
  • Because when call initiated from navbar.razor as below, navbar is loaded before json is parsed, so cant return proper value.

My code:

Index.razor:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
        await JSRuntime.InvokeVoidAsync("Create", fileName);
}


JS:
function Create(fileName) {
  $.getJSON(fileName, function (data) {
     data.forEach((eachline, i) => {
     if (data[i].name === "something") {
     window.sendName= () => {
            var val = "Abc";
            return val;
     }
     }
     }
  }
}
  

NavBar.Razor:
<a href="" @onclick=getName>GetName</a>

 public string? Name { get; set; }   
 [JSInvokable]
        private async Task getName()
        {
            Name= await JSRuntime.InvokeAsync<string>("sendName");
      
        }

I tried using example in below link. But doesn't seem to work as I expect. https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-dotnet-from-javascript?view=aspnetcore-6.0#class-instance-examples

Sarahrb
  • 407
  • 11
  • 24

1 Answers1

2

First, you need to create a helper class. The class should have a JSInvokable method that raises an event when called.

SendNameHelper.cs:

public class SendNameHelper
{
    public event Action<string>? OnNameSent;

    [JSInvokable]
    public Task SendNameAsync(string name) 
    {
        OnNameSent?.Invoke(name);

        return Task.CompletedTask;
    }
}

Register this class as a service in Program.cs. Blazor components are now able to inject the service and subscribe to the event.

Program.cs:

// for blazor wasm
builder.Services.AddSingleton<SendNameHelper>();

// if blazor server add as scoped
builder.Services.AddScoped<SendNameHelper>();

Index.razor should use IJSRuntime to call the create JS function and pass to the function a reference to the SendNameHelper service.

Index.razor:

@inject IJSRuntime JS
@inject SendNameHelper SendNameHelper

...

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var sendNameHelperReference = DotNetObjectReference.Create(SendNameHelper);

            await JS.InvokeVoidAsync("create", fileName, sendNameHelperReference);
        }
    }
}

NavBar.razor should simply inject the SendNameHelper service and subscribe to the OnNameSent event.

NavBar.razor:

@inject SendNameHelper SendNameHelper
@implements IDisposable

...

@code {
    protected override void OnInitialized()
    {
        SendNameHelper.OnNameSent += HandleNameSent;
    }

    private void HandleNameSent(string name)
    {
        // do stuff with name
        Console.WriteLine(name);
    }

    public void Dispose()
    {
        SendNameHelper.OnNameSent -= HandleNameSent;
    }
}

And finally create function should use the SendNameHelper reference to invoke the SendName method:

JS:

window.create = (fileName, dotNetHelper) => {    
    $.getJSON(fileName, function (data) {
        data.forEach((eachline, i) => {
            if (data[i].name === 'something') {
                dotNetHelper.invokeMethodAsync('SendNameAsync', 'something');             
            }
        });

        dotNetHelper.dispose();
    });
};
Dimitris Maragkos
  • 8,932
  • 2
  • 8
  • 26
  • Thanks a lot for the answer. But I get error - 1000 Uncaught (in promise) Error: System.ArgumentException: There is no tracked object with id '8'. Perhaps the DotNetObjectReference instance was already disposed. (Parameter 'dotNetObjectId'). – Sarahrb Sep 30 '22 at 13:08
  • 1
    Have you added `if (firstRender)` check in `OnAfterRenderAsync`? – Dimitris Maragkos Sep 30 '22 at 13:14
  • No, I missed that. Let me try. Thank you. – Sarahrb Sep 30 '22 at 13:16
  • 1
    Also put `dotNetHelper.dispose();` outside `forEach` loop. The way I had it before the reference got disposed when the first `data[i].name === 'something'` is found but the loop continues, that's why it threw the exception. – Dimitris Maragkos Sep 30 '22 at 13:17
  • Thanks a lot again. Works perfect now. My application was slower because I had to stop checking data[i].name === 'something' once the condition was true. (using isValid == false). – Sarahrb Sep 30 '22 at 14:33
  • 1
    Might be better to use a different way to loop through data. A way that allows to exit the loop early. Check this: https://stackoverflow.com/questions/2641347/short-circuit-array-foreach-like-calling-break – Dimitris Maragkos Sep 30 '22 at 15:32
  • Thanks again for the suggestion. I was able to resolve it. – Sarahrb Oct 02 '22 at 13:47