应用内部的领域对象发生变化时可以通过领域事件通知,那么应用之间该如何处理?选择SignalR实时数据传输方案能够解决这一问题,在ABP框架中,可以使用简化的已封装的SignalR相关依赖库。
ABP框架提供的SignalR依赖库有两个,一个是.NET Framework环境下的Abp.Web.SignalR,另一个是.NET Core环境下的Abp.AspNetCore.SignalR。文中示例使用的是Abp.AspNetCore.SignalR。
首先在分布式服务层和展现层AbpDemo.Web项目中使用NuGet管理安装Abp.AspNetCore.SignalR程序包。发文时使用版本为5.1,与ABP框架主版本号一致。
然后在AbpDemo.Web项目的模块类AbpDemoWebModule中添加依赖模块。
[DependsOn(
typeof(AbpDemoApplicationModule),
typeof(AbpDemoEntityFrameworkCoreModule),
typeof(AbpAspNetCoreSignalRModule),
typeof(AbpAspNetCoreModule))]
public class AbpDemoWebModule : AbpModule
{
/**/
}
之后在启动类Startup中使用AddSignalR和UseSignalR方法对SignalR进行配置。
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
#region SignalR
services.AddSignalR(options =>
{
options.KeepAliveInterval = TimeSpan.FromSeconds(5);//心跳间隔
});
#endregion
//...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
//...
#region SignalR
app.UseSignalR(config =>
{
config.MapHub<MessageHub>("/messagebus");
});
#endregion
//...
}
}
Hub集线器类是SignalR类库中用于创建服务端实现实时传输的类之一,代码内容和在纯粹的ASP.NET Core项目中创建Hub类没什么区别,继承自基类Microsoft.AspNetCore.SignalR.Hub。ABP框架本身也提供了可用的基类OnlineClientHubBase和AbpHubBase,内置了日志、会话、配置、本地化等组件,都继承自基类Microsoft.AspNetCore.SignalR.Hub。所以也可以继承ABP框架提供的基类,方便的使用内置特性。
///
/// 实时消息集线器类
///
public class MessageHub:OnlineClientHubBase
{
public MessageHub(IOnlineClientManager onlineClientManager,IClientInfoProvider clientProvider):base(onlineClientManager,clientProvider)
{
}
//消息广播
public async Task Broadcast(string message)
{
await Clients.All.SendAsync(message);
}
}
完成上述步骤后,启动项目,打开客户端就可以与服务端建立连接并发送、接收实时消息了。
前两步完成后,服务端可以与客户端建立连接并顺利的发送、接收消息了,但实际上就是一个纯粹的ASP.NET Core SignalR项目,和实际业务并没有产生什么关联。如果想把之前文章中库存预警的消息发送给外部应用呢?那么就可以利用ABP框架的设计思路和特性对服务端代码进行改造。
首先,借鉴SignalR融合到ASP.NET Core WebAPI的做法,在Web项目中创建一个用于中间协作的类。
public class MessageCommunicator : IMessageCommunicator, ITransientDependency
{
private readonly IHubContext<MessageHub> _hubContext;
public MessageCommunicator(IHubContext<MessageHub> hubContext)
{
_hubContext = hubContext;
}
///
/// 发送消息给所有客户端
///
/// 通道
/// 内容
///
public async Task SendMessageToAll(string channel, string content)
{
await _hubContext.Clients.All.SendAsync(channel, content);
}
}
public interface IMessageCommunicator
{
///
/// 发送消息给所有客户端
///
/// 通道
/// 内容
///
Task SendMessageToAll(string channel, string content);
}
之后在领域层Core项目中创建用于发送消息的领域服务。
///
/// 实时消息-领域服务
///
public class MessageManager : DomainService, IMessageManager
{
private readonly IMessageCommunicator _messageCommunicator;
public MessageManager(IMessageCommunicator messageCommunicator)
{
_messageCommunicator = messageCommunicator;
}
public async Task BoradcastMessage(object obj)
{
string content = obj.ToString();
await _messageCommunicator.SendMessageToAll("broadcast", content);
}
}
public interface IMessageManager : IDomainService
{
Task BoradcastMessage(object obj);
}
相应的,SignalR的服务端代码改为:
///
/// 实时消息集线器类
///
public class MessageHub:OnlineClientHubBase
{
private readonly IMessageManager _messageManager;
public MessageHub(IMessageManager messageManager,
IOnlineClientManager onlineClientManager,
IClientInfoProvider clientProvider):base(onlineClientManager,clientProvider)
{
_messageManager = messageManager;
}
//消息广播
public async Task Broadcast(string message)
{
//await Clients.All.SendAsync(message);
await _messageManager.BoradcastMessage(message);
}
}
经过上面的改造后,就可以在应用层Application项目中使用实时消息的领域服务了。以货品管理中的出库操作为例,当货品数量低于下限时,发送消息通知外部应用。
///
/// 货品管理-应用服务
///
public class GoodsAppService: AbpDemoAppServiceBase<Goods,DetailGoodsDto,string,CreateGoodsDto,UpdateGoodsDto,PagedGoodsDto>,IGoodsAppService
{
private readonly IGoodsRecordManager _goodsRecordManager;//出入库记录领域服务
private readonly IGoodsManager _goodsManager;//货品管理领域服务
private readonly IMessageManager _messageManager;//实时消息领域服务
public IEventBus EventBus { get; set; }//事件总线
private const int MinNum = 50;//货品数量下限
public GoodsAppService(IRepository<Goods,string> repository,
IGoodsRecordManager goodsRecordManager,
IGoodsManager goodsManager,
IMessageManager messageManager) :base(repository)
{
_goodsRecordManager = goodsRecordManager;
_goodsManager = goodsManager;
_messageManager = messageManager;
EventBus = NullEventBus.Instance;
}
///
/// 出库
///
///
///
public async Task<DetailGoodsDto> Out(InOutGoodsDto input)
{
Goods entity = Repository.FirstOrDefault(input.Id);
if (entity != null)
{
entity.GoodsNum = entity.GoodsNum - input.GoodsNum;
}
GoodsRecord record = input.MapTo<GoodsRecord>();
record.OperateType = GoodsOperateType.Out;
string recordId = await _goodsRecordManager.OutRecord(record);
entity = await Repository.UpdateAsync(entity);
if (entity.GoodsNum<=MinNum)
{
string message = string.Format("货品{0}当前库存为{1},低于最低允许库存{2},请及时采购补充!", entity.GoodsName, entity.GoodsNum, MinNum);
await _messageManager.BoradcastMessage(message);
}
DetailGoodsDto result = entity.MapTo<DetailGoodsDto>();
return await Task.FromResult(result);
}
}
此外也可参考实时消息领域服务的例子将消息推送的方法加入到货品管理领域服务GoodsManager或出入库记录领域服务GoodsRecordManager中,甚至将其加入到领域事件的处理方法中也是可行的。
源代码示例-Github