게시판
      
상위분류 : 잡필방 중위분류 : 서류가방 하위분류 : 전산과 컴퓨터
작성자 : 문시형 작성일 : 2020-09-11 조회수 : 5,462
제 목 : blazor reflash page

Using the Code

Let’s assume you have a page reporting a list of stocks, and any time one of these prices change, the HTML page needs to be refreshed.

Before SignalR, it was common to have a JavaScript code using Ajax that periodically (for example, every 5 seconds) executes a GET request to the server, in order to retrieve possible new prices and display them in the HTML page.

Today, thanks to Blazor and its embedded SignalR functionality, we can invert this trend, and give the responsibility to the server to update the HTML page only when there is some new price to display.

In the following example, Blazor will take care of updating the HTML page, while SqlTableDependency component will take care of getting notification from SQL Server database anytime the table content will be changed, due to an insertupdate or delete:

We must create a .NET CORE 3.0 Blazor web application, using the proper template from Visual Studio 2019.

Then, we install the SqlTableDependency NuGet package, that will take care of getting notifications on record table changes:

Hide   Copy Code

PM> Install-Package SqlTableDependency 

Now, for this example, let’s consider we want to monitor values of the following SQL Server table:

Hide   Copy Code

CREATE TABLE [dbo].[Stocks](
    [Code] [nvarchar](50) NULL,
    [Name] [nvarchar](50) NULL,
    [Price] [decimal](18, 0) NULL
) ON [PRIMARY]

For this reason, we define a C# model class mapping the properties we are interested to:

Hide   Copy Code

namespace BlazorApp1.Models
{
    public class Stock
    {
        public decimal Price { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
    }
}

Now we create a singleton instance wrapping SqlTableDependency and forwarding record table changes to Blazor page. We start creating its interface:

Hide   Copy Code

using BlazorApp1.Models;
using System;
using System.Collections.Generic;

namespace BlazorApp1.Service
{
    public delegate void StockChangeDelegate(object sender, StockChangeEventArgs args);

    public class StockChangeEventArgs : EventArgs
    {
        public Stock NewValue { get; }
        public Stock OldValue { get; }

        public StockChangeEventArgs(Stock newValue, Stock oldValue)
        {
            this.NewValue = newValue;
            this.OldValue = oldValue;
        }
    }

    public interface ITableChangeBroadcastService : IDisposable
    {
        event StockChangeDelegate OnStockChanged;
        IList<Stock> GetCurrentValues();
    }
}

And then its implementation:

Hide   Shrink    Copy Code

using BlazorApp1.Models;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using TableDependency.SqlClient;
using TableDependency.SqlClient.Base.EventArgs;

namespace BlazorApp1.Service
{
    public class TableChangeBroadcastService : ITableChangeBroadcastService
    {
        private const string TableName = "Stocks";
        private SqlTableDependency<Stock> _notifier;
        private IConfiguration _configuration;

        public event StockChangeDelegate OnStockChanged;

        public TableChangeBroadcastService(IConfiguration configuration)
        {
            _configuration = configuration;

            // SqlTableDependency will trigger an event 
            // for any record change on monitored table  
            _notifier = new SqlTableDependency<Stock>(
                 _configuration["ConnectionString"], 
                 TableName);
            _notifier.OnChanged += this.TableDependency_Changed;
            _notifier.Start();
        }

        // This method will notify the Blazor component about the stock price change stock
        private void TableDependency_Changed(object sender, RecordChangedEventArgs<Stock> e)
        {
            this. OnStockChanged(this, new StockChangeEventArgs(e.Entity, e.EntityOldValues));
        }

        // This method is used to populate the HTML view 
        // when it is rendered for the first time
        public IList<Stock> GetCurrentValues()
        {
            var result = new List<Stock>();

            using (var sqlConnection = new SqlConnection(_configuration["ConnectionString"]))
            {
                sqlConnection.Open();

                using (var command = sqlConnection.CreateCommand())
                {
                    command.CommandText = "SELECT * FROM " + TableName;
                    command.CommandType = CommandType.Text;

                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        if (reader.HasRows)
                        {
                            while (reader.Read())
                            {
                                result.Add(new Stock
                                {
                                    Code = reader.GetString(reader.GetOrdinal("Code")),
                                    Name = reader.GetString(reader.GetOrdinal("Name")),
                                    Price = reader.GetDecimal(reader.GetOrdinal("Price"))
                                });
                            }
                        }
                    }
                }
            }

            return result;
        }

        public void Dispose()
        {
            _notifier.Stop();
            _notifier.Dispose();
        }
    }
}

Now that we have set up the database record change notification, it is time to implement our Blazor component. As a first step, we retrieve all current stock price in OnInitialized() method and then we subscript to event notification about table record change, in order to refresh the HTML view:

Hide   Shrink    Copy Code

@page "/"
@using BlazorApp1.Models
@using BlazorApp1.Service

@inject ITableChangeBroadcastService StockService
@implements IDisposable

<h1>Stock prices</h1>

<p>Immediate client notification on record table change with Blazor</p>

<table class="table">
    <thead>
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var stock in stocks)
        {
            <tr>
                <td>@stock.Code</td>
                <td>@stock.Name</td>
                <td>@stock.Price</td>
            </tr>
        }
    </tbody>
</table>

@code {
    IList<Stock> stocks;

    protected override void OnInitialized()
    {
        // Subscription to table record change events
        this.StockService.OnStockChanged += this.StockChanged;
        this.stocks = this.StockService.GetCurrentValues();
    }

    // The event handler, will update the HTML view according to new stock value
    private async void StockChanged(object sender, StockChangeEventArgs args)
    {
        var recordToupdate = this.stocks.FirstOrDefault(x => x.Code == args.NewValue.Code);

        if (recordToupdate == null)
        {
            this.stocks.Add(args.NewValue);
        }
        else
        {
            recordToupdate.Price = args.NewValue.Price;
        }

        await InvokeAsync(() =>
        {
            base.StateHasChanged();
        });
    }

    public void Dispose()
    {
        this.StockService.OnStockChanged -= this.StockChanged;
    }
}

The table record change event handler simply checks if the stock is in the displayed list and then inserts or updates its Price value. Note here that the HTML will be refreshed automatically from Blazor. We do not need to send any notification to browsers as well as we do not need make any polling request from browsers to server, in order to update the HTML view content.

To conclude, we define the dependency resolution as singleton:

Hide   Copy Code

namespace BlazorApp1
{
    public class Startup
    {
        …
        …
        public void ConfigureServices(IServiceCollection services)
        {
            …
            services.AddSingleton<ITableChangeBroadcastService, TableChangeBroadcastService>();
            …
        }
}

And… don’t forget to set the database connection string!

Hide   Copy Code

{
    "ConnectionString": "Data Source=***; initial catalog=***; User ID=sa;Password=***"
}

Enjoy!

| | 목록으로