ABP框架系列之四十:(Notification-System-通知系统)
Introduction
Notifications are used to inform users on specific events in the system. ASP.NET Boilerplate provides a pub/sub based real time notification infrastructure.
通知用于通知用户系统中的特定事件。ASP.NET的模板提供了一个基于实时通知基建Pub/Sub。
Sending Models(发送模式)
There are two ways of sending notifications to users:
- User subscribes to a specific notification type. Then we publish a notification of this type which is delivered to all subscribed users. This is the pub/sub model.
- 用户订阅特定的通知类型。然后,我们发布这种类型的通知,该通知传递给所有订阅用户。这是pub/sub模型。
- We can directly send a notification to target user(s).
Notification Types(通知类型)
There are also two types of notifications:
- General notifications are arbitrary type of notifications. "Notify me if a user sends me a friendship request" is an example of this type notifications.
- 一般通知是任意类型的通知。如果用户发送了一个友好请求,请通知我“这是此类通知的一个示例。
- Entity notifications are associated to a specific entity. "Notify me if a user comment on this photo" is an entity based notification since it's associated to a specific photo entity. Users may want to get notifications for some photos, not for all.
- 实体通知与特定实体相关联。”如果用户评论这张照片,请通知我“它是一个基于实体的通知,因为它与特定的照片实体相关联。用户可能希望获得一些照片的通知,而不是所有的照片。
Notification Data
A notification generally include a notification data. For example: "Notify me if a user sends me a friendship request" notification may have two data properties: sender user name (which user sent this friendship request) and request note (a note that user did write in the request). It's obvious that the notification data type is tightly coupled to notification types. Different notification types have different data types.
Notification data is optional. Some notifications may not require a data. There are some pre-defined notification data types those can be enough for most cases. MessageNotificationData can be used for simple messages and LocalizableMessageNotificationData can be used for localizable and parametric notification messages. We will see example usage in later sections.
通知数据是可选的。有些通知可能不需要数据。有一些预定义的通知数据类型,大多数情况下这些数据类型是足够的。messagenotificationdata可以用于简单的消息和localizablemessagenotificationdata可用于定位和参数的通知消息。我们将在后面的部分中看到示例用法。
Notification Severity(通知的严重程度)
There are 5 levels of notification severity, defined in NotificationSeverity enum: Info, Success, Warn, Error and Fatal. Default value is Info.
About Notification Persistence(关于通知的持久性)
See Notification Store section for more information on notification persistence.
Subscribe to Notifications(订阅通知)
INotificationSubscriptionManager provides API to subscribe to notifications. Examples:
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)); } }
First, we injected INotificationSubscriptionManager. First method subscribes to a general notification. User wants to get notified when someone sends a friendship request. Second one subscribes to a notification related to a specific entity (Photo). User wants to get notified if anyone write comment to a specified photo.
首先,我们注入inotificationsubscriptionmanager。第一种方法订阅一般通知。当有人发送友情请求时,用户希望得到通知。二个订阅某一特定实体相关的通知(照片)。用户希望得到通知,如果有人对指定的照片发表评论。
Every notification type should have unique names (like SentFrendshipRequest and CommentPhoto in the samples)
每个通知类型必须具有唯一的名称(如样品中sentfrendshiprequest和commentphoto)
INotificationSubscriptionManager has also UnsubscribeAsync, IsSubscribedAsync, GetSubscriptionsAsync... methods to manage subscriptions.
Publish Notifications(发布通知)
INotificationPublisher is used to publish notifications. Examples:
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);
}
}
In the first example, we published a notification to a single user. SentFrendshipRequestNotificationData should be derived from NotificationData like that:
[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; } }
In the second example, we sent a notification to a specific user for a specific entity. Notification data classes don't need to be serialzable normally (since JSON serialization is used by default). But it's suggested to mark it as serializable since you may need to move notifications between applications and may want to use binary serialization in the future. Also, as declared before, notification data is optional and may not be required for all notifications.
在第二个示例中,我们为特定实体发送通知给特定用户。通知数据的类不需要serialzable正常(由于JSON序列化是默认使用)。但它的建议将其标记为可序列化的因为你可能需要移动应用程序之间和通知可能要在未来使用二进制序列化。此外,正如前面声明的那样,通知数据是可选的,可能不需要所有通知。
Note: If we publish a notification to specific users, they don't need to be subscribed to those notifications.
注意:如果我们向特定用户发布通知,则不需要订阅这些通知。
In the third example, we did not define a dedicated notification data class. instead, directly used built-in LocalizableMessageNotificationData with dictionary based data and published notification as 'Warn'.LocalizableMessageNotificationData can store dictionary-based arbitrary data (this is also true for custom notification data classes since they also inherit from NotificationData class). We used "remainingDiskInMb" as argument on localization. Localization message can include these arguments (like "Attention! Only {remainingDiskInMb} MBs left on the disk!" in the example). We will see how to localize it in the Client Side section.
在第三个例子中,我们没有定义一个专用的通知数据类。相反,直接使用基于字典的数据和公布的通知,警告“内置localizablemessagenotificationdata。localizablemessagenotificationdata可以存储任意数据字典的基础(这也是真正的自定义通知数据类,因为他们也可以从notificationdata类)。我们用“remainingdiskinmb”作为参数对定位。本地化消息可以包含这些参数(如“注意”)!MBs left on the disk!”在例子中)。我们将看到如何在客户端部分本地化它。
User Notification Manager
IUserNotificationManager is used to manage notifications of users. It has methods to get, update or delete notifications for a user. You can use it to prepare a notification list page for your application.
Real Time Notifications
While you can use IUserNotificationManager to query notifications, we generally want to push real time notifications to the client.
Notification system uses IRealTimeNotifier to send real time notifications to users. This can be implemented with any type of real time communication system. It's implemented using SignalR in a seperated package. Startup templates have already SignalR installed. See SignalR Integration document for more information.
Note: Notification system calls IRealTimeNotifier asynchronously in a background job . So, notifications may be sent with a small delay.
Client Side
When a real time notification is received, ASP.NET Boilerplate triggers a global event in the client side. You can register like that to get notifications:
abp.event.on('abp.notifications.received', function (userNotification) { console.log(userNotification); });
abp.notifications.received event is triggered for each received real time notification. You can register to this event as shown above to get notifications. See javascript event bus documentation for more information on events. An example incoming notification JSON for "System.LowDisk" example:
abp.notifications.received事件触发每个收到实时通知。您可以像上面显示的那样注册这个事件来获得通知。有关事件的更多信息,请参见JavaScript事件总线文档。例来信通知JSON”system.lowdisk”
{ "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" }
In this object;
- userId: Current user id. Generally you don't need this since you know the current user.
- 当前用户ID。一般来说,您不需要这个,因为您知道当前用户。
- state: Value of UserNotificationState enum. 0: Unread, 1: Read.
- notification: Notification details.
- notificationName: Unique name of the notification (same value used while publishing the notification).
- data: notification data. In this example, we used LocalizableMessageNotificationData (as published in the example before).
- message: Localizable message information. We can use sourceName and name to localize message on the UI.
- type: Notification data type. Full type name, including namespaces. We can check this type while processing the notification data.
- properties: Dictionary based custom properties.基于字典的自定义属性
- entityType, entityTypeName and entityId: Entity information if this is an entity related notification.
- severity: Value of NotificationSeverity enum. 0: Info, 1: Success, 2: Warn, 3: Error, 4: Fatal.
- creationTime: Time of when this notification is created.
- id: Notification id.
- id: User notification id.
Surely, you will not just log the notification. You can use notification data to show notification information to the user. Example:
当然,您不只是记录通知。可以使用通知数据向用户显示通知信息。例子:
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); } });
To be able to process notification data, we should check the data type. This example simply gets message from notification data. For the localized message (LocalizableMessageNotificationData), we are localizing the message and replacing parameters. For simple message (MessageNotificationData), we directly get the message. Surely, in a real project, we will not use alert function. We can use abp.notify api to show nice UI notifications.
为了能够处理通知数据,我们应该检查数据类型。这个示例只从通知数据获取消息。的本地化消息(localizablemessagenotificationdata),我们是本地化的信息替换参数。对于简单的信息(messagenotificationdata),我们直接得到的消息。当然,在实际的项目中,我们不会使用警报功能。我们可以用abp.notify API显示漂亮的UI通知。
If you need to implement such a logic above, there is an easier and scaleable way. You can just use single line of code to show a UI notification when a push notification is received:
如果你想实现这样的逻辑之上,有一个更简单的和可扩展的方式。当接收到推送通知时,您可以只使用单行代码显示UI通知:
abp.event.on('abp.notifications.received', function (userNotification) { abp.notifications.showUiNotifyForUserNotification(userNotification); });
This shows a UI notification like that (for System.LowDisk notification published above):
It works for built-in notification data types (LocalizableMessageNotificationData and MessageNotificationData). If you have custom notification data types, then you should register data formatters like that:
它的内置数据类型工作的通知(localizablemessagenotificationdata和messagenotificationdata)。如果你有自定义通知数据类型,那么你应该登记,数据格式化:
abp.notifications.messageFormatters['MyProject.MyNotificationDataType'] = function(userNotification) { return ...; //format and return message here };
Thus, showUiNotifyForUserNotification can create shown messages for your data types. If you just need to the formatted message, you can directly useabp.notifications.getFormattedMessageFromUserNotification(userNotification) which is internally used by showUiNotifyForUserNotification.
Startup templates include the code to show UI notifications when a push notification is received.
Notification Store
Notification system uses INotificationStore to persist notifications. This should be implemented in order to make notification system properly working. You can implement it yourself or use module-zero which already implements it.
系统采用inotificationstore坚持通知的通知。为了使通知系统正常工作,应该实现这一点。您可以自己实现它,也可以使用已经实现它的module-zero。
Notification Definitions(通知定义)
You don't have to define a notification before usage. You can just use any notification name without defining it. But, defining it may bring you some additional benefits. For example, you can then investigate all notifications in your application. In this case, we can define a notification provider for our module as shown below:
使用前不必定义通知。您可以使用任何通知名称而不定义它。但是,定义它可能会给你带来一些额外的好处。例如,您可以调查应用程序中的所有通知。在这种情况下,我们可以为我们的模块定义一个通知提供者,如下所示:
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" is unique name of the notification. We defined a localizable displayName (then we can show it while subscribing to the notification on UI). And finally, we declared that this notification is available to a user only if he has "App.Pages.UserManagement" permission.
There are also some other parameters, you can investigate in the code. Only notification name is required for a notification definition.
After defining such a notification provider, we should register it in PreInitialize event of our module, as shown below:
public class AbpZeroTemplateCoreModule : AbpModule { public override void PreInitialize() { Configuration.Notifications.Providers.Add<MyAppNotificationProvider>(); } //... }
Finally, you can inject and use INotificationDefinitionManager in your application to get notification definitions. Then you may want to prepare a automatic page to allow user to subscribe those notifications.
最后,你可以注入和获取通知定义应用程序中使用inotificationdefinitionmanager。然后,您可能需要准备一个自动页面,允许用户订阅这些通知。