'How to refresh web page after database update in ASP.NET CORE with Blazor

I'm making a small room booking web app and I would like to have the web page refresh on a given interval; ie a given minute or when a change has been done to the database. I found StateHasChanged(); but i don't really know how to implement it (Newbie One Kenobi here!) I tried to put it within the function for adding an appointment to the schedule:

var result = Service.CreateSchedule(nextSchedule);
    if (result)
    {
        StateHasChanged();
        NavigationManager.NavigateTo("/roomzfront/1");
    }

But I probably need something more than this, or in an other place of the code.



Solution 1:[1]

Actually, you don't need to refresh the page just to get the newer version of the database content.

Instead, all we need to do is to re-fetch the data from your database because the content showing in your browser is bound to the data you have fetched when the page first loaded.

For example, if you have a page List.razor showing your data and you can also create new data lines there. So you might have these codes:

@page "/list"
@inject DbService DbService

<h2>All Data Lines<h2>
<table>
    <thead>
        <tr>
            <th>Title</th>
            <th>Content</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var line in dataLines)
        {
            <tr>
                <td>@line.Title</td>
                <td>@line.Content</td>
            </tr>
        }
    </tbody>
</table>

<h2>Add A New Data Line</h2>
<input type="text" placeholder="Title" @bind="newLine.Title"/>
<input type="text" placeholder="Content" @bind="newLine.Content"/>
<button @onclick="AddNewLine">Add</button>

@code 
{
    List<DataLine> dataLines = new List<DataLine>();
    protected override void OnInitialized()
    {
        dataLines = DbService.GetAllData();
    }

    DataLine newLine = new DataLine();
    void AddNewLine()
    {
        DbService.CreateLine(newLine);
    }
}

To help you understand what it looks like, let's run the code in our mind; this is your browser down below:

 (<-)  (->)  (https://contoso.com/list                           )

All Data Lines

|---------------------|------------------|
|        Title        |      Content     |
|=====================|==================|
|        Data1        |      Content1    |
|---------------------|------------------|
|        Data2        |      Content2    |
|---------------------|------------------|

Add A New Data Line

[          Title          ]
[         Content         ]
(Add)

=================End of Your Browser=================

Now let's type in something in Title and Content and then click Add. Your website now starts to submit the "add" request to your Database. After that, you can't see anything changed in the table above until you refresh. That is because the data currently showing in the table is fetched when the page first loaded. When you refresh the page, it will fetch the data again. However if you don't, the data is outdated but all we need to do is to update (fetch) the data manually when your website has done some changes to the database. Which means you can simply add a line dataLines = DbService.GetAllData(); at the end of function void AddNewLine() like this:

void AddNewLine()
{
    DbService.CreateLine(newLine);
    DbService.GetAllData();
}

Now since the data has been re-fetched, your table now is displaying the newest data of your database.

I hope that helps you and if there is anything wrong, feel free to let me know!

Solution 2:[2]

you'll need to go with either SignalR or WebSockets - they're supported out of the box these days - to get your DB update notification, although frankly actually getting notifications from a database can be painful unless it's Firebase or SapphireDb.

You could go for the Notifications API but you'll need to write Javascript Interop to chat with the Service Worker and most sane people turn off notifications by default these days. Or I suppose there's Server Push protocol but that's not universally supported and again, service workers.

Regarding actual change notifications, your best bet is to fire them in the middle tier as a part of a successful Write data operation (unless Firebase or Sapphire, as above) but be aware that if data is coming from any other source than just your WebAPI layer, this won't be accurate.

TL;DR - you picked a really tough one. Sounds trivial, especially to management types, but it absolutely isn't.

Solution 3:[3]

// To refresh data on your page simply add the StateHasChanged() opcode.

private async void GetEmployeesFromCache()
    {
        var stopWatch = new Stopwatch();
        stopWatch.Start();

        CacheStatus = "Processing";
        Employees = await _employeeManager.GetEmployeesFromCache();
        CacheStatus = $"Completed in { stopWatch.ElapsedMilliseconds} ms";

        StateHasChanged();
    }

Solution 4:[4]

You can try below -

<ul>
    @foreach (var booking in Bookings)
    {
        <li>@booking.BookedRoomNumber</li>
    }
</ul>

@functions{

var timer = new Timer(new TimerCallback(_ =>
        {
            // Code to fetch the data and bind it to the page level variable
            // Ex : Bookings = GetBookingsData();

            // This line is necessary
            this.StateHasChanged();
        }), null, 1000, 1000);

}

Solution 5:[5]

Here is another approach if you don't want to go with SignalR or WebSockets.

I have a Timer that dispatches on an interval as part of my Sprite component, to give you an example of how to do it:

The Sprite has a property called Subscriber

[Parameter]
public ISpriteSubscriber { get; set; }

The host component or page is an ISpriteSubscriber interface.

namespace DataJuggler.Blazor.Components.Interfaces
{

    #region interface ISpriteSubscriber
    /// <summary>
    /// This interface is used by the AnimationManager to notifiy callers that a refresh occurred.
    /// </summary>
    public interface ISpriteSubscriber
    {

        #region Methods

            #region Refresh()
            /// <summary>
            /// This method will call StateHasChanged to refresh the UI
            /// </summary>
            void Refresh();
            #endregion

            #region Register(Sprite sprite)
            /// <summary>
            /// This method is called by the Sprite to a subscriber so it can register with the subscriber, and 
            /// receiver events after that.
            /// </summary>
            void Register(Sprite sprite);

        #endregion

        #endregion

        #region Properties

            #region ProgressBar
            /// <summary>
            /// This is used so the ProgressBar is stored and available to the Subscriber after Registering
            /// </summary>
            ProgressBar ProgressBar { get; set; }
            #endregion

        #endregion

    }
    #endregion

}

Than in your razor code to set the parent, you set Subscriber=this:

Sprite Properties

Notice the Interval=50. This sets the timer to refresh every 50 milliseconds.

In the setter for my Sprite component, the first thing I do is call Register with the parent:

[Parameter]
public ISpriteSubscriber Subscriber
{
    get { return subscriber; }
    set 
    {   
        // set the value
        subscriber = value;

        // if the value for HasSubscriber is true
        if (HasSubscriber)
        {
            // Register with the Subscriber so they can talk to each other
            Subscriber.Register(this);
        }
    }
}

This code here is on the Index page that hosts the sprite, and Registers the Sprite with the parent:

public void Register(Sprite sprite)
{
    // If the sprite object exists
    if (NullHelper.Exists(sprite))
    {
        // if this is the RedCar
        if (sprite.Name == "RedCar")
        {
            // Set the RedCar
            RedCar = sprite;
        }
        else 
        {
            // Set the WhiteCar
            WhiteCar = sprite;
        }
    }
}

Now when my start race button is clicked, I start only 1 timer, I didn't want two timers running even though I have two cars:

public void StartRace()
{
    // if both cars exist
    if (NullHelper.Exists(RedCar, WhiteCar))
    {
        // Create a new instance of a 'RandomShuffler' object.
        Shuffler = new RandomShuffler(2, 12, 100, 3);

        // Start the timer on the RedCar
        RedCar.Start();
    }
}

Here is the Start method of the Sprite:

public void Start()
{
    this.Timer = new Timer();
    this.Timer.Interval = this.interval;
    this.Timer.Elapsed += Timer_Elapsed;
    this.Timer.Start();
}

And then the Timer_Elapsed event, calls the Subscriber to refresh:

private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
    // if a subscriber exists
    if (HasSubscriber)
    {
       // Notify Subscriber
       Subscriber.Refresh();
    }
}

Now my refresh method is called every 50 milliseconds in this case, and I update my UI:

public void Refresh()
{
    // do your updates

    // Update the UI
    InvokeAsync(() =>
    {
        StateHasChanged();
    });
}

If you want to see a full working example, clone this project and look in the samples folder for the ProgressBarSample.

https://github.com/DataJuggler/DataJuggler.Blazor.Components

There is also a video here if you want to watch: https://youtu.be/frtetHgfdIo

I have used this parent / child approach for a few things and it worked so well I wrote a blog post about it: Using Interfaces To Communicate Between Blazor Components

I find this is to be a good way to talk to other components or send data.

Solution 6:[6]

I used SignalR in .net core 3.1 to achieve what you are asking. Here is the official tutorial, which I suggest you implement first. it's rather easy and gets you familiar with SignalR. Afterwards you can update any page somebody is on by putting the code in a layout, etc. The example is rather intricate for what I needed it to do.

https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-6.0&tabs=visual-studio

I just removed the section of client code in the Javascript from this example. I made there only be a method that refreshes the page. You don't need any of the html layout stuff from the example, as you aren't doing client to client.

All you have to do is grab the service after

var host = CreateHostBuilder(args).Build();

Remove the run() call,

grab the IHubContext:

var hubContext = host.Services.GetService(typeof(IHubContext< your hub class name here >))

Pass that to a private instance inside whatever class you want:

new myClassthatUpdatesPage = new myClassThatUpdatesPage((IHubContext<>)hubContext)

Add the .run() call back to the host object:

host.run()

My custom class was entirely static, so I could just call it after any point of instantiation.

to be able to send messages / info, and that class can then just use it's context

async method() in my custom class just calls:

myContext.Clients.All.SendAsync("ForceUpdate")

This method must exist in javascript side on the client.

for me, it just updated the page with:

connection.on("ForceUpdate", function()){
    window.location.reload(1);
}

And that's it. Fairly easy.

I used this method in combination with a threaded instance of a static file watcher on the client side to allow for an XML file update to automatically update my page.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 Rich Bryant
Solution 3 Muaadeeb
Solution 4 Ritesh Kulkarni
Solution 5
Solution 6 Joel Anthony Collins