Sharing Data Between Clients with Blazor

Published by Mika Berglund on

Share tomatos

Today, when I was “mind-coding”, i.e. writing applications in my head (which I tend to do pretty often actually), I started building a Blazor application that shared data across multiple clients. In a Blazor Server application, all data modifications happen on the server. Data then gets sent back to the client over a SignalR connection. Normally, the data on the server changes in response to an action that the user takes. For instance clicking a button or typing in some text in a text box. But that does not have to be the case. The trigger to change the data on the server might just as well come from outside of the application.

So I had to try this out in practice as well, and not just in theory. That’s why I created this sample application on my GitHub account. this article describes the main points of that application.

The Blazor Timer Application

The sample timer application is dead-simple. The main purpose is just to show you the concept of sharing data across multiple clients. You start it on one client, and the time starts ticking on all clients currently on the page.

The concept works. I’ve deployed the application to Azure, and tried running it simultaneously on my laptop and mobile phone. The changes occur in real-time on all clients.

The magic happens in the Timer.razor page and its code-behind file Timer.razor.cs. When the start button in the UI is clicked, it calls the StartAsync() method in the code-behind class. This method starts the timer instance defined by the class. The timer is triggered every second. Each time the static counter SharedTotalSeconds is incremented. In addition, the static OnTimerChanged event is also triggered to signal all connected clients that the timer has been updated. The rest is then just normal event handling where the value displayed in the timer is calculated from the total number of seconds.

Real-World Scenario

Obviously, you need to implement this in a bit more robust fashion for production use. But the concept is still the same. You update a shared data source such as a database or table. Then you trigger an event that gets delivered to every connected client. Each client then updates their own state from the shared information

You can probably think of lots of different use cases for this, ranging from chat applications to real-time scoreboards and monitoring applications. I already have a few ideas where I’m going build on this pattern, but more about those in a future article. Have fun!


9 Comments

Jóhann · May 19, 2020 at 05:30

Any thoughts on how you would do this for an out-scalable (multiple server instance) application? I figure a master server with internal signalr connections to slave instances would be called for

    Mika Berglund · May 19, 2020 at 08:57

    I would not perhaps use SignalR for this. My solution would probably include publishing change events to Event Grid, and then have all clients subscribe to those events. If the data that is changing is large, then I would store the data somewhere, for instance in Cosmos DB, and then just publish the ID and partition key to that document with the Event Grid event.

    I was first thinking about using the Cosmos DB change feed, but now that I had a little closer look at it, it does not seem to include any triggers, so the clients would need to poll the change feed, which is perhaps not an optimal solution.

    So, I would first have a closer look at using Event Grid.

      Jóhann · May 19, 2020 at 12:06

      Very interesting! Thanks for sharing your take on this

        Mika Berglund · May 19, 2020 at 13:05

        My pleasure!

          oleg · September 22, 2020 at 18:34

          Hi Mika, do you know of a way to restrict the default blazor server app’s SignalR hub to handle communications just for the userID that launched the app instance? I have a custom login screen I’d like no info shared with other clients until some future time in the app lifecycle

          Mika Berglund · September 24, 2020 at 00:26

          That’s how it works already. Each user will have their own component instances on the server. That’s why you need some kind of mechanism to do sharing. In my sample in this article, I just used a static object, meaning that all component instances would be able to access the same static object.

          However, that’s just to prove my point. In a real-world scenario, you would need to wire up some other kind of shared resource, like Event Grid for instance, and then subscribe to the events you want in each component instance.

Raf Agus · September 20, 2020 at 22:10

Mika, thank you for taking the time to post this. Microsoft should hire you to teach classes on how to introduce topics that are new to the Dev world.

oleg · September 24, 2020 at 18:21

Mika, thanks for the info. I’d like to clarify my point. Your app uses the ‘default’ SignalR hub, which configured to push updates to any client launching your app’s URL. Thats fine, but the problem I am trying to resolve is this: when you app launches and I start the timer, I do NOT want to share any data with any other connections right away, I’d like the hub to push updates just for me. Is this possible in your app?

    Mika Berglund · September 26, 2020 at 16:31

    I haven’t change anything in the application related to the SignalR Hub it uses. In fact, in Blazor Server applications, Signal R is pretty invisible. The reason I did the prototype and wrote the article was that I wanted to investigate if it would be possible to share data between multiple users.

    You see by default, you have no mechanism in Blazor to do that sharing. All data is by default and automatically send back to the appropriate user. It’s like sessions in classic ASP and ASP.NET – Every user has their own, and that data is not visible to any other user. If you want to share that data, you must so something else. The same goes for Blazor. Every user has their own server-side “session”, which is accessible only to them.

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *