Blazor+SQLTableDependency+SignlR:从OnChange事件通知特定组



我有一个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; }
}

Asset.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; }
}

AssetService.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 Hub

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();
}

所以流量应该是这样的。

  1. 用户登录-通过SignalR建立连接
  2. 连接信息存储在数据库中
  3. 连接将根据用户连接的页面和OrganizationId添加到SignalR组
  4. 用户从UI创建一个新资产
  5. AddAsset方法在资产服务中调用
  6. 将资产插入数据库中
  7. SQLTableDependency检测到更改,然后调用AssetChanged处理程序方法
  8. AssetChanged处理程序方法调用OnAssetChanged事件
  9. AssetHub需要订阅OnAssetChanged事件
  10. 当OnAssetChanged事件被激发时,AssetHub中的处理程序方法需要调用SendAssetToGroup方法
  11. 当用户从"资产"页面导航到另一个页面时,SignalR连接将从数据库中删除,该连接也将从组中删除

在第9步和第10步之前,一切都在进行中。有没有办法做到这一点,因为SQLTableDependency不在乎是谁做了更改,所以我也无法查找需要推送更新的连接组。有什么想法吗?

当UI使用一个名为:Student 的类时

UI组件加入称为"UI"的组;学生;或";BlahNamespace.Student";。如果它只是一个名称列表,如果它是一个实体,则将名称和另一个ID连接为字符串的组连接起来"BlahNamespace.Student:201";在您的情况下,如果数据库从实体中知道这一点,您也可以附加组织的名称以获得更精细的粒度。

服务器可以根据操作情况根据需要通知组。

我将集线器注入API控制器中以实现这一点。

就我个人而言,我不会使用信号服务来传输数据,保持它的重量轻,只是";信号";改变。然后客户可以决定如何处理。这样,在所有配置的安全性下,只能通过API以一种方式访问数据。

相关内容

  • 没有找到相关文章

最新更新