Two way databinding to singleton service Blazor Serverside

I have been playing with Blazor on the client using Webassembly quite a bit. But I thought I would try the serverside version now and I had a simple idea I wanted to try out.

So my understading was that Blazor serverside uses SignalR to "push" out changes so that the client re-renders a part of its page.

what I wanted to try was to databind to a property on a singleton service like this:

@page "/counter"
@inject DataService dataService

<h1>Counter</h1>

<p>Current count: @currentCount ok</p>
<p> @dataService.MyProperty  </p>
<p>
    @dataService.Id
</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>


@code {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
        dataService.MyProperty += "--o--|";
    }


}

Startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddSingleton<WeatherForecastService>();
        services.AddSingleton<DataService>();
    }

Service:

namespace bl1.Services
{
public class DataService
{
    public DataService()
    {
        this.Id = System.Guid.NewGuid().ToString();
    }
    public string Id {get;set;}
    public string MyProperty { get; set; }  
}
}

So my question is this. Why, if I open up this page in two tabs, do I not immediately see the value being updated for the property MyProperty with SignalR when I am changing the value on the property in one tab in the other tab? Is there a reason that is not supposed to work or am I just simply doing it wrong?

I thought the upside of using Blazor on the serverside was that you could easily use the fact that SignalR is available and get live updates when values change on the server.

I do get the latest value from the singleton service in the other tab but only after I click the button there.


Sorry you didn't get a better answer earlier Ashkan (I just read your question now). What you were attempting is actually something Blazor does very well and you are correct it is the perfect architecture for problems like this. Using SignalR directly, like the above answer suggested, would be correct for a Blazor WASM solution, but that doesn't address your question, which was about using Server-side Blazor. I will provide the solution below.

The important point is to understand that a blazor component does not "poll" for changes in its bound properties. Instead, a component will automatically re-render itself (to an internal tree) if say a button is clicked on that component. A diff will then be performed against the previous render, and server side blazor will only send an update to the client (browser) if there is a change.

In your case, you have a component that uses an injected singleton for its model. You then open the component in two tabs and, as expected (given Blazor's architecture), only the component in the tab where you clicked the button is re-rendering. This is because nothing is instructing the other instance of the component to re-render (and Blazor is not "polling" for changes in the property value).

What you need to do is instruct all instances of the component to re-render; and you do this by calling StateHasChanged on each component.

So the solution is to wire up each component in OnInitialized() to an event you can invoke by calling Refresh() after you modify a property value; then un-wiring it in Dispose().

You need to add this to the top of your component to correctly clean up:

@implements IDisposable

Then add this code:

    static event Action OnChange;

    void Refresh() => InvokeAsync(StateHasChanged);
    override protected void OnInitialized() => OnChange += Refresh;
    void IDisposable.Dispose() => OnChange -= Refresh;

You can move my OnChange event into your singleton rather than having it as a static.

If you call "Refresh()" you will now notice all components are instantly redrawn on any open tabs. I hope that helps.


See the documentation here

A Blazor Server app is built on top of ASP.NET Core SignalR. Each client communicates to the server over one or more SignalR connections called a circuit. A circuit is Blazor's abstraction over SignalR connections that can tolerate temporary network interruptions. When a Blazor client sees that the SignalR connection is disconnected, it attempts to reconnect to the server using a new SignalR connection.

Each browser screen (browser tab or iframe) that is connected to a Blazor Server app uses a SignalR connection. This is yet another important distinction compared to typical server-rendered apps. In a server-rendered app, opening the same app in multiple browser screens typically doesn't translate into additional resource demands on the server. In a Blazor Server app, each browser screen requires a separate circuit and separate instances of component state to be managed by the server.

Blazor considers closing a browser tab or navigating to an external URL a graceful termination. In the event of a graceful termination, the circuit and associated resources are immediately released. A client may also disconnect non-gracefully, for instance due to a network interruption. Blazor Server stores disconnected circuits for a configurable interval to allow the client to reconnect. For more information, see the Reconnection to the same server section.

So in your case the client in another tab is not notified of the changes made on another circuit within another ConnectionContext.

Invoking StateHasChanged() on the client should fix the problem.

For the problem you describe you better use plain SignalR, not Blazor serverside.