前言

本文将以一个场景讲述如果在 Dynamics CRM App 中使用 “消息提醒(Notification)”,我将使用三种推送方式为用户创建消息提醒:

  1. Cloud Flow
  2. Client API
  3. Csharp(C#)

1.启用应用消息通知

Power Apps –> 点击菜单栏中 “App” –> 找到自己的应用 –> 点击 “三个点” –> 点击 “编辑”

Power Apps 界面

Power Apps 界面

点击应用编辑页面上方的 Settings 按钮

Power Apps App Setting

Power Apps App Setting

点击 Features 选项卡 –> 启用 In App Notification –> 保存

启用消息提醒

启用消息提醒

如果一切顺利,可以在 Table 中看到 Notification 表:

Notification 表

Notification 表

2.创建消息提醒

方式1 使用 Cloud Flow

场景
客户记录被分派后,通知被分派的用户:xxxx客户已经分派给您了,请及时与客户取得联系。

(1) 新建 Flow

打开解决方案 –> 新建 –> Automation –> Cloud flow –> Automated

Create Cloud Flow

Create Cloud Flow

输入一个有意义的 Flow 名称 –> 通过模糊搜索(输入:Dataverse) –> 选择 When a row is added, modified or deleted 触发器 –> Create

Create Cloud Flow

Create Cloud Flow

(2) 配置 Flow

(1)配置 When a row is added, modified or deleted 触发器,然后点击 “+New Step”

  • Change type:Modified
  • Table name:Accounts
  • Scope:Organization
  • Select columns:ownerid
配置 “触发器”

配置 “触发器”

(2)通过模糊搜索(输入:Dataverse),选择 Add a new row 操作

选择 “操作”

选择 “操作”

(3)配置 Add a new row 操作,然后点击保存按钮

配置 “操作”

配置 “操作”

提示:变量需要通过 “Add dynamic content” 选择。
  • Table name:选择 Notifications
  • Title:输入 Client assignment reminders
  • Body:输入 Customer [变量:Account Name] has been assigned to you, please contact the customer in time.
  • Expiry (seconds):输入 1200
  • IconType:选择 Success
  • Owner (Owners):输入 /systemusers(变量: Owner(Value))
  • Data:输入下面的 Json ,其中 entityLigicalName 输入 account, 和 RecordId 需要替换
{
    "actions": [
        {
            "title": "Open Account record",
            "data": 
            {
                "url": "?pagetype=entityrecord&etn=account&id=变量:Account",
                "navigationTarget": "newWindow"
            }
        }
    ]
}

(3) 测试 Flow

打开一条客户记录,将这条客户记录分派给自己。

将客户记录分派给自己

将客户记录分派给自己

如果一切顺利的话,你将在右上角看到弹出消息提醒:

右上角弹出消息提醒

右上角弹出消息提醒

点击右上角菜单栏中的小铃铛,可以展开消息提醒:

展开消息提醒

展开消息提醒

同时,用户在 Power Apps 移动端的应用中,一样能看到消息提醒:

在 Power Apps 移动端的应用中看到的消息提醒

在 Power Apps 移动端的应用中看到的消息提醒

方式2 使用 Client API

场景
  1. 在客户表单添加 Example In-App Notification 按钮
  2. 点击按钮后创建消息提醒

(1) 编写 JavaScript 代码

这是本次使用的 JavaScript 代码:

gdh_/Account/Account.js
/**
 * Example code ...
 */
'use strict';
if (Gdh === undefined) { var Gdh = {}; }
if (Gdh.D365 === undefined) { Gdh.D365 = {}; }
if (Gdh.D365.Account === undefined) { Gdh.D365.Account = {}; }
(function () {
    this.formOnLoad = function (executionContext) {
        //var formContext = executionContext.getFormContext();
    }

    this.formOnSave = function () {
        Xrm.Navigation.openAlertDialog({ text: "Record saved." });
    }

    this.ExampleCallAppNotification = function (executionContext) {
        var formContext = executionContext;
        var currentUserID = Xrm.Utility.getGlobalContext().userSettings.userId.replace("{", "").replace("}", "");
        var accountName = formContext.getAttribute("name").getValue();
        var sBody = `Customer [**${accountName}**] has been assigned to you, please contact the customer in time.`;
        var currentRecordId = formContext.data.entity.getId().replace("{", "").replace("}", "");
        var url = `?pagetype=entityrecord&etn=account&id=${currentRecordId}`;
        var tData = {
            "@odata.type": "Microsoft.Dynamics.CRM.expando",
            "actions@odata.type": "#Collection(Microsoft.Dynamics.CRM.expando)",
            "actions": [
                {
                    "title": "Open Account record",
                    "data": {
                        "@odata.type": "#Microsoft.Dynamics.CRM.expando",
                        "type": "url",
                        "url": url,
                        "navigationTarget": "newWindow"
                    }
                }
            ]
        }
        var SendAppNotificationRequest = new this.SendAppNotificationRequest(
            "Client assignment reminders",
            `/systemusers(${currentUserID})`,
            sBody,
            200000000,
            100000001,
            200000000,
            null,
            null,
            tData
        );
        Xrm.WebApi.online.execute(SendAppNotificationRequest).then(function (response) {
            if (response.ok) {
                console.log("Status: %s %s", response.status, response.statusText);

                return response.json();
            }
        })
            .then(function (responseBody) {
                console.log("Response Body: %s", responseBody.NotificationId);
            })
            .catch(function (error) {
                console.log(error.message);
            });
    }

    this.SendAppNotificationRequest = function (
        title,
        recipient,
        body,
        priority,
        iconType,
        toastType,
        expiry,
        overrideContent,
        actions) {
        this.Title = title;
        this.Recipient = recipient;
        this.Body = body;
        this.Priority = priority;
        this.IconType = iconType;
        this.ToastType = toastType;
        this.Expiry = expiry;
        this.OverrideContent = overrideContent;
        this.Actions = actions;
    };

    this.SendAppNotificationRequest.prototype.getMetadata = function () {
        return {
            boundParameter: null,
            parameterTypes: {
                "Title": {
                    "typeName": "Edm.String",
                    "structuralProperty": 1
                },
                "Recipient": {
                    "typeName": "mscrm.systemuser",
                    "structuralProperty": 5
                },
                "Body": {
                    "typeName": "Edm.String",
                    "structuralProperty": 1
                },
                "Priority": {
                    "typeName": "Edm.Int",
                    "structuralProperty": 1
                },
                "IconType": {
                    "typeName": "Edm.Int",
                    "structuralProperty": 1
                },
                "ToastType": {
                    "typeName": "Edm.Int",
                    "structuralProperty": 1
                },
                "Expiry": {
                    "typeName": "Edm.Int",
                    "structuralProperty": 1
                },
                "OverrideContent": {
                    "typeName": "mscrm.expando",
                    "structuralProperty": 5
                },
                "Actions": {
                    "typeName": "mscrm.expando",
                    "structuralProperty": 5
                },
            },
            operationType: 0,
            operationName: "SendAppNotification",
        }
    }
}).call(Gdh.D365.Account);

(2) 代码上传为网络资源

代码上传为网络资源

代码上传为网络资源

(3) 添加按钮

打开 解决方案 –> 选择 Account –> 选择 Commands –> 选择 New command

添加按钮-01

添加按钮-01

点击 “+New”

添加按钮-02

添加按钮-02

填写按钮信息 –> 点击 Save and Publish

  • Label:Example In-App Notification
  • Action:Run JavaScript
  • Library:gdh_/Account/Account.js | Gdh.D365.Account.ExampleCallAppNotification
  • Parameter:PrimaryControl
添加按钮-03

添加按钮-03

(4) 测试

点击 Example In-App Notification 按钮后,你将在右上角看到弹出消息提醒:

悬浮出消息提醒

悬浮出消息提醒

方式3 使用 Csharp

在控制台应用程序中添加如下代码,然后运行。

Example.cs
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Tooling.Connector;
using System;
using System.Configuration;

namespace Blog.D365.Conn.ConsoleApp
{
    public class Program
    {
        static void Main(string[] args)
        {
            string connectionStr = ConfigurationManager.ConnectionStrings["Dev-Office365"].ConnectionString;
            CrmServiceClient client = new CrmServiceClient(connectionStr);
            if (client.IsReady)
            {
                IOrganizationService orgService = client;
                NotificationService notificationService = new NotificationService(orgService);
                notificationService.CreateAppNotification(
                    "Client assignment reminders -- Form Console",
                    new Guid("DDF2C431-A1DF-EE11-904D-0017FA06CFC8"),
                    "Customer [**Bright Design Studio**] has been assigned to you, please contact the customer in time.",
                    new OptionSetValue(100000001),
                    new OptionSetValue(200000000),
                    "?pagetype=entityrecord&etn=account&id=23956352-CEBA-EF11-B8E8-0017FA0527B1",
                    "newWindow"
                );
            }
            else
            {
                throw new Exception(client.LastCrmError);
            }
        }
    }

    public class NotificationService
    {
        private readonly IOrganizationService _orgService;

        public NotificationService(IOrganizationService orgService)
        {
            _orgService = orgService;
        }

        public void CreateAppNotification(
            string title, 
            Guid recipientId, 
            string body, 
            OptionSetValue iconType, 
            OptionSetValue toastType, 
            string url, 
            string navigationTarget, 
            Entity overrideContent = null)
        {
            OrganizationRequest request = new OrganizationRequest
            {
                RequestName = "SendAppNotification",
                Parameters = new ParameterCollection
                {
                    ["Title"] = title,
                    ["Recipient"] = new EntityReference("systemuser", recipientId),
                    ["Body"] = body,
                    ["IconType"] = iconType,
                    ["ToastType"] = toastType,
                    ["Actions"] = new Entity
                    {
                        Attributes = {
                            ["actions"] = new EntityCollection
                            {
                                Entities = {
                                    new Entity
                                    {
                                        Attributes = {
                                            ["title"] = "Open Account record",
                                            ["data"] = new Entity
                                            {
                                                Attributes = {
                                                    ["type"] = "url",
                                                    ["url"] = url,
                                                    ["navigationTarget"] = navigationTarget
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    ["OverrideContent"] = overrideContent,
                }
            };
            _orgService.Execute(request);
        }
    }
}
在控制台运行执行后看到的效果

在控制台运行执行后看到的效果

3.常用属性

(1) ToastType

ToastType描述
Timed200000000消息提醒会短暂出现(默认为 4 秒),然后消失
Hidden200000001消息提醒不会弹出,只有通过点击右上角的 “铃铛(消息中心)” 才能看到

(2) IconType 选项

IconType
Info100000000
Success100000001
Failure100000002
Warning100000003
Mention100000004
Custom100000005
IconType 选项

IconType 选项

(3) 设置通知优先级

通过设置 Priority 来改变通知在通知中心的显示顺序

Priority
Normal 普通的200000000
High 高的200000001
信息
默认值为 Normal 。通知中心中的通知按优先级和创建日期降序排列。高优先级通知显示在通知中心列表顶部。

(4) Notification actions

this.ExampleCallAppNotification = function (executionContext) {
    // ...
    var url = `?pagetype=entityrecord&etn=account&id=${currentRecordId}`;
    var tData = {
        "@odata.type": "Microsoft.Dynamics.CRM.expando",
        "actions@odata.type": "#Collection(Microsoft.Dynamics.CRM.expando)",
        "actions": [
            {
                "title": "Open Account record",
                "data": {
                    "@odata.type": "#Microsoft.Dynamics.CRM.expando",
                    "type": "url",
                    "url": url,
                    "navigationTarget": "newWindow"
                }
            }
        ]
    }
    // ...
}

actions 支持三种操作类型:

Priority
Normal 普通的200000000
High 高的200000001

(5) 定义 URL 动作

URL 操作类型支持从应用通知上的操作导航到定义的 URL。有以下参数:

  • url:选择操作时要打开网址的 URL

  • navigationTarget:控制导航链接打开的位置

    • dialog,Dialog 形式打开
    • inline,默认;在当前页面中打开
    • newWindow,新浏览器选项卡中打开
this.ExampleCallAppNotification = function (executionContext) {
    // ...
    var url = `?pagetype=entityrecord&etn=account&id=${currentRecordId}`;
    var tData = {
        "@odata.type": "Microsoft.Dynamics.CRM.expando",
        "actions@odata.type": "#Collection(Microsoft.Dynamics.CRM.expando)",
        "actions": [
            {
                "title": "Open Account record",
                "data": {
                    "@odata.type": "#Microsoft.Dynamics.CRM.expando",
                    "type": "url",
                    "url": url,
                    "navigationTarget": "newWindow"
                }
            }
        ]
    }
    // ...
}

Title 和 Body 使用 Markdown

提示
Title 和 Body 不可以直接添加样式,需要在 OverrideContent 属性中进行重写(覆盖)Title 和 Body 。
样式Markdown
粗体**Bold**
斜体_Italic_
无序列表- Item 1\r- Item 2\r- Item 3
有序列表1. Green\r2. Orange\r3. Blue
超链接[Title](url)

例如重写消息提醒的 Title ,然后为 Body 中的客户名称添加超链接,还将 “please contact the customer in time” 设置为斜体,修改的代码如下。

  • Client API
this.ExampleCallAppNotification = function (executionContext) {
    var formContext = executionContext;
    var currentUserID = Xrm.Utility.getGlobalContext().userSettings.userId.replace("{", "").replace("}", "");
    var accountName = formContext.getAttribute("name").getValue();
    var sBody = `Customer [**${accountName}**] has been assigned to you, please contact the customer in time.`;
    var currentRecordId = formContext.data.entity.getId().replace("{", "").replace("}", "");
    var url = `?pagetype=entityrecord&etn=account&id=${currentRecordId}`;
    var tData = {
        "@odata.type": "Microsoft.Dynamics.CRM.expando",
        "actions@odata.type": "#Collection(Microsoft.Dynamics.CRM.expando)",
        "actions": [
            {
                "title": "Open Account record",
                "data": {
                    "@odata.type": "#Microsoft.Dynamics.CRM.expando",
                    "type": "url",
                    "url": url,
                    "navigationTarget": "newWindow"
                }
            }
        ]
    };
    var overrideContent = {
        "@odata.type": "#Microsoft.Dynamics.CRM.expando",
        "title": "**(Override)Client assignment reminders**",
        "body": `Customer [${accountName}](${url}) has been assigned to you, _please contact the customer in time_.`
    };
    var SendAppNotificationRequest = new this.SendAppNotificationRequest(
        "Client assignment reminders",
        `/systemusers(${currentUserID})`,
        sBody,
        200000000,
        100000001,
        200000000,
        null,
        overrideContent,
        tData
    );
    Xrm.WebApi.online.execute(SendAppNotificationRequest).then(function (response) {
        if (response.ok) {
            console.log("Status: %s %s", response.status, response.statusText);
            return response.json();
        }
    })
        .then(function (responseBody) {
            console.log("Response Body: %s", responseBody.NotificationId);
        })
        .catch(function (error) {
            console.log(error.message);
        });
};

效果如下:

IconType 重写消息提醒的 Title、Body

IconType 重写消息提醒的 Title、Body

需要注意

1.应用内通知功能,用户需要权限才能接收通知并向自己或其他用户发送通知

表名所需权限备注
Send In-App Notification组织
Notification创建,读取,写入,追加,追加到如果是通过创建 Notification 记录的形式来发送消息通知,才需要配置这张表的权限

2.关于存储。Notification 表使用的是数据库存储容量,需要考虑 “通知量” 和 “设置过期”,建议合理的设置 Expiry

3.通知是特定用户的。每条通知仅针对单个用户,在发送通知时标识为收件人。不支持向团队发送通知,如果需要向多个用户发送通知,则必须为每个用户创建通知。

参考

  1. Notification (appnotification) table/entity reference (Microsoft Dataverse)
  2. Send in-app notifications within model-driven apps