【发布时间】:2020-09-03 15:19:43
【问题描述】:
我有一个 Blazor 应用程序,它使用 SQLTableDependency 检测数据库更改,然后通过 SignalR 通知所有客户端有关更改。这可行,但我需要一种能够检测更改并仅通知特定 SignalR 组的方法。因为 SQLTableDependency 不关心谁在数据库中插入、更改或删除了一条记录,所以我也不确定如何知道哪个组发送更新。请参阅下文,了解有关我的应用程序以及我想要完成的任务的更多详细信息。
我们为每个客户建立一个新的组织。一个组织有自己的资产列表,并且可以有多个用户。
Organization.cs
public class Organization
{
public int OrganizationId { get; set; }
public string OrganizationName { get; set; }
public List<Asset> Assets { get; set; }
public List<ApplicationUser> Users { get; set; }
public bool IsDisabled { get; set; }
}
资产.cs
public class Asset
{
public int AssetId { get; set; }
public string SerialNumber { get; set; }
public int OrganizationId { get; set; }
public virtual Organization Organization { get; set; }
public DateTime DateAdded { get; set; }
}
ApplicationUser.cs
public class ApplicationUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int OrganizationId { get; set; }
public virtual Organization Organization { get; set; }
public List<Connection> Connections { get; set; }
public string Timezone { get; set; }
}
Connection.cs - 我将每个 SignalR 连接存储在数据库中。
public class Connection
{
public string ConnectionId { get; set; }
public string UserName { get; set; }
public bool Connected { get; set; }
public string Group { get; set; }
public DateTime ConnectionTime { get; set; }
}
资产服务.cs
public class AssetService : IAssetService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public AssetService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public async Task<Asset> AddAssetAsync(Asset asset, string currentUserName)
{
try
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<DataContext>();
if (asset.Device != null)
{
db.Entry(asset.Device).State = EntityState.Modified;
}
asset.DateAdded = DateTime.UtcNow;
await db.Assets.AddAsync(asset);
await db.SaveChangesAsync();
return asset;
}
}
catch (System.Exception ex)
{
throw ex;
}
}
}
AssetHub.cs - SignalR 集线器
public class ChatHub : Hub
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IServiceScopeFactory _serviceScopeFactory;
public ChatHub(UserManager<ApplicationUser> userManager, IServiceScopeFactory serviceScopeFactory)
{
_userManager = userManager;
_serviceScopeFactory = serviceScopeFactory;
}
public async Task SendAssetToGroup(string userName, string location, Asset asset)
{
if (!string.IsNullOrWhiteSpace(userName))
{
var user = await _userManager.Users.Include(x => x.Connections).SingleAsync(x => x.UserName == userName);
if (user != null)
{
var group = $"{user.AccountId}-{location}";
await Clients.Group(group).SendAsync("AssetUpdate", user.Email, asset);
}
}
}
public override async Task OnConnectedAsync()
{
var httpContext = Context.GetHttpContext();
var location = httpContext.Request.Query["location"];
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<ApplicationDbContext>();
if (!string.IsNullOrWhiteSpace(userName))
{
var user = await db.Users.Include(x => x.Connections).SingleAsync(x => x.UserName == httpContext.User.Identity.Name);
if (user != null)
{
var group = $"{user.OrganizationId}-{location}";
var connection = new Connection { Connected = true, ConnectionId = Context.ConnectionId, Group = group, UserName = user.UserName };
await Groups.AddToGroupAsync(connection.ConnectionId, group);
user.Connections.Add(connection);
db.Users.Update(user);
}
}
await db.SaveChangesAsync();
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
if (!string.IsNullOrWhiteSpace(Context.ConnectionId))
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<ApplicationDbContext>();
var connection = await db.Connections.Where(x => x.ConnectionId ==
Context.ConnectionId).FirstOrDefaultAsync();
if (connection != null)
{
await Groups.RemoveFromGroupAsync(connection.ConnectionId, connection.Group);
db.Connections.Remove(connection);
await db.SaveChangesAsync();
}
}
}
await base.OnDisconnectedAsync(exception);
}
}
AssetTableChangeService.cs - 这是我需要帮助的地方。当 SQLTableDependency 检测到 Assets 表发生变化时,我需要能够调用 AssetHub 中的 SendAssetToGroup 方法。由于用户不属于组织,因此我不想将更新推送到所有组织,我只想将更新仅发送给特定组织组之外的用户。
public class AssetTableChangeService : IAssetTableChangeService
{
private const string TableName = "Assets";
private SqlTableDependency<Asset> _notifier;
private IConfiguration _configuration;
public event AssetChangeDelegate OnAssetChanged;
public StockTableChangeService(IConfiguration configuration)
{
_configuration = configuration;
// SqlTableDependency will trigger an event
// for any record change on monitored table
_notifier = new SqlTableDependency<Asset>(
_configuration.GetConnectionString("DefaultConnection"),
TableName);
_notifier.OnChanged += AssetChanged;
_notifier.Start();
}
private void AssetChanged(object sender, RecordChangedEventArgs<Asset> e)
{
OnAssetChanged.Invoke(this, new AssetChangeEventArgs(e.Entity, e.EntityOldValues));
}
public void Dispose()
{
_notifier.Stop();
_notifier.Dispose();
}
所以流程应该是这样的。
- 用户登录 - 通过 SignalR 建立连接
- 连接信息存储在数据库中。
- 根据用户连接的页面和 OrganizationId 将连接添加到 SignalR 组。
- 用户从 UI 创建新资产。
- 在资产服务中调用 AddAsset 方法。
- 资产被插入到数据库中。
- SQLTableDependency 检测到更改,然后调用 AssetChanged 处理程序方法。
- AssetChanged 处理程序方法调用 OnAssetChanged 事件。
- AssetHub 需要订阅 OnAssetChanged 事件。
- 触发 OnAssetChanged 事件时,AssetHub 中的处理程序方法需要调用 SendAssetToGroup 方法。
- 当用户从资产页面导航到另一个页面时,会从数据库中删除 SignalR 连接,并从组中删除该连接。
在第 9 步和第 10 步之前,我已经完成了所有工作。无论如何,是否有可能使这成为可能,因为 SQLTableDependency 不关心谁进行了更改,所以我无法查找更新需要的连接组也被推。有什么想法吗?
【问题讨论】:
-
你没有提到这是 Blazor-ServerSide 还是 Blazor-WASM - 这很重要
标签: c# .net-core signalr blazor signalr-hub