4

I am trying to update my Blazor page on an event generated by js.Js with interop invokes C# code on an event and i need to somehow call this.StateHasChanged in the c# callback :

JS

window.methods = {
    timeout: null,
    setTimeout: function () {
        timeout = window.setInterval(async function () {
            console.log("From js timeout occured ,sending to .net");
            await DotNet.invokeMethodAsync('[assembly name]', "TimeoutCallback", Date.now().toString());
        }, 2000);
    },

    clearTimeout: function () {
        if (this.timeout == null || this.timeout == undefined) {
            return;
        }
        console.log("Clearing timeout");
        window.clearTimeout(timeout);
    }
}

C#

@inherits StuffBase
@page "/test"

Current value: @val
<button onclick="@(async()=>await StartTimeout())">Start timeout </button>
<button onclick="@(async()=>await ClearTimeout())">Clear timeout</button>

public class StuffBase : BlazorComponent {

        protected static string val { get; set; }
        protected async Task StartTimeout() {
            await JSMethods.SetTimeout();
        }
        [JSInvokable("TimeoutCallback")]  //gets called by js on event !
        public static async Task TimeoutCallback(string data) {

            val = data; 
            Console.WriteLine("From callback ,received:" + data); //gets updated
            await Task.Delay(1000);
           //how can i call this.StateHasChanged
        }
        protected async Task ClearInterval() {
            await JSMethods.ClearInterval();
        }
    }

Interop

public class JSMethods {
        public static async Task SetTimeout() {
            await JSRuntime.Current.InvokeAsync<string>("methods.setTimeout");
        }
        public static async Task ClearTimeout() {
            await JSRuntime.Current.InvokeAsync<string>("methods.clearTimeout");
        }   
    }

As you can see first i call from c# -> setTimeout that in js attaches the timeout with its handler. What happens is that i manage to get the TimeoutCallback called from js but and while in console i get my value updated , i somehow need to notify the UI to update itself.

How can i achieve this since all my .NET methods that are called from js have (according to the documentation) to be static ?

6
  • Why do your .NET methods have to be static? The framework allows you to call instance methods as well. As per the official docs Commented Feb 18, 2019 at 12:36
  • The c# callback that js invokes must be static or else you will get an exception in the browser. Commented Feb 18, 2019 at 14:23
  • Not if you call the method on an instance as described in the docs Commented Feb 18, 2019 at 14:27
  • I used the callback in the instance of the component .I tried to define it non-static. What are you referring to ? In the documentation : learn-blazor.com/architecture/interop/documentation" you can see all [JsInvokeable] methods are static .Also here dzone.com/articles/javascript-interop-in-blazor the same thing is pointed out. Commented Feb 18, 2019 at 14:31
  • In the official documentation, which I linked to in my first comment it explains how you can pass a .net object reference down to JS and then invoke methods on that instance from JS. The only static methods are the ones invoking the JS interop calls. I think you'll find both the Learn-Blazor.com page and the dzone article are out of date. Commented Feb 18, 2019 at 15:25

5 Answers 5

11

I think it would be a nicer option to pass the C# instance down to JS and have your JS call back to that C# instance.

StuffBase.cs

    public class StuffBase : ComponentBase
    {
        protected static string val { get; set; }

        protected async Task StartTimeout()
        {
            await JSRuntime.Current.InvokeAsync<string>("methods.setTimeout", new DotNetObjectRef(this));
        }

        [JSInvokable("TimeoutCallback")]  //gets called by js on event !
        public async Task TimeoutCallback(string data)
        {
            val = data;
            Console.WriteLine("From callback ,received:" + data); //gets updated
            await Task.Delay(1000);
            StateHasChanged();
        }
        protected async Task ClearTimeout()
        {
            await JSMethods.ClearTimeout();
        }
    }

JS

window.methods = {
    timeout: null,
    setTimeout: function (stuffBaseInstance) {
        timeout = window.setInterval(async function () {
            console.log("From js timeout occured ,sending to .net");
            await stuffBaseInstance.invokeMethodAsync('TimeoutCallback', Date.now().toString());
        }, 2000);
    },

    clearTimeout: function () {
        if (this.timeout == null || this.timeout == undefined) {
            return;
        }
        console.log("Clearing timeout");
        window.clearTimeout(timeout);
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Haha , that's a whole new level of leveraging interop.Thanks !
It works ! Just a minor update: The 'DotNetObjectRef' Class has changed to 'DotNetObjectReference' in .Net Core 3.1. This means that new DotNetObjectRef(this) has to be changed to DotNetObjectReference.Create(this) in order to make the code work.
This one is soo good! You don't have to struggle with static methods!
3

Use this way:

private static App? _app;

public App()
{
    _app = this;
}

I used in App.razor , for component may be not work, by this way can cascading to app then use in all companents.
Then use like this:

[JSInvokable]
public static void TimeoutCallback(string data)
{
    ...
    _app?.StateHasChanged();
}

1 Comment

Works like a charm. Thank you very much. I used in a component like a constructor as you have here for the app and worked fine.
2

So what if they are static... Does that mean that you can't call the StateHasChanged method. Just type this at the end of the TimeoutCallback method: StateHasChanged();

Anyhow, if that is true, and you can't call the StateHasChanged method from the TimeoutCallback method, you may invoke an event handler from the TimeoutCallback method like this: TimeoutCallbackLoaded?.Invoke(data); And from there call StateHasChanged();

Edit: OK, I've noticed now that you've defined a class named JSMethods with two methods calling SetTimeout and ClearTimeout. In that case, and it is a better design is to also define the TimeoutCallback in that class. After all the are related, right ? Then define an event handler as mention above which notify a subscriber (your UI code) of the occurrence of the event, passing it the data returned from JSIterop, and in the method that handles the event in the UI code, you can call StateHasChanged();

1 Comment

Thank you very much i solved it thanks to your suggestion. May i post as an answer?
2

I have solved it thanks to user @Isaac 's suggestion to add an event, and calling StateHasChanged from its callback:

public class StuffBase : BlazorComponent {

        protected static string val { get; set; }
        protected  delegate void OnTimeout(string data);
        private static event OnTimeout OnTimeOutOccured;
        protected override void OnInit() {
            OnTimeOutOccured += x => {
                this.StateHasChanged();
            };
        }

        protected async Task StartTimeout() {
            await JSMethods.SetTimeout();
        }
        [JSInvokable("TimeoutCallback")]
        public static  async Task TimeoutCallback(string data) {

            val = data;
            Console.WriteLine("From callback ,received:" + data);
            await Task.Delay(1000);
            OnTimeOutOccured?.Invoke(data);

        }
        protected async Task ClearTimeout() {
            await JSMethods.ClearTimeout();
        }
    }

1 Comment

This is fine and all for learning purposes. But, as I've said in my answer, your JSMethods class should also contain the TimeoutCallback method. And that this class should also define an event to be invoked from the TimeoutCallback method, passing the data to subscribers of the event...
1

After much futzing with the above answers, I could not get them to work in core 3.1 and Blazor web assembly. For client side blazor, JSInvokable only works for static methods. So my solution was to have javascript in index.html click a hidden button defined in the razor page:

index.html

<script language="javascript">

        window.resetjs = function () {
            document.getElementById('jspromptbutton').click();
        }
    </script>
</head>

<body>

    <button type="button" class="btn btn-primary"
            onclick="resetjs()">
        fire prompt
    </button>

then in JSInteropRazor, add a hidden button to fire the non-static method to fire statehaschanged, or in my case refresh the data in the razor page.

JSInterop.razor

<button type="button" id="jspromptbutton" style="display:none;"  @onclick="TriggerNetInstanceMethod">
    Fire statehaschanged and refreshAPI
</button>

@code {
    public async Task TriggerNetInstanceMethod()
    {
        this.StateHasChanged();
        string url = "http://myapi.com";
        var newApiData = await Http.GetJsonAsync<Tuple<string, string>>(url);
    }
}

Kind of a kluge, but I am going with it until a better solution is found..

1 Comment

You can use events.You define an event that gets raised in the js static method.And then in your non-static methods you provide a callback for that event.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.