在这个简单的示例中,我们将看到发生在SQL Server数据库表更改时如何更新HTML页面,而无需重新加载页面或从客户端到服务器进行异步调用,而是从客户端获取此HTML刷新内容。服务器使用Blazor服务器端(.NET CORE 3.0)。
背景之前,我发表了一篇有关“使用SignalR和SQLTableDependency进行记录更改的SQL Server通知”的文章。
上一篇文章使用了SignalR,以获取实时更改页面内容的通知。尽管功能正常,在我看来,SignalR不是那么直接和容易使用。
在Blazor的帮助下,从服务器到HTML页面的通知得到了极大的简化,从而获得了极好的抽象水平:使用Blazor——实际上——我们的代码只是C#和Razor语法。
假设您有一个报告库存清单的页面,并且其中任何一种价格发生变化时,都需要刷新HTML页面。
在SignalR之前,通常有一个使用Ajax 的JavaScript代码来定期(例如,每5秒一次)向服务器执行一个GET请求,以便检索可能的新价格并将其显示在HTML页面中。
如今,借助Blazor及其嵌入式SignalR功能,我们可以扭转这一趋势,并让服务器有责任仅在显示一些新价格时才更新HTML页面。
在下面的例子中,Blazor会负责更新HTML页面,而SqlTableDependency组件会负责在由于insert,update或delete而更改表内容时从SQL Server数据库获取通知:
我们必须使用Visual Studio 2019中的适当模板创建.NET CORE 3.0 Blazor Web应用程序。
然后,我们安装SqlTableDependency NuGet软件包,该软件包将负责获取有关记录表更改的通知:
PM> Install-Package SqlTableDependency
现在,对于此示例,让我们考虑要监视以下SQL Server表的值:
CREATE TABLE [dbo].[Stocks]( [Code] [nvarchar](50) NULL, [Name] [nvarchar](50) NULL, [Price] [decimal](18, 0) NULL ) ON [PRIMARY]
因此,我们定义了一个C#模型类,该类映射了我们感兴趣的属性:
namespace BlazorApp1.Models { public class Stock { public decimal Price { get; set; } public string Code { get; set; } public string Name { get; set; } } }
现在,我们创建一个SqlTableDependency单例实例,将记录表更改包装并转发到Blazor页面。我们开始创建其接口:
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(); } }
然后它实现:
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(); } } }
现在我们已经设置了数据库记录更改通知,是时候实现Blazor组件了。第一步,我们检索OnInitialized()方法中的所有当前股价,然后我们订阅有关表记录更改的事件通知,以刷新HTML视图:
@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> <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>@</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; } }
表格记录更改事件处理程序仅检查库存是否在显示的列表中,然后插入或更新其Price值。请注意,HTML将从Blazor自动刷新。为了更新HTML视图内容,我们不需要向浏览器发送任何通知,也不需要从浏览器向服务器发出任何轮询请求。
总而言之,我们将依赖性解析定义为单例:
namespace BlazorApp1 { public class Startup { … … public void ConfigureServices(IServiceCollection services) { … services.AddSingleton<ITableChangeBroadcastService, TableChangeBroadcastService>(); … } }
而且…别忘了设置数据库连接字符串!
{ "ConnectionString": "Data Source=***; initial catalog=***; User ID=sa;Password=***" }