通知系统

介绍

  在系统中通知用来基于特定的事件告知用户。ABP提供了pub/sub基础实时通知基础设施。

发送模型

  有两种给用户发送通知的方式:

  • 用户订阅一个特定的通知类型。然后我们发布此类型的一个通知,此类型会被发送给所有订阅的用户。这就是pub/sub模型。
  • 我们可以直接给目标用户发送一个通知。

通知类型

  通知有两种类型:

  • 普通通知可以是任意类型。“如果一个用户发送给我一个友好的请求,通知我”就是这种通知类型的一个例子。
  • 实体通知关联到一个特定的实体。“如果一个用户评论了这张照片,通知我”就是一种实体基础类型,因为它和一个特定的photo实体关联。用户可能想得到一些照片的通知,而不是全部。

通知数据

  一个通知一般包含通知数据。例如:“如果一个用户发送给我一个友好的请求,通知我”通知会有两个数据属性:sender user name(哪个用户发送了这个友好请求)和request note(发送用户在请求中的笔记)。很显然通知数据的类型和通知类型紧密相连。不同的通知类型有不同的数据类型。

  通知数据是可选的。一些通知可能不请求数据。这有预定义的通知数据类型,这些可以满足大多数场景。MessageNotificationData可以用于简单的信息,LocalizableMessageNotificationData可以用于本地化和参数化的通知信息。在之后的部分我们将会看到示例用法。

通知严重性

  通知严重性有5种等级,定义在NotificationSeverity枚举中:InfoSuccessWarnErrorFatal。默认值是Info

关于通知持久化

  参见通知存储部分了解更多关于通知持久化的信息。

订阅通知

  INotificationSubscriptionManaer提供了API类来订阅通知。示例:

public class MyService : ITransientDependency
{
private readonly INotificationSubscriptionManager _notificationSubscriptionManager; public MyService(INotificationSubscriptionManager notificationSubscriptionManager)
{
_notificationSubscriptionManager = notificationSubscriptionManager;
} //Subscribe to a general notification
public async Task Subscribe_SentFrendshipRequest(int? tenantId, long userId)
{
await _notificationSubscriptionManager.SubscribeAsync(new UserIdentifier(tenantId, userId), "SentFrendshipRequest");
} //Subscribe to an entity notification
public async Task Subscribe_CommentPhoto(int? tenantId, long userId, Guid photoId)
{
await _notificationSubscriptionManager.SubscribeAsync(new UserIdentifier(tenantId, userId), "CommentPhoto", new EntityIdentifier(typeof(Photo), photoId));
}
}

  首先,我们注入了INotificationSubscriptionManager。第一个方法订阅了一个一般通知。当某人发送了一个友好请求后,用户希望得到通知。第二个订阅了一个与特定实体(Photo)相关的通知。当任何人给特定的照片写评论时得到通知。

  每一个通知类型需要有唯一的名称(如示例中的SentFrendshipRequest和CommentPhoto)。

  INotificationSubscriptionManager还有UnsubscribeAsync,IsSubscribedAsync,GetSubscriptionsAsync...方法来管理订阅。

发布通知

  INotificationPublisher用来发布通知。示例:

public class MyService : ITransientDependency
{
private readonly INotificationPublisher _notiticationPublisher; public MyService(INotificationPublisher notiticationPublisher)
{
_notiticationPublisher = notiticationPublisher;
} //Send a general notification to a specific user
public async Task Publish_SentFrendshipRequest(string senderUserName, string friendshipMessage, UserIdentifier targetUserId)
{
await _notiticationPublisher.PublishAsync("SentFrendshipRequest", new SentFrendshipRequestNotificationData(senderUserName, friendshipMessage), userIds: new[] { targetUserId });
} //Send an entity notification to a specific user
public async Task Publish_CommentPhoto(string commenterUserName, string comment, Guid photoId, UserIdentifier photoOwnerUserId)
{
await _notiticationPublisher.PublishAsync("CommentPhoto", new CommentPhotoNotificationData(commenterUserName, comment), new EntityIdentifier(typeof(Photo), photoId), userIds: new[] { photoOwnerUserId });
} //Send a general notification to all subscribed users in current tenant (tenant in the session)
public async Task Publish_LowDisk(int remainingDiskInMb)
{
//Example "LowDiskWarningMessage" content for English -> "Attention! Only {remainingDiskInMb} MBs left on the disk!"
var data = new LocalizableMessageNotificationData(new LocalizableString("LowDiskWarningMessage", "MyLocalizationSourceName"));
data["remainingDiskInMb"] = remainingDiskInMb; await _notiticationPublisher.PublishAsync("System.LowDisk", data, severity: NotificationSeverity.Warn);
}
}

  在第一个例子中,我们发布了一个通知给一个单独的用户。SetFrendshipRequestNotificationData需要继承NotificationData

[Serializable]
public class SentFrendshipRequestNotificationData : NotificationData
{
public string SenderUserName { get; set; } public string FriendshipMessage { get; set; } public SentFrendshipRequestNotificationData(string senderUserName, string friendshipMessage)
{
SenderUserName = senderUserName;
FriendshipMessage = friendshipMessage;
}
}

  在第二个示例中,我们发送一个特定实体的通知给一个特定的用户。通知数据类不需要序列化(因为默认会使用JSON序列化)。但是建议将它标记为序列化,因为在将来可能会在应用之间移动通知并且可能会使用二进制序列化。如之前所述,通知数据是可选的,并不是所有的通知都需要。

  注意:如果我们给特定的用户发布了一个通知,他们不需要订阅这些通知。

  在第三个示例中,我们没有定义一个专用的通知数据类。而是直接使用了內建的LocalizableMessageNotificationData并发布通知为'Warn'级别。LocalizableMessageNotificationData可以存储任意基于字典类型的数据(这也同样适用于自定义通知数据类,因为他们也继承自NotificationData类)。我们使用“remainingDiskInMb”作为本地化参数。本地化信息包含三个参数(如“Attention!Only{remainingDiskInMb}MBs left on the disk!”)。我们将在客户端部分看到如何本地化它。

用户通知管理

  IUserNotificationManager用来管理用户通知。它有方法可以用来get,update,delete用户通知。你可以用它来准备一个通知列表页。

实时通知

  当你使用IUserNotificationManager来查询通知时,我们通常希望推送实时通知到客户端。

  通知系统使用IRealTimeNotifier来发送实时通知给用户。这可以被任何实时通信系统实现。在一个单独的包里,使用SignalR对它进行了实现。启动模板已经安装了SignalR。参见SignalR集成文档了解更多信息。

  注意:通知系统在后台异步调用IRealTimeNotifier。所以,通知可能会有一些小延迟。

客户端

  当一个事实通知被接收时,ABP在客户端触发一个全局事件。你可以按如下方式注册来获取通知:

abp.event.on('abp.notifications.received', function (userNotification) {
console.log(userNotification);
});

  abp.notifications.received事件在每次接收到实时通知时触发。你可以按如上所示注册这个事件来获取通知。参见javascript event bus文档来获取关于事件的更多信息。一个通知"System.LowDisk"的JSON字符串示例:

{
"userId": 2,
"state": 0,
"notification": {
"notificationName": "System.LowDisk",
"data": {
"message": {
"sourceName": "MyLocalizationSourceName",
"name": "LowDiskWarningMessage"
},
"type": "Abp.Notifications.LocalizableMessageNotificationData",
"properties": {
"remainingDiskInMb": "42"
}
},
"entityType": null,
"entityTypeName": null,
"entityId": null,
"severity": 0,
"creationTime": "2016-02-09T17:03:32.13",
"id": "0263d581-3d8a-476b-8e16-4f6a6f10a632"
},
"id": "4a546baf-bf17-4924-b993-32e420a8d468"
}

  在这个对象中:

  • userid:当前用户id。一般不需要这个因为你知道当前用户。
  • state:UserNotificationState枚举的值。0:Unread,1:Read
  • notification:通知详情。
    • notificationName:通知的唯一名称(当发布通知时也使用这个值)。
    • data:通知数据。在这个示例中,我们使用LocalizableMessageNotificationData(如在之前示例中发布的)。
      • message:本地化消息信息。我们可以使用sourceName和name在UI中本地化信息。
      • type:通知数据类型。全类型名称,包含命名空间。当处理通知数据时,我们可以检查这个类型。
      • properties:基于字典的自定义属性。
    • entityType,entityTypeNameentityId:实体信息,如果这是一个实体相关的通知。
    • severityNotificationSeverity枚举的值。0:Info,1:Success,2:Warn,3:Error,4:Fatal
    • creationTime:通知创建的时间。
    • id:通知id。
  • id:用户通知id。

  当然,你不需要记录这个通知。你可以使用通知数据显示通知消息给用户。示例:

abp.event.on('abp.notifications.received', function (userNotification) {
if (userNotification.notification.data.type === 'Abp.Notifications.LocalizableMessageNotificationData') {
var localizedText = abp.localization.localize(
userNotification.notification.data.message.name,
userNotification.notification.data.message.sourceName
); $.each(userNotification.notification.data.properties, function (key, value) {
localizedText = localizedText.replace('{' + key + '}', value);
}); alert('New localized notification: ' + localizedText);
} else if (userNotification.notification.data.type === 'Abp.Notifications.MessageNotificationData') {
alert('New simple notification: ' + userNotification.notification.data.message);
}
});

  为了能够处理通知数据,我们应该检查数据类型。这个示例简单从通知数据中获取消息。对于本地化消息(LocalizableMessageNotificationData),我们本地化消息并取代参数。对于简单的消息(MessageNotificationData),我们直接获取消息。当然,在实际工程中,我们将不使用alert函数,我们可以使用abp.notifyapi来显示UI通知。

  如果你需要实现这样的一个逻辑,这有一个简单且可伸缩的方式。当收到一个推送通知时,你可以仅使用一行代码就可以显示UI通知:

p.event.on('abp.notifications.received', function (userNotification) {
abp.notifications.showUiNotifyForUserNotification(userNotification);
});

  这会显示一个UI通知(如收到上面发布的System.LowDisk通知):

  这适用于內建通知数据类型(LocalizableMessageNotificationData和MessageNotificationData)。如果你有自定义数据类型,那么你应该如下所示注册数据格式化器:

abp.notifications.messageFormatters['MyProject.MyNotificationDataType'] = function(userNotification) {
return ...; //format and return message here
};

  因此,showUiNotifyForUserNotification可以针对于你的数据类型创建显示消息 。如果你仅仅需要格式化信息,你可以直接使用abp.notifications.getFormattedMessageFromUserNotification(userNotification),它被showUiNotifyForUserNotification内部所使用。

  启动模板包含当收到一个推送通知时,显示UI通知的代码。

通知存储

  通知系统使用INotificationStore来持久化通知。为了使通知系统正常工作,需要实现这个接口。你可以自己实现它或使用已经实现了它的module-zero.

通知定义

  在使用通知之前,你可以不用定义它。不定义的话,你仅仅能使用任何通知名称。但是,定义通知会带来一些额外的好处。例如,你可以调查你应用中的所有通知。在这种情况下,我们可以为我们的模块定义一个通知提供者,如下:

public class MyAppNotificationProvider : NotificationProvider
{
public override void SetNotifications(INotificationDefinitionContext context)
{
context.Manager.Add(
new NotificationDefinition(
"App.NewUserRegistered",
displayName: new LocalizableString("NewUserRegisteredNotificationDefinition", "MyLocalizationSourceName"),
permissionDependency: new SimplePermissionDependency("App.Pages.UserManagement")
)
);
}
}

  "App.NewUserRegistered"是通知的唯一名称。我们定义了一个本地化displayName(然后当在UI中订阅这个通知时,可以显示它)。最后,我们声明只有有"App.Pages.UserManagement"权限的用户这个通知才可用。

  还有其他的一些参数,你可以在代码中研究它。对于通知定义,只有通知名称是必须的。

  当定义了一个通知提供者之后,我们需要在模块的PreInitialize方法中注册它,如下所示:

public class AbpZeroTemplateCoreModule : AbpModule
{
public override void PreInitialize()
{
Configuration.Notifications.Providers.Add<MyAppNotificationProvider>();
} //...
}

  最后,你可以在应用程序中注入并使用INotificationDefinitionManager来获取通知定义。然后你可能想准备一个自动页来允许用户订阅这些通知。

返回主目录

ABP官方文档翻译 8.1 通知系统的更多相关文章

  1. ABP官方文档翻译 1.3 模块系统

    ABP模块系统 介绍 模块定义 生命周期方法 PreInitialize Initialize PostInitialize Shutdown 模块依赖 插件系统 ASP.NET Core ASP.N ...

  2. ABP官方文档翻译 8.2 SignalR集成

    SignalR集成 介绍 安装 服务器端 客户端 建立连接 內建特征 通知 在线客户端 PascalCase与CamelCase对比 你的SignalR代码 介绍 ABP中的Abp.Web.Signa ...

  3. ABP官方文档翻译 0.0 ABP官方文档翻译目录

    一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...

  4. ABP官方文档翻译 6.3 本地化

    本地化 介绍 应用程序语言 本地化源 XML文件 注册XML本地化源 JSON文件 注册JSON本地化源 资源文件 自定义源 当前语言是如何决定的 ASP.NET Core ASP.NET MVC 5 ...

  5. ABP官方文档翻译 4.1 应用服务

    应用服务 IApplicationService接口 ApplicationService类 CrudService和AsyncCrudAppService类 简单的CRUD应用服务示例 自定义CRU ...

  6. ABP官方文档翻译 7.1 后台Jobs和Workers

    后台Jobs和Workers 介绍 后台Jobs 关于Job持久化 创建后台Job 在队列中添加一个新Job 默认的后台Job管理器 后台Job存储 配置 禁用Job执行 异常处理 Hangfire集 ...

  7. ABP官方文档翻译 3.7 领域事件(事件总线)

    领域事件(事件总线) 事件总线 注入IEventBus 获取默认实例 定义事件 预定义事件 处理异常 实体更改 触发事件 处理事件 处理基础事件 处理者异常 处理多个事件 注册处理者 自动 手动 取消 ...

  8. ABP官方文档翻译 2.1 依赖注入

    依赖注入 什么是依赖注入 传统方式的问题 解决方案 构造函数注入模式 属性注入模式 依赖注入框架 ABP依赖注入基础设施 注册依赖注入 传统注册 帮助接口 自定义/直接注册 使用IocManager ...

  9. ABP官方文档翻译 1.2 N层架构

    N层架构 介绍 ABP架构 其他(通用) 领域层 应用层 基础设施层 网络和展现层 其他 总结 介绍 应用程序代码库的分层架构是被广泛认可的可以减少程序复杂度.提高代码复用率的技术.为了实现分层架构, ...

随机推荐

  1. 【批处理学习笔记】第二十九课:ASCII码

    前面的例子中,我们已经使用过一次ASCII码了,也就是那个笑脸.ASCII码是图形化的符号,可以用来点缀我们的批处理的.    在cmd窗口中我们可以通过任意一个字符的ASCII码来输入该字符,比如C ...

  2. 2017 Multi-University Training Contest - Team 1 1011&&HDU 6043 KazaQ's Socks【规律题,数学,水】

    KazaQ's Socks Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)T ...

  3. [bzoj1774] [Usaco2009 Dec]Toll 过路费

    Floyd神用法...设dis[i][j]表示i点到j点的最短路(只算边权),map[i][j]表示i到j最小费用 将n个点先按照点权排一下序...这样就可以比较方便的求出路径上最大点权了... 因为 ...

  4. CodeForces 543D:Road Improvement

    题目:http://codeforces.com/problemset/problem/543/D 题意:给你一棵树,一开始边都是0,可以使任意的边变成1,对于每一个根节点求使得它到其他任一点的路径上 ...

  5. Palindromes

    http://acm.hdu.edu.cn/showproblem.php?pid=1318 Palindromes Time Limit: 2000/1000 MS (Java/Others)    ...

  6. block的修饰词为什么选用copy

    想必很多开发人员知道一般用copy修饰block,接下来就讲解为什么需要用copy,甚至会讲到其实用strong修饰block也是可以的 在 Objective-C 语言中,一共有 3 种类型的 bl ...

  7. 一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](六)

    前言 大家好,我是Rector 又是星期五,很兴奋,很高兴,很high...啦啦啦... Rector在图享网又和大家见面啦!!!上一篇<一步一步创建ASP.NET MVC5程序[Reposit ...

  8. Java学习笔记22---内部类之成员内部类的继承问题

    成员内部类可以继承其他的类,也可以被其它类继承,本文主要说明其它类继承成员内部类的问题. 本文要点如下: 1).成员内部类的子类可以是内部类,也可以不是内部类: 2).当成员内部类的子类不是内部类或子 ...

  9. webzip怎么用 如何用webzip下载整个网站?

    相信很多站长对webzip这款软件都并不感到陌生,它功能强大,能够完整下载网站的内容,或者你也可以选择自行设置下载的层数.文件类型.网页与媒体文件的定位等等.具体详情你可以在百度上去搜一下.由于web ...

  10. 数据库复习总结(17)-T-Sql编程

    T-SQL(SQL SERVER) 百度百科:(即 Transact-SQL,是 SQL 在 Microsoft SQL Server 上的增强版,它是用来让应用程序与 SQL Server 沟通的主 ...