> 技术文档 > 精通微软 Dynamic365(四)_ms365 切换域名设置别名详细步骤最好有视频

精通微软 Dynamic365(四)_ms365 切换域名设置别名详细步骤最好有视频


原文:annas-archive.org/md5/4b7740950c4db25f9fa2b4a0e4722a06

译者:飞龙

协议:CC BY-NC-SA 4.0

第十一章:与 Business Central 的源代码管理和 DevOps

在没有使用源代码管理的情况下开发应用程序,就像开车时没有系安全带。创建一个必须支持多年的应用,并且需要根据新的需求进行扩展和修改,而不知道是谁写了哪些代码以及为何这样写,就像在没有地图和指南针的情况下进入荒野。也许你能勉强度过一天,但再也没有人和你在一起。

通过使用 Visual Studio Code 来开发我们的应用程序,我们拥有所需的所有工具。工具包括Git用于源代码管理,Azure DevOps用于管理应用开发,这将加强开发与运维之间的合作。

在本章中,我们将讨论以下主题:

  • 理解 Azure DevOps 及其提供的功能

  • 在 Azure DevOps 中管理任务、冲刺和看板

  • 为你的代码创建一个代码库

  • 管理仓库

  • 分支策略

  • 分支策略

  • 理解 Git 合并策略

  • 使用 Visual Studio Code 探索 Git

  • 理解 Azure DevOps 管道

  • 理解 YAML 管道

理解 Azure DevOps 及其提供的功能

也许你听过像Team Foundation ServerTeam Foundation ServiceTFSVisual Studio Team Services这样的术语——这些现在统称为Azure DevOps。在 Azure DevOps 中,你可以找到开发团队所需的一切,例如:

  • Azure Pipelines:它为任何语言、平台和云提供 CI/CD。它看起来像这样:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/4c0dc6df-2476-4bf5-a532-a357452a50de.png

  • Azure Boards:这是你可以通过使用看板、待办事项、团队仪表板和报告来跟踪项目活动的区域。它看起来像这样:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/19644b93-c057-463d-94c1-ded0d05af54b.png

  • Azure Artifacts:这是一个用于保存和分发包的工具。它看起来像这样:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/8901dfeb-f1cc-4fb2-9f1c-1f03436a718b.png

  • Azure Repos:这提供云托管的私有和公共 Git 仓库。它看起来像这样:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/126f9e00-6804-4ef0-acd4-8a7693966586.png

  • Azure Test Plans:这提供用于计划和探索性测试的工具。它看起来像这样:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/7124f58a-d242-47d1-862c-876767520493.png

当然,你不需要使用所有可用的工具。你可以从 Azure Repos 开始,然后添加 Azure Pipelines 并将所有这些连接到 Azure Boards。

由于你可以免费获得五个用户,因此使用这些工具没有任何费用。如果你的开发人员有 MSDN 订阅,他们已经包括了 Azure DevOps 的许可证。如果这对你来说还不够,你可以购买额外的许可证(每个用户每月从$6 起—请参见azure.microsoft.com/en-us/pricing/details/devops/)。

如果你的公司域连接到Azure Active DirectoryAAD),你可以通过这些账户和组来管理 Azure DevOps 的访问。这意味着你的用户无需额外的账户即可处理所有相关事务。

创建 Azure DevOps 账户和项目

Azure DevOps中,你可以拥有多个账户。在每个账户下,你可以有多个项目。当你使用 Azure DevOps 创建一个新项目时,生成的项目 URL 会如下所示:

dev.azure.com/myaccount/myproject

让我们从创建一个新的 Azure DevOps 账户开始。你可以选择使用 Microsoft 账户访问 Azure DevOps,或使用公司的 AAD 账户。

如果你使用个人账户创建 Azure DevOps 账户,之后可以将所有权转移到公司账户。

创建新的 Azure DevOps 账户,请按照以下步骤进行:

  1. 访问 go.microsoft.com/fwlink/?LinkId=307137 并使用你的 Microsoft 或 AAD 账户登录。

  2. 阅读并接受《服务条款》、《隐私声明》和《行为准则》。点击继续。

  3. 如果你已经使用你的账户在 Azure DevOps 上工作,你可以通过点击“新建组织”按钮来创建一个新的组织。

  4. 输入你的组织名称并选择托管项目的区域。

  5. 现在,你可以创建你的第一个项目。选择该项目是公开的(任何人都可以访问)还是私有的(仅限你授权的用户访问)。

  6. 你现在是新 Azure DevOps 账户和项目的拥有者,恭喜!以下截图展示了这一点:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/88cfb5c0-5638-49ae-a5e8-10e5142558bf.png

如果你拥有多个 Azure DevOps 账户,你可以在它们之间自由切换,并在其中创建项目,前提是你拥有相应的权限。

默认情况下,新的项目将使用Git 仓库和敏捷工作项流程模板。如果你更倾向于将产品待办事项视为用户故事,将障碍视为问题(以及其他一些差异,详见docs.microsoft.com/en-us/azure/devops/boards/work-items/guidance/choose-process),你可以将流程更改为Scrum

你可以为每个产品/客户创建一个项目,或者你也可以将所有内容放在一个项目中,并使用其他工具按产品/客户将内容分组。这取决于是否有不同的团队在各自的项目上工作,或者你是否在项目之间共享资源。

我建议从一个项目开始,该项目包含一个待办事项列表(队列),用来为团队优先安排工作。如果你有两个队列或两个独立的团队,可以使用不同的项目。

在 Azure DevOps 中管理任务、冲刺和看板

Azure DevOps 是管理 Dynamics 365 Business Central 项目的一个重要工具,从项目的早期阶段开始。通过使用 Boards 功能,你可以开始集中管理项目的任务、功能、缺陷和一般活动。

在你的 Azure DevOps 项目中,如果你点击 Boards,你会看到以下选项:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f8c68a95-9e84-465f-922e-7d7dd8f77087.png

以下是可用选项的简要描述(我们稍后会详细查看):

  • Work Items:在这里,你可以管理你的活动列表(分配给你或你跟踪的活动,或你团队的活动)。

  • Boards:在这里,你可以访问你的看板视图。

  • Backlogs:在这里,你可以访问你的产品待办事项,它是一个项目团队计划开发和交付的工作项列表。

  • Sprints:在这里,你可以管理项目的迭代(在 Scrum 方法论中,冲刺通常定义为一个不超过三周的时间段,其中任务被分组并必须完成)。

  • Queries:这是一个可以设置查询来查找和列出工作项的区域。

作为项目经理,你可以做的第一件事是选择 Backlogs,并为你的项目创建一个产品待办事项(产品待办事项对应你的项目计划——你的团队计划交付的路线图)。

在这里,你可以创建阶段和工作项(任务、缺陷等),并将任务分配给团队的用户。下图显示了这一点:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/86129be1-7047-440e-882d-bfcbdd7e4da1.png

在下图中,我们定义了一些类型为产品待办事项(Product Backlog Item)的工作项。这些工作项对应一组活动。在每个产品待办事项下,我们有相应的任务。每个任务都有自己的状态(待办、进行中或完成),描述,以及一个优先级:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f2a7e0a7-95b9-46ff-a7df-8a8619c51ec5.png

在 Backlog 页面右侧,你有一个 Sprint 面板。

根据 Scrum 方法论,团队以固定的时间间隔计划和跟踪工作,这个时间间隔被称为 冲刺节奏。你可以定义冲刺,以对应你的团队在项目中使用的节奏。

在 Azure DevOps 中,你可以选择一个 冲刺,定义开始和结束日期,然后通过拖放活动将来自 Backlog 的活动分配到特定的冲刺(例如,在上图中,我将“客户类别开发”活动分配到了 Sprint 2)。

在为你的项目安排活动和冲刺后,还有一些其他有趣的视图可供使用。如果你点击 Work Items,你可以查看工作项的状态(例如,分配给你的工作项、所有工作项和最近创建的工作项):

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/efae64a6-c3a1-48c8-b80d-f7b81b2a601e.png

如果你点击 Boards,你可以看到项目的看板(以卡片形式按状态排序的任务视图;你可以拖放任务来更改其状态):

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/3b0c73ab-5195-4a68-a99f-32ad3e2efa9a.png

如果你选择冲刺(Sprints),你可以查看项目中定义的每个冲刺(迭代路径)的详细信息。在这里,你可以查看任务板(Taskboard)视图,并查看冲刺待办事项和容量:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/0fc64d6c-0f37-47fa-a716-87938d2acfcc.png

在这里,你可以监控每个冲刺的进度以及与每个任务相关的活动。

在查询页面,你可以定义自定义查询来检索工作项。在这里,我定义了一个查询,用于立即查看我项目中已声明完成的所有任务:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/a7596d60-4315-4f5f-9dcd-4505404f28b1.png

执行时,查询返回所需的结果(可以以不同的格式查看):

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/603b2c19-214c-42ef-9b15-b1234644edb2.png

另一个有趣的功能是所谓的 Delivery Plans。Delivery Plans 将工作项以卡片形式展示,并配有时间线或日历视图。它非常有用,可以查看团队活动的预期发布或交付日期。

Delivery Plans 不是一个标准功能,要获取它,你需要从市场下载并安装 Azure DevOps 扩展(点击页面右上角的袋子图标)。扩展如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/0d76ccf7-8f5c-4c73-8dd7-225c0fea6de7.png

安装完成后,你会在左侧看到一个名为 Plans 的新菜单,点击它,你可以在时间线上查看你的项目交付计划:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/59b981a6-ebf3-412f-a7ff-8ded0a0051e5.png

最酷的部分是,所有这些项目管理功能都可以在一个工具中使用,并且与日常使用的开发工具(例如 Visual Studio Code)完全集成。在 Visual Studio Code 中,你可以例如提交代码并将该提交关联到分配给你的任务。通过这种方式,你可以拥有完整的产品生命周期,项目经理可以查看为开发或解决特定任务或问题所做的代码修改。

例如,如果我们选择已完成的客户类别开发任务,并点击链接,我们可以看到任务的所有详细信息。特别是,通过历史(History)链接,我们可以查看任务的整个历史:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/0b41e438-b7fb-433f-9cc2-40fd6fb9563e.png

如果我们点击链接,我们可以看到与该任务相关的所有提交。在这里,我们可以看到,对于此任务,我们有三个提交和一个拉取请求:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/fa66cca1-43e4-4913-9544-ca854510d65c.png

如果你选择特定的提交,你可以查看代码修改的详细信息:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f61199ab-c56a-4928-bd3d-e58c76430ff7.png

如你所见,你可以从单一门户和统一界面中完全控制项目的各个方面。

创建代码库

仓库的目的是为了将你的源代码保存在安全、可靠并且在需要时可以访问的地方。Azure DevOps 提供了无限制的仓库。拥有这样一个安全的存储空间,并且不限空间,免费提供,这对于当笔记本被盗或硬件损坏时,可以大大缓解你的焦虑。而且你可以随时随地访问它;不需要 VPN。此外,你还可以轻松地将更改与需求(工作项)连接起来,了解某些操作的原因以及是谁完成的。

创建新仓库,按照以下步骤操作:

  1. 转到 Azure DevOps 的 Repos 部分。

  2. 展开顶部的仓库选择(你可以管理现有仓库,导入仓库或创建新仓库)。

  3. 选择新仓库。

  4. 输入名称。

  5. 点击创建。

不久后,你的新仓库就可以使用了,主页面会提供你所需要的所有信息,帮助你将代码填充到仓库中。以下截图展示了这一点:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/d0a773c5-6c39-4108-a6ea-566773002e17.png

在我们刚创建的仓库主页面上,你可以找到以下功能的链接:

  • 克隆到你的计算机:你可以使用项目的 URL 和 Git 来克隆仓库,或者点击 VS Code 中的 Clone 按钮来打开 Visual Studio Code(或其他支持的开发工具),选择目标文件夹,然后让 Visual Studio Code 将仓库克隆到本地磁盘。然后,你可以根据需要将源代码填充进去。

  • 从命令行推送现有仓库:复制命令,转到你的本地仓库并运行这些命令,你的本地仓库将与这个新的 Azure DevOps 仓库连接,并将推送到其中。

  • 导入仓库:如果你在其他地方有一个 Git 仓库,想要将其导入,只需输入 URL,当前状态将被导入。

  • 使用 READMEgitignore 初始化:如果你愿意,可以只创建一个包含项目描述的 README 文件,或者仅创建一个 .gitignore 文件,稍后再向仓库中添加其他内容。

接下来,我们来看一下如何管理仓库。

管理仓库

在你创建的每个仓库中,你可以设置多个设置。我们将介绍其中最有趣的设置。你可以通过点击项目设置 | Repos | 仓库来管理仓库:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/6382c3b3-9f58-4a62-9003-3a4764483e66.png

在仓库的设置页面中,我们可以管理主要设置,我们将在接下来的章节中查看这些设置。

安全

你可以为所有仓库分配组并设置权限,也可以根据需要为每个仓库、每个分支或标签进行细化设置。我建议检查哪些用户可以执行以下操作:

  • 删除仓库

  • 强制推送(这可能会重写仓库中的历史记录)

  • 创建仓库

  • 跳过策略

如果你设置了正确的配置,可以防止代码丢失。

选项

在所有 Git 仓库和每个单独的仓库中,你可以设置以下内容:

  • 跨平台兼容性:这确保设置能够防止因文件/文件夹名称仅因大小写不同而导致的问题。Git 是区分大小写的,允许开发者将 File.txtfile.txt 作为两个独立的文件添加。但 Windows 和 iOS 不区分大小写,因此在这方面会出现问题。最佳实践是命名保持一致,避免创建此类文件。启用仓库中的选项将强制开发者保持名称唯一。因为即使标签和分支在 Git 内部也是文件,所以标签和分支名称也可能存在冲突。

  • Fork:如果您不希望允许用户从仓库创建 Fork,您可以禁用此功能。Fork 是仓库的副本,并保持与原始仓库的连接。开发者可以使用 Fork 创建拉取请求,将更改从一个仓库传输到另一个仓库。Git 是一个分布式系统,一个仓库可以存在于多个地方(多个服务器或同一服务器上的多个仓库——都可以)。

  • 工作项管理(每个仓库):保持此设置开启,以便将提交与现有工作项连接。通过这种方式,您将获得每个需求所做的工作的信息,甚至可以知道哪些更改是每个构建或发布版本的一部分。这有助于为每个版本创建变更日志。

  • 代码搜索分支(每个仓库):您可以为每个仓库选择最多五个分支进行代码搜索索引。这些分支中的文件将可以通过 Azure DevOps 的搜索功能进行搜索。只需输入您想搜索的文本,Azure DevOps 将在所有仓库中快速找到它。这是一个非常方便的工具。

  • 分支策略(每个分支):请参见Branching policies部分。

我们将在下一节中查看这些分支策略。

分支策略

为了保持应用程序的质量,您可以定义一些必须满足的政策,才能将开发者的更改合并到选定的分支中。

通常,政策是在master分支上定义的,但它也可以是您希望保持健康的任何分支。如果您为某个分支定义了策略,则不能直接将更改推送到该分支,只能通过 Pull Request(PR)进行。有关更多详细信息,请参见Pull request部分。通过这种方式,每个更改都会被检查和测试,如果不符合政策,该更改将无法进入分支。您可以在策略中定义以下内容:

  • 最少审阅者数量:这是指必须有多少位审阅者批准 PR 才能被接受。

  • 检查关联的工作项:这强制开发者将 PR 与工作项关联,以建立需求和更改之间的链接。

  • 检查评论是否解决:如果审阅者写了评论,则必须在接受 PR 之前解决这些评论。

  • 强制合并策略:你可以禁止快速前进合并(虽然失去了一些细节,但得到了简化),或者强制使用压缩合并,这将把开发者的所有提交合并为目标分支上的一个新提交。

  • 构建验证:你可以定义一个构建管道,用来构建和测试更改。如果构建成功,PR 可以被接受。构建失败可能会阻止 PR 被接受(可选行为)。通过这种方式,你可以保持分支的健康状态。

  • 自动包括代码审查员:这定义了将默认作为审查员的用户组或用户。审查员可以根据特定的更改来选择,例如,当脚本文件或应用程序的设置发生更改时,负责人将被自动添加为审查员。

一些开发者倾向于将分支策略视为增加的障碍,但这是一个很好的机会,可以通过代码审查提升团队成长和质量。它为团队提供了相互学习的机会,并帮助彼此传授新知识。额外的代码审查人员总是有益的。

分支策略

我们已经为分支设置了策略,以便进行规则和技术检查,但问题是,如何在 Git 中使用分支来支持你的工作?你应该使用哪种策略?什么时候应该创建新分支?什么时候应该合并分支?

你可以使用许多策略,而且没有万能的解决方案。适合你团队的最佳策略可能对另一个团队来说并不合适,或者你可能会为你的 AppSource 应用程序使用一种策略,为你的 PerTenant 应用程序使用另一种策略。

在你决定采用哪种方式之前,考虑一下KISS 原则Keep It Simple, Stupid)。

在本章的所有示例中,我们将主分支视为最稳定的分支,它代表着已发布到生产环境的应用程序。你可以决定将该分支命名为其他名称,这不会影响策略本身。你只需要定义名称并确保全团队保持一致。接下来,我们将讨论各类分支。

仅有一个主分支

拥有一个分支是你可以使用的最简单策略。如果只有一个开发者在开发应用程序,就不需要创建分支。即使有更多的开发者,他们也可以继续在一个分支上工作,每次出现冲突时将更改合并到该分支中。但由于未完成的更改可能被推送到分支中,这会使产品很难保持稳定:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/43a2e748-3e32-44c1-aa9f-6e2fbc307f27.png

为了稳定应用程序,你需要暂停开发。不过,当你看到需要时,仍然可以稍后将策略更改为其他方案。

功能/开发分支

为了将一个功能或者一个开发者的变更隔离开来,你可以为每个功能或者开发者创建一个分支。这样,开发者们就可以在各自的分支上进行工作,直到完成工作并将变更集成回主分支之前,不会与其他人的工作产生冲突。

当功能完成并集成回主分支后,功能分支可以被删除。如果分支是按开发者分的,那么可以预期分支将长时间存在。从长远来看,这可能会成为一个问题。以下图表描述了这一点:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/02a47d18-3809-46fb-817d-b3e2ffa3cd49.png

每个开发者使用一个分支会将不同功能的不同变更混合在一起,以后选择性地释放选定的功能可能会成为问题。

使用功能分支可以让你有可能仅发布选定的功能或在流程后期取消功能开发而无需付出成本。

发布分支

下一个策略是为你正在准备的每个发布创建一个分支。它使你能够在发布之前稳定产品并将其与正在进行的开发隔离开来:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/0789341c-9221-44af-8b5a-c0b63ae86a39.png

正如我们在前面的图表中所看到的,它非常适合适用于 AppSource 的应用程序,因为你可以根据 AppSource 验证程序修复问题,而应用程序不会受到在此期间进行的新开发的影响。修复可以随时集成回主分支。

其他策略

即使为了服务创建一个新的分支(例如,通过另一个团队完成已发布版本的支持并长期支持时,例如创建服务包),或者为了热修复而创建热修复分支时,也可以创建一个新分支并将其与发布和开发分离开来。

如你所见,分支的唯一目的是为了将其变更与其他为不同目的进行的变更隔离开来,以防需要长时间保持隔离状态。

每种策略都可以与其他策略结合使用,这样你可以根据自己的需求创建自己的策略。其中一种组合被命名为Git flow,我们将在接下来探讨它。

Git flow

Git flow 是一种工作流程,结合了特性分支和 bug 和发布分支。它被广泛使用,你甚至可以找到支持此流程的工具,通过自动化不同部分来支持它。在 Git flow 中,主分支代表已发布的版本,即其中的每个提交代表产品的一个发布版本。

第二个分支是开发分支,用作从中创建每个功能分支的集成分支。

当准备发布新版本时,首先从开发分支创建一个新的发布分支。在发布分支上进行稳定性和微调,直到版本准备好发布。发布通过将发布分支与主分支合并来完成。

如果发布的版本有 bug,可以从主分支创建一个新的 bug 修复分支。所有修复都在这个分支上进行,然后将其合并回主分支(创建一个新的修复版本的应用)并合并到开发分支(以便在下一个发布版本中保留 bug 修复)。

以下图示展示了这一点:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/31223925-853c-4063-bd72-d21e5f33da78.png

这种流程适用于开发 AppSource 应用,因为发布可以被隔离,并且你可以轻松地支持多个版本的应用。

GitHub flow

GitHub flow 是一种用于 GitHub 上开发的工作流。它基于两个规则:

  • 主分支中的一切都可以随时发布。

  • 发布可以随时进行,甚至一天多次。

它本质上是特性分支。修复 bug 的方式与开发其他任何特性相同:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f0b511d3-3aff-40bc-8e02-0a6e3e287d87.png

正如我们在前面的图示中看到的,它需要产品的自动化发布,因此可能仅适用于 Business Central 中的 PerTenant 或 OnPrem 应用。AppSource 的发布周期较长,因此此流程不适合它。它甚至假设只有一个版本的产品会发布,但对于 AppSource 来说,这并不真实。

分支考虑因素

在公司中可以使用多种分支策略,因为每种策略适用于不同的情况。但不要忘记 KISS(保持简单!)。采用复杂的策略而对团队没有任何帮助,只会导致捷径和团队不遵守规则。采用单一分支也是一种策略。可以从一个分支开始,根据需要再添加其他分支。随着团队的成长,策略也可以不断发展。

对于 AppSource 开发,你可以使用任何策略,但最适合的策略是 Git flow,因为它允许你分离每个发布并支持多个版本。不要忘记,客户租户上的应用只有在进行重大版本发布或因出现关键 bug 时(这是在合作伙伴请求之后)才会自动更新。这意味着多个版本可以一起在云中共存。

对于 PerTenant 应用开发,因为在这种情况下,你只会向客户租户发布一个版本的产品,所以你可以使用任何策略,包括 GitHub flow。

理解 Git 合并策略

我们不会深入探讨 git merge 命令的所有可能性,但我们会解释一些与 Git 和合并相关的术语。

快进合并

当你在 Git 中合并两个分支,其中一个分支是另一个分支提交的子集时,结果将是快进合并,此时根本不会进行合并。请参考以下图示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/1a9295f0-78d7-45af-8afc-c27bf79fe0e4.png

分支将重置到一个新的位置。

Squash 提交

使用 squash commit 可以帮助你保持分支的简洁。当你想要将一个分支合并到另一个分支时,使用 squash commit 可以将分支中的提交合并成一个新的提交,并附上新的提交信息,并将这个新的提交连接到目标分支。下图展示了这一过程:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/7d852665-29fe-4e46-a8e0-9be0ffb4eff6.png

分支之间不会有真正的合并。你只需丢弃旧分支,因为所有的更改现在都已经提交到目标分支。你会失去一些细节,但获得了简洁性。这取决于你的优先级是什么。

Rebase

你可以使用rebase命令代替merge。顾名思义,你将分支从树上剪切下来,并将其重新基于另一个提交。通过这种方式,你可以在不进行合并的情况下将更改基于新版本。下图展示了这一过程:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/7094ca8b-0b60-422f-a36d-122fbcb520fb.png

所有从原始基点到分支头(分支的起始点)之间的提交都会被获取并重新应用到新的基点上。同样,你获得了简洁性,但失去了现实,因为你在影响历史。

Git 合并的考虑事项

当你需要将更改合并回目标分支时,你可以选择使用快速前进合并、常规合并或 rebase,或者使用 squash 提交。通过结合这些技巧,你可以在 Git 中保持简单的历史记录,但可能会丢失一些必要的细节。但再次强调,不要害怕做出选择。只需从最简单的方式开始,如果你觉得这会有所帮助,可以以后再修改规则。

接下来,我们继续看看如何在 Visual Studio Code 中使用 Git。

使用 Visual Studio Code 探索 Git

Git 是 Visual Studio Code 的默认版本控制系统,你可以通过 Visual Studio Code 的 GUI 执行基本的 Git 操作(如 push、pull、fetch 和 clone)。你还可以通过安装扩展来丰富集成体验。

我推荐这些扩展:

  • Azure Repos:连接到 Azure DevOps 仓库,包括工作项和构建流水线

  • GitLens:为 Git 历史记录添加不同的视图,如 blame 功能

让我们来看看 Git 能为我们提供什么。

Visual Studio Code 中的 Git GUI

Visual Studio Code 提供了一个与 Git 和版本控制管理系统(SCM)完全集成的体验。以下是 Visual Studio Code 的界面:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/3c2eaefc-b74d-4d66-998f-5f162ce5d1ff.png

截图左侧的数字表示以下详细信息:

  1. 当前分支:点击此处,你可以创建一个新分支或切换到另一个现有分支。

  2. 仓库状态:我们与远程仓库保持同步。如果不同步,你可以看到当前分支的进出提交数量。点击它们时,你将执行同步操作,即从远程仓库拉取和合并。

  3. 在 Azure DevOps 中的项目名称(Azure Repo 扩展):点击此项将打开 Azure DevOps 门户。

  4. 拉取请求的数量(Azure Repo 扩展):点击此项可以选择并浏览拉取请求。

  5. 上次构建状态(Azure Repo 扩展):点击此项可以查看仓库的最后一次构建。

  6. 工作项的数量(Azure Repo 扩展):点击此项可以浏览工作项并在 Web 门户中打开它们。

  7. 源代码控制活动栏:你可以看到更改文件的数量。你可以切换到源代码控制活动,在哪里你可以提交这些更改。

  8. 提交消息文本框:在提交更改之前,输入提交信息。

  9. 更改列表:你可以选择你希望撤销或暂存以便提交的更改。如果你双击此项,你可以打开差异窗口,查看当前状态与最后一次提交状态之间的差异。

  10. 源代码控制菜单:你将在这里找到更多关于源代码控制的命令。

完成这些之后,我们来探索 Git/Visual Studio Code 的工作流。

Git 工作流

当你在 Visual Studio Code 和 Git 中工作时,以下是你常见的工作流:

  1. 第一步是将你想要工作的仓库获取到本地系统中。

  2. 如果你想处理现有的代码,你需要获取远程仓库的 URL。然后,你可以使用 Git: Clone 命令,输入 URL,并选择仓库克隆到的文件夹(它会被克隆到一个以仓库名命名的子文件夹中)。

  3. 如果你正在创建一个新的应用程序,你可以先创建文件夹,打开它并在 Visual Studio Code 中创建基本结构(使用 AL: GO! 或其他命令),然后使用 Git: Initialize Repository 将文件夹变成一个 Git 仓库。之后,你可以通过命令提示符将本地仓库连接到新的远程仓库。请参见 如何为你的代码创建仓库 部分。

  4. 检出现有分支或创建一个你想进行开发的新分支。你可以通过点击底部的分支按钮来完成此操作。别忘了检查你正在工作的是正确的分支。

  5. 在你进行了一些更改后,转到源代码管理活动栏(按 Ctrl + Shift + G 打开 Git),写下有意义的消息(例如 My first commit),然后提交更改(点击消息上的勾号或按 Ctrl + Enter)。如果你还没有暂存一些更改(即在更改列表中选择已更改的文件并将其移动到暂存区),Visual Studio Code 会询问你是否想提交所有更改。我建议你逐一查看更改并手动检查和暂存它们,因为修复已经提交的内容并不简单或方便。通过暂存更改,你可以选择所有修改中的一部分进行提交。你甚至可以在行级别上进行暂存/取消暂存,方法是打开差异窗口,右键点击行并选择“暂存/取消暂存选中的范围”。通过这种方式,你可以将更改拆分为独立的提交,例如,如果它们与不同的需求相关。

  6. 如果你想撤销更改,只需点击已更改行中的“放弃更改”。

  7. 提交更改后,如果你想让其他人能够使用这些更改,你可以点击窗口底部的同步按钮,它将把提交推送到远程仓库(如果有更改,它还会从远程仓库拉取更改)。这就是按钮的样子:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/599ef93d-f14b-4edd-a9aa-0cef589b2d06.png

  1. 如果你想将更改合并到开发分支(或任何你不负责的其他分支),请在 Azure DevOps 门户中创建一个拉取请求。你可以通过点击状态栏中的“拉取请求”按钮(在Visual Studio Code Git 图形界面部分中的第 4 项),然后选择“浏览你的拉取请求”选项进入。如果你需要修复某些问题,例如在拉取请求期间发生冲突,只需进行修改、提交并推送,拉取请求将会自动更新为新的提交。

  2. 如果所有内容都在远程仓库中,并且更改已经合并,你可以在不再需要该文件夹时直接删除磁盘上的该文件夹。

制定关于提交信息格式的规则是一种良好的实践,以确保公司内部的一致性。掌握写好提交信息的技能应该是每个开发者持续改进的一部分。你可以通过写 #1234 来引用工作项,其中 1234 是工作项的 ID。你可以在网上找到一些示例和规则,了解如何写出好的 Git 提交信息。以下是一个示例:

Fix error \"Value is incorrect\" in Sales postingError text was changed to give more context to user andin some cases, solved by finding correct value automatically.Fix bug #1234Related to #1258

你可以通过点击“创建新分支”直接从 Azure DevOps 中的工作项创建一个新分支:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/683cb346-cd35-4fb0-b3a8-02857d2a12cb.png

这样,分支将与工作项关联,大家都知道更改是在哪里开发的。当你使用特性分支策略时,这种做法是非常好的。

合并

在某些情况下,你需要将远程仓库中的更改与本地更改合并。在这种情况下,同步仓库后,你将在源代码控制部分看到一个名为 MERGE CHANGES 的新部分(在正常开发工作中会有 CHANGES 和 STAGED CHANGES 部分)。

当你点击每一行/文件时,Visual Studio Code 会打开一个编辑窗口,显示更改,你可以接受这些更改或手动修正它们。所有冲突解决后,将更改暂存并提交为新的合并提交,并将更改同步(推送)到远程仓库。

学习了 Git 之后,让我们来看一下 Azure DevOps Pipelines 是如何工作的。

理解 Azure DevOps Pipelines

由于业务案例应用程序的生命周期较短,并且你发布新版本的频率应该比过去(AL 时代之前)要高得多,你不能手动构建、测试和部署应用程序。

为了自动化生命周期的这一部分,你可以使用 Azure DevOps Pipelines,它将为你构建、测试和部署。你将生成的源代码作为输入提供给管道,而在管道的另一端,你将获得一个经过测试的应用程序,甚至可以自动交付或部署。现在,管道有两种类型:

  • 构建管道:输入是源代码,输出是应用程序和其他工件。

  • 发布管道:输入是构建管道生成的输出,输出是经过测试的应用程序,已交付或部署到选定位置。

计划是将会有一个多阶段管道,覆盖整个过程。请参考以下图表:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/d881b4bc-688a-4335-89dd-6a1332749368.png

在构建阶段,你将与源代码进行工作,生成产品/应用并进行测试。通常,运行应用程序的测试不需要将应用程序部署到某个地方。对于 Dynamics 365 Business Central,它有所不同,你需要将应用部署到服务器上才能进行测试——是否使用容器并不重要。

在发布管道中,你尝试将应用程序交付或部署到不同的环境中(当前版本、下一个版本、不同的本地化、新环境,或者是包含先前版本的环境以测试数据升级等),在该环境中进行测试,并执行交付/部署应用程序所需的其他步骤。这使得你可以随时以尽可能少的人工干预交付或部署应用程序。

你在提交或拉取请求中添加的与工作项相关的所有内容都会通过管道传输,并且在每次发布时,你都能看到与该发布相关的所有工作项。这帮助你识别和描述属于应用特定版本的更改,并且这个列表可以随应用程序一起自动交付。

代理

你创建的管道必须在某个地方执行。执行由名为代理的应用程序完成。

你可以使用托管代理,这些代理由微软在 Azure 上维护,并运行在不同操作系统上,安装了不同的附加软件(如 macOS、Ubuntu 和带有 Visual Studio 2019 的 Windows 2019)。对于这些托管代理,你可以在 Azure DevOps 组织中使用一些免费的分钟数(你可以在“组织设置”的“计费”部分查看使用的分钟数)。但是,由于不能安装附加软件,使用这些托管代理是有限制的。

如果你愿意,也可以使用自己的代理。这意味着你可以在自己的服务器上安装并配置一个小应用程序,该应用程序通过 RESTful API 连接到 Azure DevOps,并在你的服务器上执行管道中的任务。如何安装代理的详细信息,在点击“下载代理”按钮时,会在设置中的“代理池”部分显示,如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/395ca764-7b4d-45d7-b89a-c71ad7eb8a81.png

关于如何配置代理并获取认证代理所需的访问令牌的详细信息,可以在下载代理页面点击“详细说明”找到 (go.microsoft.com/fwlink/?LinkID=825113)。不要忘记以管理员身份运行代理,这样才能在执行 Dynamics 365 Business Central 任务时做必要的操作。

用于执行管道的代理由代理的能力(你可以在“代理池”部分设置)和所需的代理任务能力(你可以稍后在管道定义中的代理任务上设置)决定。这意味着,如果有多个具有相同能力的代理可用,每次运行管道时都可以由不同的代理处理。接下来,让我们看看如何创建构建管道。

创建构建管道

要创建你的第一个构建管道,请打开 Azure DevOps 门户中的管道部分,然后点击“新建管道”:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/86796189-bebe-4763-bca1-57eccb12279a.png

现在,你可以选择要构建的代码源。首先,我们将使用经典编辑器,这意味着我们将手动创建管道,只是为了查看管道设置的不同部分。稍后,我们将使用Azure Repos Git从 YAML 文件一步创建整个管道。

点击“使用经典编辑器”后,你可以选择代码的来源(选择 Azure Repos Git | 团队项目,然后选择源代码的存储库和该存储库的分支)。选择正确的值后点击“继续”。

因为没有预定义的 Dynamics 365 Business Central 管道模板,我们需要从空工作开始:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/85b9eaa7-02fb-416f-9a6e-8bd8afb3dcb7.png

现在您已经进入了管道编辑器,您可以添加管道代理作业,这些作业代表了您需要执行的构建和测试应用程序的管道步骤。在右侧,您可以看到您在左侧选择的实际步骤的参数。

在参数中,您可以使用变量,这些变量可以在“变量”标签中定义,也可以由系统定义。您可以在 Azure DevOps 文档中找到有关变量的详细解释。

要在任务的参数中使用变量,请使用 $(variablename) 语法。在 PowerShell 脚本中使用时,请使用 $env:VARIABLENAME 语法。所有可作为环境变量访问的变量都是大写的,且点被替换为下划线。

注意查看“查看 YAML”按钮。当我们研究 YAML 构建管道时,它会很有用。

在为 Business Central 构建应用程序时,您通常会使用可以运行现有脚本(例如,如果它们是源代码的一部分)或运行内联定义脚本的 PowerShell 任务。

在定义了所有所需任务后,您可以为新的构建管道定义触发器。您可以选择以下几种方式:

  • 持续集成:每当新的提交被推送到服务器时,这将会运行。检查分支过滤器,只为您希望节省计算时间的分支运行构建。您甚至可以为仓库中的路径指定过滤器,只有当路径发生变化时,才会触发管道(例如,当readme.md被修改时不触发)。

  • 定时:管道将在设定的时间自动触发。您可以选择仅在上次有变化时触发它。

  • 构建完成:当另一个管道完成时,会触发该管道。这在您有应用程序之间的依赖关系时非常有用,可以在依赖项构建完成后,触发应用程序的构建。

当一切准备就绪后,保存管道并尝试运行它。在大多数情况下,您需要多次运行和修改,才能获得第一次成功的构建。

常见的构建任务包括以下步骤:

  1. 准备构建环境(安装脚本、下载工具、创建 Docker 容器等)。

  2. 编译应用程序(下载符号,使用 ALC.exe 编译应用程序等)。

  3. 安装应用程序(将其发布并安装到 Docker 容器中)。

  4. 运行应用程序的测试并下载结果。

  5. 发布测试结果(即使测试失败,也应该执行此操作)。

  6. 发布构件(将应用程序推送到 Azure DevOps 商店或共享文件夹)。

  7. 清理环境(例如,删除 Docker 容器;无论管道中是否存在失败的步骤,都应该进行此操作)。

变量组和安全文件

在创建管道时,您主要需要定义在构建之间共享的值(例如用户名、密码、密钥等)。为此,您可以创建变量组。要定义新的变量组,请打开库:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/b0d8ecaa-8bb6-45a6-8c71-a673d91d5326.png

在创建变量组时,定义名称和描述。为了更好的安全性,你可以选择将变量存储在 Azure 密钥库中,或者仅创建变量名称/值对,并在密码字段上使用锁图标来隐藏值。此外,你还可以为每个变量组定义安全性。

要能够使用变量组中的变量,你需要将该变量组链接到你的流水线。只需打开编辑器,转到“变量”标签,选择变量组,然后使用“链接变量组”按钮。变量组链接后,你就可以在任务中使用这些变量。

如果你在构建流水线中需要使用证书或其他文件,你可以将其存储为库中的安全文件。你可以通过流水线中的“下载安全文件”任务来下载这个文件。这样,用户不需要访问该文件,并且该文件也不需要能够访问网络资源。该过程将从 Azure DevOps 存储中下载该文件,在那里它是被保护的。

接下来,我们来看看什么是 YAML 流水线。

理解 YAML 流水线

在上一节中,我们使用经典编辑器创建了一个流水线,向你展示流水线的不同部分,并让你对流水线有一个初步了解。但在编辑器中创建流水线并不方便,而且你无法对定义进行版本控制。这就是YAML 流水线的存在意义。它们与经典流水线有相同的属性和部分,但它们是通过 YAML 文件定义的,作为源代码的一部分。这意味着你可以将流水线定义为代码(你可以直接编写流水线代码),并且可以使用你正在使用的所有工具来处理代码。

首先,下面是一些关于YAML的信息。YAML 是一种类似于 XML 和 JSON 的文件语法,但它主要是为了让人类阅读(而 XML 和 JSON 是为了计算机读取)。这意味着 YAML 的语法非常易于理解。它不像 XML 和 JSON 那样使用人工标记来传达意义,而是使用缩进和一些符号,比如-来表示列表项,:用来分隔名称和值:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/1232ddc2-0626-4411-bbda-f69d07ae40f3.png

如果你查看示例,你应该能够识别出属性(格式为name: value)、包含属性的对象(例如前面截图中的customer)以及带有对象的列表(例如前面截图中的items)。

通过使用 YAML,你可以定义流水线编辑器中看到的所有部分:

  • 带有参数和属性的作业和任务

  • 变量,包括变量组

  • 触发器

如果你在编辑器中创建了某些流水线,你可以使用编辑器中的“查看 YAML”按钮来查看定义相同内容的 YAML 文件。这样,你就可以开始创建你的 YAML 流水线。只需在你的项目中创建一个azure-pipelines.yaml文件,将流水线描述写入其中,然后将该文件提交到你的代码仓库中。

当您想要更改管道中的内容时,只需更改 YAML 文件,提交并推送。管道将自动更改。

创建 YAML 管道

若要基于 YAML 文件创建管道,请转到 Pipelines | Builds 部分并执行以下操作:

  1. 点击“New Pipeline”。

  2. 选择 Azure Repos Git(YAML)。

  3. 选择仓库。

  4. Azure DevOps 将自动检测仓库中的 YAML 管道文件并打开它。

  5. 点击“Run”。

完成了。您的新管道已创建!是不是很简单?

YAML 管道模板

为了使其更加通用,您可以在 YAML 管道中使用模板。这意味着您将每个任务的 YAML 文件定义作为单独的文件存储在仓库中,并且可以从 YAML 管道中引用这些模板。这些定义与所有在其 YAML 管道中使用它们的应用程序共享,如果您需要修复某些内容,您只需在一个地方进行修复。当然,这样做的结果是,您可能会从一个地方影响到所有管道。请注意!

这是创建管道的方法:

  1. 为您的模板创建一个新的仓库。

  2. 将包含任务定义的 YAML 文件放入其中:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/cf999ca4-f727-4216-8a41-8a8cda080e61.png

  1. 将仓库引用添加到 YAML 管道文件中:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/66d66345-fecc-46ad-8554-8b999bc4eca1.png

引用参数如下:

    • Repository:在 YAML 管道文件中使用的名称

    • Type:源仓库类型

    • Name:仓库名称

    • Ref:要使用的模板版本的分支或引用

    • Endpoint:在 Azure DevOps Service connections 部分中定义的端点名称

  1. 在 Azure DevOps Service connections 中添加与您的仓库同名的服务连接:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/5d5dae3c-a9d6-4dba-be2a-de857338314a.png

  1. 更改 YAML 文件以将这些模板作为步骤引用:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/2ff68881-0438-42ca-87ee-4b4c4131a258.png

在 YAML 文件中,我们有这些引用:

  • Template@MSDYN365BC_Yaml仓库中的文件路径和名称

  • Parameters:任务的参数值

所有特定于仓库的设置应保留在您的 YAML 管道文件中。所有共享的内容,如步骤定义,应放在模板仓库中。

您可以在github.com/kine/MSDYN365BC_Yaml找到模板和管道文件的示例。

要使用此模板交换新应用程序,您可以将应用程序克隆为模板,网址为github.com/kine/MSDyn365BC_AppTemplate

仅供将来参考,作为 Dynamics 365 Business Central 的通用 YAML 管道,您还可以参考以下 YAML 定义:

variables: build.clean: all platform: x64trigger: nonesteps:- task: PowerShell@2 displayName: \'Install NAVContainerHelper module\' inputs: targetType: filePath filePath: \'BuildScripts\\InstallNAVContainerHelper.ps1\'- task: PowerShell@2 displayName: \'Create a Docker Container for the build\' inputs: targetType: filePath filePath: \'BuildScripts\\CreateDockerContainer.ps1\' arguments: \'-credential ([PSCredential]::new(\"$(DockerContainerUsername)\", (ConvertTo-SecureString -String \"$(DockerContainerPassword)\" -AsPlainText -Force)))\'- task: PowerShell@2 displayName: \'Copy Files to Docker Container\' inputs: targetType: filePath filePath: \'BuildScripts\\CopyFilesToDockerContainer.ps1\'- task: PowerShell@2 displayName: \'Compile extension stored in the repository\' inputs: targetType: filePath filePath: \'BuildScripts\\CompileApp.ps1\' arguments: \'-Credential ([PSCredential]::new(\"$(DockerContainerUsername)\", (ConvertTo-SecureString -String \"$(DockerContainerPassword)\" -AsPlainText -Force))) -BuildFolder \"$(Build.Repository.LocalPath)\" -BuildArtifactFolder \"$(Build.ArtifactStagingDirectory)\"\' failOnStderr: true- task: PowerShell@2 displayName: \'Publish extension\' inputs: targetType: filePath filePath: \'BuildScripts\\PublishApp.ps1\' arguments: \'-Credential ([PSCredential]::new(\"$(DockerContainerUsername)\", (ConvertTo-SecureString -String \"$(DockerContainerPassword)\" -AsPlainText -Force))) -BuildArtifactFolder \"$(Build.ArtifactStagingDirectory)\"\' failOnStderr: true- task: PublishBuildArtifacts@1 displayName: \'Publish Artifacts\' inputs: PathtoPublish: \'$(Build.ArtifactStagingDirectory)\' ArtifactName: FinalApp

该管道模型使用一组 PowerShell 脚本,您可以将其存储在名为BuildScripts的文件夹中。它可以与您的扩展文件一起存储,如下图所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/e2ec1949-4356-4c78-9755-8f44a207ae71.png

如果成功执行,此管道将发布你的 Dynamics 365 Business Central 扩展的最终 .app 文件作为工件(管道输出),你可以从构建摘要页面下载。

发布管道

构建管道完成后,你可以使用发布管道来交付或部署构建工件,或者执行你想做的其他操作。要创建一个新的发布管道,请进入发布部分并点击新建发布管道。由于没有 Business Central 发布管道的模板,请从空作业开始。

每个发布管道的创建包括以下内容:

  • 工件:这可以是构建管道的输出,Azure DevOps Git 仓库,GitHub 仓库,TFVC 仓库,Azure 工件,Azure 容器,Docker Hub 仓库,或 Jenkins 作业。

  • 阶段:每个阶段是一个独立的过程,可以在不同的代理上执行,并且可以由不同的事件触发。

  • 变量:这些与构建管道中的变量相同。

对于每个工件,你可以定义触发器来启动管道。可以是每次工件更新时(持续部署),或者按照给定的时间表(例如每晚发布)。

在每个阶段,你可以设置预部署条件和后部署条件:

  • 预部署条件:这些包括以下内容:

    • 发布后:当选定的工件被部署或按照给定的时间表时,会触发此操作。

    • 阶段结束后:当另一个阶段完成时会触发此操作。

    • 仅手动:必须有人在门户中触发部署。

    • 预部署审批:选定的用户必须批准部署到此阶段。

    • 闸门:这些是可以根据某些条件批准部署的自动化过程(例如,在发布到上一个阶段后没有错误时)。

  • 后部署条件:这些包括以下内容:

    • 后部署审批:选定的用户必须批准发布阶段成功,并且发布可以继续。

    • 闸门:自动化过程可以批准阶段发布。

    • 自动重新部署触发器:你可以在需要时触发重新部署;例如,在阶段失败后,你可以重新部署上次成功的部署。这对于恢复到最后一个已知的工作版本非常有用。

以下是 Dynamics 365 Business Central 的发布管道示例:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/2c11b674-a11b-4702-bf75-4c4275ed8a3e.png

每个阶段将应用程序(并运行测试)部署到 Business Central 沙盒的不同版本(当前版本、未来版本和主版本)。如果一切正常,应用程序将部署到 QA 环境进行用户测试。如果测试成功,应用程序将由证书签名并存储在服务器上以供以后使用(发送到 AppSource)或部署到目标环境(每租户应用)。这就是 YAML 管道的意义所在。

概述

在本章中,我们了解了 Azure DevOps 的概念以及它所提供的功能,然后在 Azure DevOps 上创建了我们的账户。我们查看了如何使用 Azure DevOps 来管理和规划我们的工作。我们为我们的代码创建了一个仓库,并学习了可以设置哪些内容来支持我们的开发周期。

分支策略部分,我们学习了如何在项目中使用分支来保持开发的稳定性和可追溯性。在Git 合并策略在 Visual Studio Code 中使用 Git部分,我们查看了一些与 Git 源代码控制相关的特定方面,并了解了如何通过 Visual Studio Code 使用 Git SCM 来确保我们的代码安全。

我们学习了 Azure DevOps Pipelines,如何使用它们以及如何通过经典设计器创建它们。在上一节中,我们查看了 YAML 文件,并了解了如何使用它们将管道定义为我们代码的一部分。

在下一章中,我们将深入探讨 Dynamics 365 Business Central APIs,并探索如何创建新的 API 以及如何使用现有的 API 来执行集成操作。

第四部分:与 Dynamics 365 Business Central 的高级集成

本节将介绍 Dynamics 365 Business Central API 框架。我们将描述如何通过使用 Dynamics 365 Business Central 和不同的 Azure 服务来创建真实的业务流程。我们还将涵盖 Dynamics 365 Business Central 与 Dynamics 365 Power Platform 结合使用的方式。最后,我们将使用 Power Platform 和 Dynamics 365 Business Central 构建一个真实世界的应用程序。

本节包括以下章节:

  • 第十二章,Dynamics 365 Business Central APIs

  • 第十三章,使用 Business Central 和 Azure 实现无服务器业务流程

  • 第十四章,使用 Azure Functions 进行监控、扩展和 CI/CD

  • 第十五章,Business Central 与 Power Platform 集成

第十二章:Dynamics 365 Business Central API

在上一章中,我们学习了如何在 Dynamics 365 Business Central 项目中使用 DevOps 技术,并重点讨论了源代码管理和 CI/CD 管道等方面。

在本章中,我们将学习如何通过使用平台暴露的 RESTful API,将 Dynamics 365 Business Central 与外部应用集成,重点将放在以下主题:

  • 比较 OData 和 RESTful API

  • 使用 Dynamics 365 Business Central 标准

  • 为新实体和现有实体创建自定义 API

  • 创建使用 Dynamics 365 Business Central API 的应用程序

  • 使用绑定动作

  • 使用 Dynamics 365 Business Central Webhook

  • 在 Microsoft Graph 自动化 API 中使用 Dynamics 365 Business Central API

到本章结束时,你将能够为 Dynamics 365 Business Central 创建 RESTful API,并使用它们与外部应用集成。

比较 Dynamics 365 Business Central 中的 OData 和 API

每个能够发出 HTTP 调用的客户端都可以使用 RESTful API。通过使用 HTTP 协议的 GET、POST、PATCH 和 DELETE 动词,实体可以创建、读取、更新和删除CRUD)。为了与 Dynamics 365 Business Central 进行集成,OData 和 RESTful API 是推荐的工具。

开放数据协议OData)是一个 Web 协议,允许你使用 URI 进行资源标识,通过 HTTP 调用对表格数据执行 CRUD 操作。在 Dynamics 365 Business Central 中将对象暴露为 OData 非常简单:打开 Web 服务页面,插入一个新记录,选择你想要暴露的页面类型,然后点击发布

Dynamics 365 Business Central 会自动为发布的实体分配 OData 和 OData V4 URL,然后你可以通过执行 HTTP REST 调用(GET、POST、PUT、DELETE 和 PATCH)到提供的端点(如下面的截图所示),将此发布的实体(我们的页面)作为 Web 服务使用:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/c98816aa-87ea-4ec0-a24b-4abc52287ad5.png

当调用 OData 端点时,你可以应用过滤器、使用分组、使用流过滤器,并通过使用绑定动作调用业务逻辑(我们将在本章稍后的使用绑定动作部分进行讨论)。

Dynamics 365 Business Central 中的 API 在后台使用相同的 OData 栈,但在我们谈论集成时,它们有三个主要优势:

  • 它们有版本控制(在进行服务集成时,这是最重要的事情之一,因为你需要一个稳定的合同)。

  • 它们支持 Webhook(你可以发布你的 API 页面,然后调用/api/microsoft/runtime/beta/webhookSupportedResources来验证该实体是否支持 Webhook)。

  • 它们有命名空间,因此你可以根据作用域或功能领域来隔离和分组你的 API:{{shortUrl}}/api/APIPublisher/APIGroup/v1.0/mycustomers(\'01121212\')

固定合同是微软阻止扩展标准 API 页面的主要原因。如果您尝试扩展标准 API 页面(例如 Customer Entity 页面),Visual Studio Code 会抛出错误:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/28547ffb-41b7-41f4-aebb-843e11dc5951.png

欲了解如何使用 RESTful API 的更多信息,我推荐以下链接:www.odata.org/getting-started/basic-tutorial/

现在我们已经解释了 OData Web 服务和 RESTful API 之间的主要区别,在接下来的章节中,我们将看到如何在应用程序中使用 Dynamics 365 Business Central API。

使用 Dynamics 365 Business Central 标准 API

Dynamics 365 Business Central 平台将一些标准实体暴露为 RESTful API。暴露的实体总结如下表:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/7bf5d66c-9ea0-40de-a991-ef4d309a4521.png

Dynamics 365 Business Central API 端点具有以下格式:

端点 URL 部分 描述 https://api.businesscentral.dynamics.com Dynamics 365 Business Central 基础 URL(标准 API 和自定义 API 相同) /v2.0 API 版本 /your tenant domain Dynamics 365 Business Central 租户的域名或 ID /environment name 环境名称(生产、沙盒等)。可以从 Dynamics 365 Business Central 管理门户中检索 /api 固定值 /beta  表示正在使用的 API 版本

截至撰写本文时,Dynamics 365 Business Central API 正在使用端点版本 2.0,API version_number = 1.0

在使用基本身份验证时需要租户 ID。操作方法如下:

GET https://api.businesscentral.dynamics.com/v2.0/{tenant Id}/{environment name}/api/v1.0/$metadata

如果您使用 OAuth 认证,则不需要租户 ID:

GET https://api.businesscentral.dynamics.com/v2.0/{environment name}/api/v1.0/$metadata

Dynamics 365 Business Central API 的版本 1.0 仅支持生产和主要沙盒环境。如果您需要在与默认沙盒环境(即 Sandbox)不同的沙盒环境或不同的生产环境中使用 API,则需要使用版本 2.0 的 API,如下所示的端点:

https://api.businesscentral.dynamics.com/v2.0/{tenant Id}/OtherSandboxName/api/

使用 API 时,首先要做的是使用特定的公司 ID。要检索您在 Dynamics 365 Business Central 租户中可用的公司列表,您需要向 /companies API 端点发送 HTTP GET 请求。以下是此 API 调用的示例:

GET https://api.businesscentral.dynamics.com/v2.0//production/api/beta/companiesContent-Type: application/x-www-form-urlencodedAuthorization: Basic sdemiliani 

这是我们收到的响应:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/45abce03-adfd-4ed1-a570-4907b6cfb9fd.png

如果我们想要检索特定公司(例如 Cronus IT)的 Customer 记录列表,我们需要向以下 API 端点发送 HTTP GET 请求:

GET https://api.businesscentral.dynamics.com/v2.0//production/api/beta/companies(80d28ea6-02a3-4ec3-98f7-936c2000c7b3)/customersContent-Type: application/x-www-form-urlencodedAuthorization: Basic sdemiliani 

这是我们从中收到的响应:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/ed085ecb-7366-49a5-82a0-f1cc76497d3d.png

您在调用 API 时还可以应用过滤器。例如,在这里,我们检索所有 Item 记录,其中 unitPrice 大于 100:

GET https://api.businesscentral.dynamics.com/v2.0//production/api/beta/companies(80d28ea6-02a3-4ec3-98f7-936c2000c7b3)/items?$filter=unitPrice%20gt%20100Content-Type: application/x-www-form-urlencodedAuthorization: Basic sdemiliani 

这是响应:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/7acfdb8a-14f2-41f9-a0c8-e2854ed360e0.png

Dynamics 365 Business Central 标准 API 还支持expand等功能,在一次调用中,您可以扩展实体之间的关系,并检索主实体及其相关实体。例如,要在一次 HTTP 调用中检索销售发票及其所有销售发票行记录,您可以执行对以下 API 端点的 HTTP GET 调用:

GET https://api.businesscentral.dynamics.com/v2.0//production/api/beta/companies(80d28ea6-02a3-4ec3-98f7-936c2000c7b3)/salesInvoices(034a122b-962b-4007-b3d1-00718c2f21ff)?$expand=salesInvoiceLinesContent-Type: application/x-www-form-urlencodedAuthorization: Basic sdemiliani 

结果是,您将得到一个包含销售发票头信息及其相关销售发票行详细信息的单一 JSON 响应对象。以下是头部对象:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/243dead6-3fab-4545-96da-cc2af0bb7d3b.png

此外,这里是相关行的详细信息:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/fce0475a-103f-454d-95dd-389552a2c1cc.png

现在,您可以解析这个 JSON 对象,并根据需要使用其数据。

在下一节中,我们将看到如何为 Dynamics 365 Business Central 中新增的自定义实体创建 API 页面,以及如何为现有实体创建新的 API 页面。

在 Dynamics 365 Business Central 中创建自定义 API

通过 Dynamics 365 Business Central 扩展,您可以创建自定义实体,并且可以将自定义实体作为 RESTful API 对外暴露。

要在 Dynamics 365 Business Central 中创建一个新的 API,您需要定义一个新的Page对象,PageType = API。为此,您可以使用 tpage代码段,然后选择类型为 API 的页面,如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/fc9eed41-f330-4ba8-8278-91dc7b5074e8.png

创建 API 页面时,请记住以下几点:

  • 字段必须具有符合 REST-API 规范的名称格式(仅限字母数字字符,且不能包含空格或特殊字符(camelCase))。

  • 您应使用实体的 ID(SystemId)。

  • 当您通过 API 插入、修改或删除实体时,底层表的触发器不会执行。您需要通过在页面级别处理相应的触发器来调用表的触发器。

  • 在 API 页面中的OnModify触发器中,您需要处理重命名记录的可能性(通过记录 ID 进行 API 调用可能会触发主键重命名)。

在这里,我们将看到两种主要情况:

  • 如何为自定义实体实现 API(假设有一个扩展将Car实体添加到 Dynamics 365 Business Central 中,以便在 ERP 中管理汽车详细信息)

  • 如何为现有实体实现新 API

我们将在以下章节中详细讨论每种情况。

为自定义实体实现新的 API

在此示例中,我们将在 Dynamics 365 Business Central 中创建一个新的实体来处理Cars的详细信息,并且该实体还将作为 API 对外暴露,供外部应用程序使用:

  1. 为了实现这一点,我们首先创建一个新的Car表,如下所示:
table 50111 Car{ DataClassification = CustomerContent; Caption = \'Car\'; LookupPageId = \"Car List\"; DrillDownPageId = \"Car List\"; fields { field(1; ModelNo; Code[20]) { Caption = \'Model No.\'; DataClassification = CustomerContent; } field(2; Description; Text[100]) { Caption = \'Description\'; DataClassification = CustomerContent; } field(3; Brand; Code[20]) { Caption = \'Brand\'; DataClassification = CustomerContent; } field(4; Power; Integer) { Caption = \'Power (CV)\'; DataClassification = CustomerContent; } field(5; \"Engine Type\"; Enum EngineType) { Caption = \'Engine Type\'; DataClassification = CustomerContent; } field(10; ID; Guid) { Caption = \'ID\'; DataClassification = CustomerContent; } } keys { key(PK; ModelNo) { Clustered = true; } } trigger OnInsert() begin ID := CreateGuid(); end;}

Car表包含所需的字段,并且它有一个ID字段,定义为Guid,该字段会在OnInsert触发器中自动分配。

  1. Engine Type字段是Enum EngineType类型,enum定义如下:
enum 50111 EngineType{ Extensible = true; value(0; Petrol) { Caption = \'Petrol\'; } value(1; Diesel) { Caption = \'Diesel\'; } value(2; Electric) { Caption = \'Electric\'; } value(3; Hybrid) { Caption = \'Hybrid\'; }}
  1. 我们还创建了一个 Car List 页面(标准列表页面),用于在 Dynamics 365 Business Central 中管理 Car 数据。Car List 页面定义如下:
page 50112 \"Car List\"{ PageType = List; SourceTable = Car; Caption = \'Car List\'; ApplicationArea = All; UsageCategory = Lists; layout { area(content) { repeater(General) { field(ModelNo;ModelNo) {  ApplicationArea = All; } field(Description;Description) {  ApplicationArea = All; } field(Brand;Brand) {  ApplicationArea = All; } field(\"Engine Type\";\"Engine Type\") {  ApplicationArea = All; } field(Power;Power) {  ApplicationArea = All; } } } } }
  1. 现在,我们需要创建 API 页面(通过使用 tpage 代码片段并选择 API 类型的页面)。CarAPI 页面定义如下:
page 50111 CarAPI{ PageType = API; Caption = \'CarAPI\'; APIPublisher = \'sd\'; APIGroup = \'custom\'; APIVersion = \'v1.0\'; EntityName = \'car\'; EntitySetName = \'cars\'; SourceTable = Car; DelayedInsert = true; ODataKeyFields = ID; layout { area(Content) { repeater(GroupName) { field(id; ID) {  Caption = \'id\', Locked = true; } field(modelno; ModelNo) {  Caption = \'modelNo\', Locked = true; } field(description; Description) {  Caption = \'description\', Locked = true; } field(brand; Brand) {  Caption = \'brand\', Locked = true; } field(engineType; \"Engine Type\") {  Caption = \'engineType\', Locked = true; } field(power; Power) {  Caption = \'power\', Locked = true; } } } } trigger OnInsertRecord(BelowxRec: Boolean): Boolean begin Insert(true); Modify(true); exit(false); end; trigger OnModifyRecord(): Boolean var Car: Record Car; begin Car.SetRange(ID, ID); Car.FindFirst(); if ModelNo  Car.ModelNo then begin Car.TransferFields(Rec, false); Car.Rename(ModelNo); TransferFields(Car); end; end; trigger OnDeleteRecord(): Boolean begin Delete(true); end;}

此页面暴露了我们希望在 API 中展示的字段,按照 OData 规范应用命名规则。

然后我们处理 OnInsertRecordOnModifyRecordOnDeleteRecord 页面触发器,以调用表的触发器并处理记录重命名。

  1. 现在,在 Visual Studio Code 中按 F5 并发布你的扩展。当发布完成后,搜索 Car List,然后插入一些示例 Car 记录,例如以下内容:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f9446d19-55fe-43da-9e9f-4852b6171b75.png

  1. 现在,我们可以测试我们的自定义 API。当它在 SaaS 租户中发布时,自定义 API 端点的格式如下:
{BaseURL}/v2.0///api///

如果你是在基于 Docker 的沙盒环境中进行测试(例如,我在这里使用的是 Azure 虚拟机),API 端点如下所示:

{BaseServerUrl:ODATA_Port}/{ServerInstance}/api////

你可以通过以下 URL 检查已发布 API 的元数据(这里,d365bcita0918vm 是托管容器的 Azure 虚拟机的名称):

https://d365bcita0918vm.westeurope.cloudapp.azure.com:7048/BC/api/sd/custom/v1.0/$metadata

API 按公司调用。要获取数据库中公司的列表,你必须发送一个 GET 请求到以下 URL:

{baseUrl}/{D365BCInstance}/api/sd/custom/v1.0/companies

要获取所选公司车辆的列表,你需要发送一个 GET 请求到以下 URL(通过传递前面调用中检索到的公司 ID):

{baseUrl}/{D365BCInstance}/api/sd/custom/v1.0/companies({id})/cars

在我们的环境中,URL 如下所示:

GET https://d365bcita0918vm.westeurope.cloudapp.azure.com:7048/BC/api/sd/custom/v1.0/companies(ecdc7cd0-ab75-4d40-8d0e-80d2471c4378)/cars

这是我们收到的响应:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/65185f83-17be-4343-b409-7e877dd30d9f.png

如你所见,我们有已插入记录的 JSON 表示,每个字段(JSON token)都具有我们在 API 定义中分配的名称。

要通过我们之前发布的自定义 cars API 插入一条新的 Car 记录,你需要发送一个 POST 请求到以下 URL,并将 JSON 记录作为请求体传递:

{baseUrl}/{D365BCInstance}/api/sd/custom/v1.0/companies({id})/cars

这是我们发送的 HTTP 请求:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/4d187f52-ab3c-4746-be53-37356fafb782.png

返回的响应如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f222ec68-ee03-4ede-99e4-8085818b81a2.png

我们收到 HTTP/1.1 201 Created 响应,并且 Car 记录的 JSON 详情被添加到 Dynamics 365 Business Central 中。

如果你查看 Dynamics 365 Business Central 中的 Car List,你可以看到新记录已被创建:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/b90a5159-d150-49a2-b03b-35e1b46e8954.png

发送 POST 请求时,记得正确设置请求的内容类型为 application/json. 否则,你可能会在响应消息中收到一个相当困惑的错误,如 {\"error\":{\"code\":\"BadRequest\",\"message\":\"Cannot create an instance of an interface.\"}}

要检索特定汽车记录的详细信息,只需发送一个 GET 请求到以下 URL:

{baseUrl}/{D365BCInstance}/api/sd/custom/v1.0/companies({id})/cars({id})

这是通过传递要检索的汽车记录的 GUID 来完成的。

在我们的示例中,如果我们想要检索 Mercedes 记录的详细信息,我们必须向以下 URL 发送 HTTP GET 请求:

https://d365bcita0918vm.westeurope.cloudapp.azure.com:7048/BC/api/sd/custom/v1.0/companies(ecdc7cd0-ab75-4d40-8d0e-80d2471c4378)/cars(0237f4af-3422-41b3-94aa-81196346460e)

这是我们收到的响应:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/c9094066-a3b3-4b6b-8f95-5bea016a5324.png

正如你所看到的,我们已经检索到了 Car 记录的 JSON 表示。

为现有实体实现一个新的 API

正如我们在 比较 Dynamics 365 Business Central 中的 OData 和 API 部分中讨论的,你不能扩展现有的标准 Dynamics 365 Business Central API 页面。如果需要检索标准 Dynamics 365 Business Central 实体的新字段,你需要在你的命名空间中创建一个新的 API 页面。

例如,在这里,我正在创建一个简单的新 API,用于检索客户的详细信息,这些信息在标准的 Customer API 中并未原生暴露。API 页面定义如下:

page 50115 MyCustomerAPI{ PageType = API; Caption = \'customer\'; APIPublisher = \'SD\'; APIVersion = \'v1.0\'; APIGroup = \'customapi\'; EntityName = \'customer\'; EntitySetName = \'customers\'; SourceTable = Customer; DelayedInsert = true; ODataKeyFields = SystemId; //URL: https://api.businesscentral.dynamics.com/v2.0/TENANTID/sandbox/api/SD/customapi/v1.0/customers layout { area(Content) { repeater(GroupName) { field(no; \"No.\") {  Caption = \'no\', Locked = true; } field(name; Name) {  Caption = \'name\', Locked = true; } field(Id; SystemId) {  Caption = \'Id\', Locked = true; } field(balanceDue; \"Balance Due\") {  Caption = \'balanceDue\', Locked = true; } field(creditLimit; \"Credit Limit (LCY)\") {  Caption = \'creditLimit\', Locked = true; } field(currencyCode; \"Currency Code\") {  Caption = \'currencyCode\', Locked = true; } field(email; \"E-Mail\") {  Caption = \'email\', Locked = true; } field(fiscalCode; \"Fiscal Code\") {  Caption = \'fiscalCode\', Locked = true; } field(balance; \"Balance (LCY)\") {  Caption = \'balance\', Locked = true; } field(countryRegionCode; \"Country/Region Code\") {  Caption = \'countryRegionCode\', Locked = true; } field(netChange; \"Net Change\") {  Caption = \'netChange\', Locked = true; } field(noOfOrders; \"No. of Orders\") {  Caption = \'noOfOrders\', Locked = true; } field(noOfReturnOrders; \"No. of Return Orders\") {  Caption = \'noOfReturnOrders\', Locked = true; } field(phoneNo; \"Phone No.\") {  Caption = \'phoneNo\', Locked = true; } field(salesLCY; \"Sales (LCY)\") {  Caption = \'salesLCY\', Locked = true; } field(shippedNotInvoiced; \"Shipped Not Invoiced\") {  Caption = \'shippedNotInvoiced\', Locked = true; } } } } }

当我们将其发布到 Dynamics 365 Business Central 租户时,可以通过以下端点访问此 API:

{baseUrl}/{D365BCInstance}/api/sd/customapi/v1.0/companies({id})/customers

如果我们向此端点发送 HTTP GET 请求以检索 Customer 记录,我们将获得以下 API 响应:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/17137bef-cc4f-46de-ae38-2f43c92d18e5.png

正如你所看到的,自定义 API 显示了我们已添加到 API 页面中的所有 Customer 字段(包括 NormalFlowfields 字段)。

在下一节中,我们将看到如何从外部应用程序使用 Dynamics 365 Business Central API。

创建一个使用 Dynamics 365 Business Central API 的应用程序

正如我们在本章中提到的,API 对于将外部应用程序与 Dynamics 365 Business Central 集成非常有用(它们允许我们使用简单的 HTTP 调用来管理 ERP 实体和业务逻辑)。例如,在这里,我们将创建一个 C# .NET 应用程序,用于在 Dynamics 365 Business Central SaaS 租户中创建 Customer 记录。

这个场景非常适用于实现自定义数据加载程序。通过使用 API,你可以创建非常强大的数据传输例程,避免使用标准工具,如配置包,从而加载大量数据。

该应用程序是一个 .NET 控制台应用程序,执行以下操作:

  • 使用基本身份验证(用户名和 Web 服务访问密钥)连接到 Dynamics 365 Business Central 租户

  • 读取此租户中的公司并检索公司 ID

  • 创建一个 JSON 对象,表示一个 Customer 记录

  • 通过向 Customer API 端点发送 POST 请求,传递要创建的 Customer 记录的 JSON 数据

本书的完整源代码可以在该书的 GitHub 仓库中找到。

该应用程序的主要功能定义如下:

static HttpClient client = new HttpClient();static string baseURL, user, key;static string workingCompanyID;static void Main(string[] args){ GetSettingsParameters(); RunAsync().GetAwaiter().GetResult();}static async Task RunAsync(){ client.BaseAddress = new Uri(baseURL); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue(\"application/json\")); string userAndPasswordToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(user + \":\" + key)); client.DefaultRequestHeaders.TryAddWithoutValidation(\"Authorization\", $\"Basic {userAndPasswordToken}\"); try {  //Reads D365BC tenant companies await GetCompanies(baseURL); //Creates a D365BC customer await CreateCustomer(baseURL, workingCompanyID); } catch (Exception e) { Console.WriteLine(e.Message); } Console.ReadLine();}

Main 方法中,我们通过调用 GetSettingsParameters 函数读取应用程序设置参数,然后异步启动读取公司(GetCompanies)和通过 API 创建 Customer 记录(CreateCustomer)的任务。

GetSettingsParameters 函数定义了使用 Dynamics 365 Business Central API 所需的必填参数(例如租户 ID、API URL、用户和访问密钥),并按如下方式定义:

static void GetSettingsParameters(){ string tenantID = \"\"; baseURL = \"https://api.businesscentral.dynamics.com/v2.0/\" + tenantID + \"/production/api/beta\"; user = \"\"; key = \"\";}

我们在 GetCompaniesCreateCustomer 方法中调用了 Dynamics 365 Business Central 的 API。

GetCompanies 方法中,我们向以下端点发送一个 HTTP GET 请求:

https://api.businesscentral.dynamics.com/v2.0/{tenantID}/production/api/{APIversion}/companies

然后,我们检索指定租户中的公司列表。响应是 JSON 格式的,因此我们需要解析它(我们正在检索 idname 字段)。相关代码如下:

static async Task GetCompanies(string baseURL){ HttpResponseMessage response = await client.GetAsync(baseURL + \"/companies\"); JObject companies = JsonConvert.DeserializeObject (response.Content.ReadAsStringAsync().Result); JObject o = JObject.Parse(companies.ToString()); foreach (JToken jt in o.Children()) { JProperty jProperty = jt.ToObject(); string propertyName = jProperty.Name; if (propertyName == \"value\") { foreach (JToken jt1 in jProperty.Children()) { JArray array = new JArray(jt1.Children()); for (int i = 0; i < array.Count; i++) {  string companyID = array[i].Value(\"id\");  string companyName = array[i].Value(\"name\");  Console.WriteLine(\"Company ID: {0}, Name: {1}\", companyID, companyName);  if (companyName == \"CRONUS IT\")  {  workingCompanyID = companyID;  } } } } }}

在这里,我们希望与特定公司合作,因此我们将所需的公司 ID 保存到一个名为 workingCompanyID 的全局变量中,以便在整个应用程序中使用该公司 ID。

CreateCustomer 方法中,我们向以下 API 端点发送一个 POST 请求:

https://api.businesscentral.dynamics.com/v2.0/{tenantID}/production/api/{APIversion}/customers

这是通过在请求体中传递一个 JSON 对象来完成的。这个对象是一个表示 Customer 记录的 JSON(请求的内容类型必须是 application/json)。然后,读取 API 响应。

CreateCustomer 方法的代码如下:

static async Task CreateCustomer(string baseURL, string companyID){ JObject customer = new JObject(  new JProperty(\"displayName\", \"Stefano Demiliani API\"), new JProperty(\"type\", \"Company\"), new JProperty(\"email\", \"demiliani@outlook.com\"), new JProperty(\"website\", \"www.demiliani.com\"), new JProperty(\"taxLiable\", false), new JProperty(\"currencyId\", \"00000000-0000-0000-0000-000000000000\"), new JProperty(\"currencyCode\", \"EUR\"), new JProperty(\"blocked\", \" \"), new JProperty(\"balance\", 0), new JProperty(\"overdueAmount\", 0), new JProperty(\"totalSalesExcludingTax\", 0), new JProperty(\"address\", new JObject( new JProperty(\"street\", \"Viale Kennedy 87\"), new JProperty(\"city\", \"Borgomanero\"), new JProperty(\"state\", \"Italy\"), new JProperty(\"countryLetterCode\", \"IT\"), new JProperty(\"postalCode\", \"IT-28021\") ) ) ); HttpContent httpContent = new StringContent(customer.ToString(), Encoding.UTF8,  \"application/json\"); HttpResponseMessage response = await client.PostAsync(baseURL + \"/companies(\"+companyID+\")/customers\", httpContent); if (response.Content != null) { var responseContent = await response.Content.ReadAsStringAsync(); Console.WriteLine(\"Response: \" + responseContent); }}

在这里,我们创建一个表示 Customer 实体的 JSON 对象,异步发送该 JSON 对象作为 HTTP POST 请求的正文到 Dynamics 365 Business Central /customers API,并读取 API 响应。当此操作被调用时,Customer 记录将在 Dynamics 365 Business Central 中创建。

请记住,Dynamics 365 Business Central 限制您在一定时间窗口内可以执行的 API 调用数量。如果外部服务对租户发起了过多请求,可能会收到 HTTP 429 错误(请求过多):

{ \"error\": { \"code\": \"Application_TooManyRequests\", \"message\": \"Too many requests reached. Actual (101). Maximum (100).\" }}

这样做的主要目的是避免例如拒绝服务DoS)攻击和租户资源不足等问题。

实际允许的每分钟最大请求数如下:

  • 沙盒环境:OData 每分钟 300 次请求(每秒 5 次请求),SOAP 每分钟 300 次请求。

  • 生产环境:OData 每分钟 600 次请求(每秒 10 次请求),SOAP 每分钟 600 次请求。

为避免这种情况,您应该处理如何向 Dynamics 365 Business Central API 端点发出请求,如果收到此错误,您应采取类似重试策略等措施,来处理外部应用中的 API 调用。

这是一个如何在自定义应用中使用 Dynamics 365 Business Central API 的示例。所提供的示例使用了 .NET 和 C#,但您可以在任何支持 HTTP 调用的平台和语言中使用这些 API。

使用绑定操作

我们可以使用绑定操作,不仅通过 RESTful API 执行 CRUD 操作,还可以调用应用中定义的标准业务逻辑(包括自定义和标准代码)。

绑定操作可以在 OData V4 端点中使用(如在demiliani.com/2019/06/12/dynamics-365-business-central-using-odata-v4-bound-actions/中所述)以及标准的 Dynamics 365 Business Central API 中。

假设你有一个代码单元(在这里描述的示例中,它被称为CustomerWSManagement),该代码单元定义了一些业务逻辑(函数集合),用于操作Customer实体,并且你想从 API 调用其中的一些方法。我们的代码单元有两个业务函数:

  • CloneCustomer:这将基于现有的客户记录创建一个新客户。

  • GetSalesAmount:这会返回给定客户的总销售金额。

CustomerWSManagement代码单元的代码定义如下:

codeunit 50102 CustomerWSManagement{ procedure CloneCustomer(CustomerNo: Code[20]) var Customer: Record Customer; NewCustomer: Record Customer; begin Customer.Get(CustomerNo); NewCustomer.Init(); NewCustomer.TransferFields(Customer, false); NewCustomer.Name := \'CUSTOMER BOUND ACTION\'; NewCustomer.Insert(true); end; procedure GetSalesAmount(CustomerNo: Code[20]): Decimal var SalesLine: Record \"Sales Line\"; total: Decimal; begin SalesLine.SetRange(\"Document Type\", SalesLine.\"Document Type\"::Order); SalesLine.SetRange(\"Sell-to Customer No.\", CustomerNo); SalesLine.SetFilter(Type, \'%1\', SalesLine.Type::\" \"); if SalesLine.FindSet() then repeat total += SalesLine.\"Line Amount\"; until SalesLine.Next() = 0; exit(total); end;}

要使用 OData V4 绑定操作,你需要在页面中声明一个函数,并且这个函数必须具有[ServiceEnabled]属性。

如果在pageextension对象中声明了一个[ServiceEnabled]函数,并且尝试访问 OData 端点的元数据(baseurl/ODataV4/$metadata),你将看不到已发布的操作。

要发布与Customer实体相关联的操作,你需要创建一个新的页面,如下所示,然后将其发布为 Web 服务:

page 50102 \"My Customer Card\"{ PageType = Card; ApplicationArea = All; UsageCategory = Administration; SourceTable = Customer; ODataKeyFields = \"No.\"; layout { area(Content) { group(GroupName) { field(Id; Id) {  ApplicationArea = All; } field(\"No.\"; \"No.\") {  ApplicationArea = All; } field(Name; Name) {  ApplicationArea = All; } } } }}

在这里,ODataKeyFields属性指定了在调用 OData 端点时作为键使用的字段(我想要的是Customer记录的No.字段)。

在这个页面中,我声明了两个过程来调用我们 AL 代码单元中定义的两个方法:

[ServiceEnabled]procedure CloneCustomer(var actionContext: WebServiceActionContext)var CustomerWSMgt: Codeunit CustomerWSManagement;begin CustomerWSMgt.CloneCustomer(Rec.\"No.\"); actionContext.SetObjectType(ObjectType::Page); actionContext.SetObjectId(Page::\"My Customer Card\"); actionContext.AddEntityKey(Rec.FIELDNO(\"No.\"), Rec.\"No.\"); //Set the result code to inform the caller that the record is created actionContext.SetResultCode(WebServiceActionResultCode::Created);end;[ServiceEnabled]procedure GetSalesAmount(CustomerNo: Code[20]): Decimalvar actionContext: WebServiceActionContext; CustomerWSMgt: Codeunit CustomerWSManagement; total: Decimal;begin actionContext.SetObjectType(ObjectType::Page); actionContext.SetObjectId(Page::\"My Customer Card\"); actionContext.AddEntityKey(Rec.FIELDNO(\"No.\"), rec.\"No.\"); total := CustomerWSMgt.GetSalesAmount(CustomerNo); //Set the result code to inform the caller that the result is retrieved actionContext.SetResultCode(WebServiceActionResultCode::Get); exit(total);end;

从前面的代码中,我们可以看到以下内容:

  • CloneCustomer是一个无参数调用的过程。它获取调用的上下文,并调用我们代码单元中定义的CloneCustomer方法。

  • GetSalesAmount是一个过程,它接受一个代码参数,调用我们代码单元中定义的GetSalesAmount过程,并将结果作为响应返回。

当我们将MyCustomerCard页面发布为 Web 服务(在这里称为MyCustomerCardWS)时,这些过程定义会发生什么?

如果我们访问 OData V4 元数据端点,现在可以看到操作已经发布:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/54407ba0-e13e-4710-af02-9f5790219f56.png

现在,我们可以尝试通过 OData 调用绑定的操作。作为第一步,我们想要调用CloneCustomer函数。为此,我们需要向以下端点发送 HTTP POST 请求:

https://yourbaseurl/ODataV4/Company(\'CRONUS%20IT\')/MyCustomerCardWS(\'10000\')/NAV.CloneCustomer

以下是我们在调用后获得的输出:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f6c96e77-d604-4184-9eaa-29d7926d6a7d.png

我们代码单元中的代码被调用,并且创建了一个Customer记录(即通过编号为\"No.\" = 10000的客户进行克隆):

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/0e86665c-2142-4e3f-8819-8a9e91cdf4c9.png

我们要调用的第二个函数(GetSalesAmount)需要一个Code[20]参数作为输入(这不是严格要求的,仅仅是为了展示如何将参数传递给绑定操作)。我们需要向以下端点发送 POST 请求:

https://yourbaseurl/ODataV4/Company(\'CRONUS%20IT\')/MyCustomerCardWS(\'10000\')/NAV.GetSalesAmount

如我们所见,这是通过传递包含所需参数的 JSON 正文来完成的。

发送的 POST 请求如下:

POST https://d365bcita0918vm.westeurope.cloudapp.azure.com:7048/NAV/ODataV4/Company(\'CRONUS%20IT\')/MyCustomerCardWS(\'10000\')/NAV.GetSalesAmountContent-Type: application/jsonAuthorization: Basic admin Z1JkubB/3epQOtfnBph04rcNgyFpaEuB9OVTnrd0VPs={ \"customerno\": \"10000\"}

发送 POST 请求后收到的响应如下:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/049d1b71-05f1-44fe-a357-ed84b08ebdac.png

正如您所见,响应是一个 JSON 对象,其值为给定客户的总销售金额(通过调用我们的代码单元方法获取)。

要在 JSON 对象中传递的参数名称必须与 OData 元数据匹配,而不是您函数的参数。

在下一节中,我们将探讨 Dynamics 365 Business Central 中 Webhooks 的概念,并探讨如何订阅从 Dynamics 365 Business Central 实体发送的通知。

使用 Dynamics 365 Business Central Webhooks

Webhooks 是创建事件驱动服务集成的一种方法:客户端不需轮询其他系统以检查实体是否有更改,而是订阅将从源系统推送到其的事件。Dynamics 365 Business Central 支持 Webhooks,因此客户端可以订阅 Webhook 通知(事件),并将自动接收到 Dynamics 365 Business Central 实体更改的通知。

要使用 Dynamics 365 Business Central 的 Webhooks,我们需要执行以下步骤:

  1. 订阅者必须通过向 subscription API 发送 POST 请求并在请求主体中传递通知 URL 来向 Dynamics 365 Business Central 注册 Webhook 订阅。端点 URL 如下:
https://api.businesscentral.dynamics.com/v2.0/TENANTID/production/api/v1.0/subscriptions

建立订阅的请求主体如下(这里,我们以 customers 实体为例):

{ \"notificationUrl\": \"YourAplicationUrl\", \"resource\": \"https://api.businesscentral.dynamics.com/v2.0/TENANTID/production/api/v1.0/companies(COMPANYID)/customers\", \"clientState\": \"SomeSharedSecretForTheNotificationUrl\"}
  1. Dynamics 365 Business Central 向订阅者返回验证令牌。

  2. 订阅者需要在响应主体中返回验证令牌,并提供状态码 200(这是强制性的握手阶段)。

  3. 如果 Dynamics 365 Business Central 在响应主体中收到验证令牌,则注册订阅并将通知发送到通知 URL。

订阅建立后,订阅者将收到有关已订阅实体每次更新的通知。如果在此之前不续订,Webhook 订阅将在 3 天后过期。

要续订 Webhook 订阅,订阅者必须向订阅端点发送 PATCH 请求(此请求也需要握手阶段)。用于续订 Webhook 订阅的请求端点如下:

https://api.businesscentral.dynamics.com/v2.0/TENANTID/production/api/v1.0/subscriptions(\'SUBSCRIPTIONID\')

要更新 Webhook 订阅,您需要在 PATCH 请求头中作为 If-Match 块传递之前建立的订阅的 @odata.etag 标签。

当订阅建立时收到的 HTTP 响应如下:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/6aaa518f-cb30-4b05-b385-957769f38583.png

如果尝试再次向已建立活动订阅的同一端点发出订阅请求,将收到以下错误:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/9ec3d49a-e51b-4296-9700-d54fb05fb7f7.png

当建立订阅时,订阅者可以在 Dynamics 365 Business Central 中的订阅实体被修改时接收通知。这是发送给订阅者的通知示例(该通知是一个包含所有修改实体的 JSON 对象):

{ \"value\": [ { \"subscriptionId\": \"customers\", \"clientState\": \"someClientState\", \"expirationDateTime\": \"2019-07-20T07:52:31Z\", \"resource\": \"api/beta/companies(80d28ea6-02a3-4ec3-98f7-  936c2000c7b3)/customers(26814998-936a-401c-81c1-0e848a64971d)\", \"changeType\": \"updated\", \"lastModifiedDateTime\": \"2019-07-19T12:54:20.467Z\" }, { \"subscriptionId\": \"webhookCustomersId\", \"clientState\": \"someClientState\", \"expirationDateTime\": \"2019-07-20T07:52:31Z\", \"resource\": \"api/beta/companies(80d28ea6-02a3-4ec3-98f7-  936c2000c7b3)/customers(130bbd17-dbb9-4790-9b12-2b0e9c9d22c3)\", \"changeType\": \"created\", \"lastModifiedDateTime\": \"2019-07-19T12:54:26.057Z\" } ]}

Webhooks 也可用于我们的扩展中的自定义对象。具有PageType = API的页面将暴露具有以下限制的 Webhooks(这些限制也适用于标准 API 页面):

  • 页面不能使用复合键。

  • 页面不能使用临时表或系统表作为SourceTable

可以通过向/subscriptions({id})端点发送 DELETE 请求来删除 Webhook 的订阅。此外,要删除订阅,您需要发送包含@odata.etagIf-Match头信息的请求。

有关 Dynamics 365 Business Central Webhooks 的更多信息,我建议查看这篇文章:

demiliani.com/2019/12/10/webhooks-with-dynamics-365-business-central/

在本节中,您了解了 Dynamics 365 Business Central 中 Webhook 的工作原理。在下一节中,我们将展示如何使用 Microsoft Graph API 与 Dynamics 365 Business Central 配合使用。

在 Microsoft Graph 中使用 Dynamics 365 Business Central API

Microsoft Graph (graph.microsoft.io/) 是一个有趣的平台,提供了一个独特的网关,用于跨多个 Microsoft 服务的 RESTful API。现在,Dynamics 365 Business Central 是 Microsoft Graph 中可用的端点之一。

要在 Graph 中使用 Dynamics 365 Business Central,您首先需要更改 Dynamics 365 Business Central 用户在 Graph 中的权限,然后启用Financials.ReadWrite.All权限范围。您可以通过使用 Graph Explorer 工具来完成:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f1ba0a01-dbda-42f8-a12d-2ee0bf59c4f2.png

设置权限后,您可以开始使用在 Graph 中可用的 Dynamics 365 Business Central API(实际上,您需要使用 BETA API 端点)。

例如,要检索您 Dynamics 365 Business Central 租户中的可用公司,您需要向https://graph.microsoft.com/beta/financials/companies发送 HTTP GET 请求,如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/e6d0b09c-38bf-4fe4-892b-cc7b08c2a2da.png

您可以解析此 JSON 响应并检索公司 ID,之后将在所有后续 API 调用中使用此 ID。

要检索某个公司Customer记录的列表,您需要向以下 URL 发送 HTTP GET 请求(并传递该公司的 ID):

https://graph.microsoft.com/beta/financials/companies(\'80d28ea6-02a3-4ec3-98f7-936c2000c7b3\')/customers

作为响应,您将收到包含所有客户列表的 JSON 数据:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/27873133-374d-4a8c-b164-e4102fdc68f6.png

要按降序posting date检索某个公司的一般账簿条目,可以向以下 URL 发送 HTTP GET 请求:

https://graph.microsoft.com/beta/financials/companies(\'80d28ea6-02a3-4ec3-98f7-936c2000c7b3\')/generalLedgerEntries?$orderby=postingDate desc

这是从 API 接收到的响应:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/33417869-e843-41ed-b143-976cfe626fb5.png

例如,要获取某个Currency(例如 USD)的详细信息,你需要向以下 URL 发送 HTTP GET 请求:

https://graph.microsoft.com/beta/financials/companies(\'80d28ea6-02a3-4ec3-98f7-936c2000c7b3\')/currencies?$filter=code eq \'USD\'

获取的响应将如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/25e76ca5-e2a1-49a5-818c-930b366c855a.png

从这个响应中,我们可以获取货币的 ID,因为我们可以稍后使用它,通过 Graph API 在 Dynamics 365 Business Central 中创建新的Customer记录。

要在公司中创建一个Customer记录,并将Currency Code设置为 USD,你需要向以下端点发送 HTTP POST 请求,并将Content-type设置为application/json

https://graph.microsoft.com/beta/financials/companies(\'80d28ea6-02a3-4ec3-98f7-936c2000c7b3\')/customers

该 POST 请求的请求体必须是包含我们想要创建的客户详细信息的 JSON 内容,如下所示:

{ \"displayName\": \"Graph Customer\", \"type\": \"Company\", \"address\": { \"street\": \"V.le Kennedy 8\", \"city\": \"Novara\", \"state\": \"IT\", \"countryLetterCode\": \"IT\", \"postalCode\": \"28021\" }, \"phoneNumber\": \"\", \"email\": \"graph@packtpub.com\", \"website\": \"\", \"currencyId\": \"12902bb7-4938-41b9-8617-33492bcac8b3\", \"currencyCode\": \"USD\", \"blocked\": \" \", \"overdueAmount\": 0}

作为响应,我们将得到一些包含已创建的Customer记录的 JSON 数据:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/95c529a6-fd84-474a-9d21-3fa9c7dab93a.png

如果你现在打开 Dynamics 365 Business Central,你会看到新创建的Customer记录:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/b3d8d45c-005e-419f-878d-06e2f1a164ed.png

在 Graph 中可用的 Dynamics 365 Business Central API 列在docs.microsoft.com/en-us/graph/api/resources/dynamics-graph-reference?view=graph-rest-beta

目前将其视为 Beta 版本,因为它们将在未来得到改进。

我们已经介绍了如何使用 Graph API 与 Dynamics 365 Business Central 进行交互。在接下来的章节中,我们将概述自动化 API。

Dynamics 365 Business Central 中的自动化 API

Dynamics 365 Business Central 还暴露了用于自动化租户相关任务的 API,例如以下内容:

  • 创建公司

  • 管理用户、组和权限

  • 处理扩展(租户扩展的安装/卸载)

  • 导入和应用配置包

自动化 API 位于/microsoft/automation命名空间下。例如,要在 Dynamics 365 Business Central 租户中创建公司,你可以向以下端点发送 HTTP POST 请求:

POST https://api.businesscentral.dynamics.com/v2.0/api/microsoft/automation/{apiVersion}/companies({companyId})/automationCompaniesAuthorization: Bearer {token}Content-type: application/json{ \"name\": \"PACKT PUB\", \"displayName\": \"PACKT Publishing\", \"evaluationCompany\": false, \"businessProfileId\": \"\"}

要获取你租户中的用户信息,你需要向以下端点发送 GET 请求:

GET https://api.businesscentral.dynamics.com/v1.0/api/microsoft/automation/beta/companies({id})/users

当你获取到用户的详细信息后,要通过自动化 API 为用户分配权限集,你需要向以下端点发送 POST 请求:

POST https://api.businesscentral.dynamics.com/v1.0/api/microsoft/automation/{apiVersion}/companies({companyId})//users({userSecurityId})/userGroupMembersAuthorization: Bearer {token}{ \"code\": \"D365 EXT. ACCOUNTANT\", \"companyName\" :\"CRONUS IT\"}

要修改 Dynamics 365 Business Central 用户的详细信息,你需要向以下端点发送 HTTP PATCH 请求:

PATCH https://api.businesscentral.dynamics.com/v1.0/api/microsoft/automation/beta/companies({id})/users({userSecurityId})Content-type: application/jsonIf-Match:*{ \"state\": \"Enabled\", \"expiryDate\": \"2021-01-01T21:00:53.444Z\"}

要获取已安装在租户上的扩展列表,你可以向以下端点发送 GET 请求:

GET https://api.businesscentral.dynamics.com/v1.0/api/microsoft/automation/{apiVersion}/companies({{companyid}})/extensions

要处理扩展的安装和卸载,你可以向以下绑定的操作发送 POST 请求:

  • Microsoft.NAV.install

  • Microsoft.NAV.uninstall

例如,要卸载一个之前安装的扩展,你可以向以下端点发送 POST 请求:

POST https://api.businesscentral.dynamics.com/v1.0/api/microsoft/automation/{apiVersion}/companies({companyId})//extensions({extensionId})/Microsoft.NAV.uninstallAuthorization: Bearer {token}

AppSource 扩展必须先在租户上安装,然后你可以通过自动化 API 安装/卸载它们。

如果您有一个每租户扩展,可以通过向以下端点发送 PATCH 请求将其上传并安装到 SaaS 租户上:

PATCH https://api.businesscentral.dynamics.com/v1.0/api/microsoft/automation/beta/companies({companyId})/extensionUpload(0)/contentAuthorization : Bearer {token}Content-type : application/octet-streamIf-Match:-*

在这里,请求体内容必须包含要上传到租户的 .app 包文件(二进制)。使用自动化 APIs 进行身份验证时,必须使用 OAuth 2.0 授权(Bearer Token)。

关于 Dynamics 365 Business Central APIs 的更多信息,请访问docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/itpro-introduction-to-automation-apis

如果需要激活 CI/CD 流水线并且需要为租户提供初始数据,自动化 APIs 就显得极其重要和强大。

概要

在本章中,我们概述了如何使用 OData 栈(特别是 RESTful APIs)与 Dynamics 365 Business Central 进行集成。我们看到了如何使用标准 APIs,创建自定义 APIs,创建使用 Dynamics 365 Business Central APIs 的应用程序,以及如何使用 Webhooks 和 Graph APIs 等高级概念。然后,我们概述了自动化 APIs。

本章结束时,您已经全面了解了如何暴露 Dynamics 365 Business Central 的业务逻辑和实体,并如何通过使用 REST HTTP 调用处理与外部应用程序的集成。APIs 是 Dynamics 365 Business Central 集成的未来,您已经学会了如何在您的应用程序和扩展中使用它们。

在下一章中,我们将看到如何在 Dynamics 365 Business Central 扩展中使用 Azure Functions 和其他无服务器服务。

第十三章:使用 Business Central 和 Azure 实现无服务器业务流程

在第十二章*《Dynamics 365* *Business Central APIs》*中,我们回顾了由 Dynamics 365 Business Central 提供的各种 API 的概述。我们学习了如何在应用程序中使用这些 API,以及如何创建自定义 API。

本章将介绍一个在云环境中架构业务应用时出现的重要概念:无服务器处理。正如你所知,在 SaaS 环境中,你不能使用所有本地环境中可用的功能(如文件和 .NET DLLs)。在云环境中,你需要重新思考这些功能,使用云基础设施提供的服务。

本章将学习以下内容:

  • Azure 平台提供的无服务器功能概览

  • 使用 Azure Functions 与 Dynamics 365 Business Central 集成

  • Dynamics 365 Business Central 在现实应用中的无服务器处理场景

到本章结束时,你将清楚地理解如何通过使用 Azure Functions 在 Dynamics 365 Business Central 中实现无服务器流程。

技术要求

为了跟上本章内容,你需要准备以下内容:

  • 一个有效的 Azure 订阅(可以是付费订阅,或者是可以免费激活的试用订阅,访问 azure.microsoft.com/free/

  • Visual Studio 或 Visual Studio Code

  • Visual Studio 或 Visual Studio Code 的 Azure Functions 扩展

Microsoft Azure 无服务器服务概览

无服务器技术在云计算世界中至关重要。它们使你能够专注于业务应用和代码,而不必处理资源的配置和管理、扩展以及更广泛地说,处理运行应用所需的基础设施。

Azure 提供了一整套托管的无服务器服务,你可以将其作为构建应用程序的基础,这些服务包括计算资源、数据库、存储、编排、监控、智能和分析。

Azure 无服务器服务可以分为以下几类:

  • 计算:

    • 无服务器函数(Azure Functions)

    • 无服务器应用环境(Azure 应用服务)

    • 无服务器 Kubernetes(Azure Kubernetes 服务)

  • 存储:

    • Azure 无服务器存储(Blob 存储)
  • 数据库

  • 工作流和集成:

    • Azure 逻辑应用

    • Azure API 管理

    • Azure 事件网格

  • 监控:

    • Azure Monitor
  • 分析:

    • Azure 流分析
  • 人工智能和机器学习:

    • Azure 认知服务

    • Azure 机器学习服务

    • Azure Bot 服务

  • DevOps:

    • Azure DevOps

处理服务所需的基础设施由 Microsoft 在全球数据中心全面管理。使用 Azure 无服务器处理时,你将享有以下三个主要优势:

  • 您可以根据需要扩展服务:您可以扩展服务的实例数量(增加或减少实例),或者您可以扩展服务的资源(增加或减少资源)。

  • 按需付费:您只需为代码运行时或执行代码时所使用的资源付费。

  • 集成安全性和监控:由 Azure 平台管理的功能。

有关 Azure 提供的无服务器服务的更多信息,请访问 azure.microsoft.com/en-us/solutions/serverless/

现在我们已经了解了 Microsoft Azure 无服务器服务的概述,接下来让我们看看 Azure Functions 的概览。

获取 Azure Functions 概览

Azure Functions 是 Azure 提供的一项服务,提供函数即服务。您可以编写代码(使用不同的编程语言),无需担心基础设施问题,您的代码将在云端执行。使用 Azure Functions,您可以按需执行代码(在请求函数后),按计划执行,或自动响应不同的事件。

您可以通过 Azure 门户直接编写 Azure 函数,或者在本地开发机器上进行开发。您还可以在将其部署到云端之前,本地调试和测试 Azure 函数。

Azure Functions 的以下是主要功能:

  • 使用您最熟悉的编程语言进行开发,或将现有代码重用到云端。

  • 集成安全性;您可以指定所需的安全设置,平台将自动处理。

  • 可扩展性管理;您可以根据负载和使用情况选择服务等级。

  • 按需付费定价模型

在撰写本文时,以下 Azure 函数类型可用:

  • HTTPTrigger:这是经典的函数类型,您的代码执行是通过 HTTP 请求触发的。

  • TimerTrigger:您的代码将在预定的时间表上执行。

  • BlobTrigger:当一个 Blob 被添加到 Azure Blob 存储容器时,您的代码将被执行。

  • QueueTrigger:当消息到达 Azure 存储队列时,您的代码将被执行。

  • EventGridTrigger:当事件被传递到 Azure Event Grid 中的订阅时,您的代码将被执行(基于事件的架构)。

  • EventHubTrigger:当事件被传递到 Azure Event Hub 时,您的代码将被执行(通常用于物联网场景)。

  • ServiceBusQueueTrigger:当消息到达 Azure 服务总线队列时,您的代码将被执行。

  • ServiceBusTopicTrigger:当订阅主题的消息到达 Azure 服务总线时,您的代码将被执行。

  • CosmosDBTrigger:当文档被添加或更新到存储在 Azure Cosmos DB 中的文档集合时,您的代码将被执行。

Azure Functions 提供以下定价计划:

  • Consumption plan:您为代码在云端执行的时间付费。

  • 应用服务计划:你为托管计划付费,就像正常的 Web 应用一样。你可以在同一个应用服务计划上运行不同的函数。

正如我们之前提到的,直接使用 .NET DLLs(AL 中的 .NET 变量)在 SaaS 环境中不可用。Azure Functions 是在 SaaS 环境中将 .NET 代码与 Dynamics 365 Business Central 结合使用的推荐方式。

在接下来的章节中,我们将查看一个验证邮箱地址的 Azure 函数的实现。我们将使用这个函数来验证 Dynamics 365 Business Central 中客户记录关联的邮箱地址。这个函数将使用 Visual Studio 开发,然后使用 Visual Studio Code 开发。

使用 Visual Studio 开发 Azure 函数

要使用 Visual Studio 创建 Azure 函数,你需要安装 Azure SDK 工具。这些工具可以在安装 Visual Studio 时直接安装,也可以稍后通过访问 azure.microsoft.com/en-us/downloads/ 安装。

现在,按照以下步骤学习如何开发 Azure 函数:

  1. 使用 Visual Studio(我使用的是 2019 版本),创建一个新项目并选择 Azure Functions 模板:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/4840e6bd-9ad4-481d-bccf-a66d5b29956f.png

  1. 为你的项目选择一个名称(在这里,我使用 EmailValidator),选择一个保存项目文件的位置,并点击创建:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/e7543e69-8394-42ec-953a-29aa1c5b9d62.png

  1. 接下来,你需要选择你的 Azure 函数的运行时版本:

    • Azure Functions v2 (.NET Standard):基于 .NET Core(跨平台),这是新的可用运行时。

    • Azure Functions v1 (.NET Framework):基于 .NET Framework,仅支持在 Azure 门户或 Windows 计算机上进行开发和托管。

在这里,我选择了 Azure Functions v2 (.NET Core)。

  1. 然后,你需要选择 Azure 函数类型(我选择了 Http 触发器,因为我希望创建一个可以通过 HTTP 调用的函数),为了简单起见,我选择了匿名作为我们函数的访问权限(无需身份验证,任何人都可以使用;我们将在本章的 管理 Azure 函数密钥 部分讨论这一点)。

  2. 现在,点击 OK 来创建解决方案。这是你在 Visual Studio 中看到的项目树:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/1cb717dc-d7aa-45b8-9ee8-49b957b989ff.png

在这里,你会看到以下文件:

  • host.json:这个文件包含影响项目中所有函数的全局配置选项。在我们的项目中,我们有运行时版本(2.0)。

  • local.settings.json:这个文件包含你的项目设置。

  • EmailValidator.cs:这是你函数的源代码(C#)。

我们函数的实现非常简单。它接收一个 email 参数作为输入(通过 GET 或 POST 请求),验证邮箱地址,然后返回一个 JSON 响应,说明该地址是否有效(这是一个名为 EmailValidationResult 的自定义对象)。

函数代码定义如下:

[FunctionName(\"EmailValidator\")]public static async Task Run( [HttpTrigger(AuthorizationLevel.Anonymous, \"get\", \"post\", Route = null)] HttpRequest req, ILogger log){ log.LogInformation(\"C# HTTP trigger function processed a request.\"); string email = req.Query[\"email\"]; string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); email = email ?? data?.email; //Validating the email address EmailValidationResult jsonResponse = new EmailValidationResult(); jsonResponse.Email = email; jsonResponse.Valid = IsEmailValid(email); string json = JsonConvert.SerializeObject(jsonResponse); return email != null  ? (ActionResult)new OkObjectResult($\"{json}\")  : new BadRequestObjectResult(\"Please pass a email parameter on the query string or in the request body\");}

这个函数从输入中获取 email 参数,并调用 IsEmailValid 函数。

这个函数通过使用 System.Net.Mail.MailAddress 类来验证电子邮件地址,如下所示:

static bool IsEmailValid(string emailaddress){ try { System.Net.Mail.MailAddress m = new System.Net.Mail.MailAddress(emailaddress); return true; } catch (FormatException) { return false; }}

在电子邮件验证后,函数会创建一个 EmailValidationResult 对象,并用响应值进行序列化,最后返回 JSON 响应。这个 EmailValidationResult 对象定义如下:

public class EmailValidationResult{ public string Email { get; set; } public bool Valid { get; set; }}

现在我们已经测试了函数,是时候将其本地发布了。

本地测试 Azure 函数

Visual Studio 提供了一个模拟器,我们可以使用它来测试和调试我们的 Azure 函数,直到它部署到 Azure。如果你运行这个项目,模拟器会启动并为你提供一个本地 URL 用于测试你的函数:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/ed490f89-9772-48fb-b4f7-b06d4913814c.png

我们可以通过打开浏览器并调用一个 URL 来测试我们的函数,例如 localhost:7071/api/EmailValidator?email=masteringd365bc@packt.com

当被调用时,模拟器会显示请求详情:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/83106311-74da-4e77-aca9-d2dfcc928a6d.png

我们可以在浏览器中看到 JSON 响应。之前的调用返回以下响应对象:

{\"Email\":\"masteringd365bc@packt.com\",\"Valid\":true}

如果我们用一个无效的电子邮件地址调用这个函数,比如 http://localhost:7071/api/EmailValidator?email=masteringd365bc,我们会得到以下响应对象:

{\"Email\":\"masteringd365bc\",\"Valid\":false}

我们的函数运行正常。

现在我们已经测试了 Azure 函数,准备将其部署。

部署函数到 Azure

现在,我们需要将函数部署到 Azure。我们可以直接从 Visual Studio 进行部署,步骤如下:

  1. 右键点击项目并选择 发布…:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/5beab55b-38ea-427e-bbdd-8c9166a9d486.png

  1. 我们需要选择一个 Azure 应用服务来部署函数。为此,我们可以选择一个现有的,或者创建一个新的。这里,我为这个函数创建了一个新的 Azure 应用服务实例:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/765bb82d-5cee-4334-8032-598f93a7f1c8.png

点击发布。

  1. 然后,我们选择订阅、资源组(创建一个新组或使用现有的)、托管计划以及用于部署的存储账户:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f058c639-82d9-422e-a27e-fbd2c1d555a3.png

现在,点击 创建 – 我们的函数(以及所有相关资源)将被部署到 Azure。

  1. 现在,我们的函数已经发布到 Azure 数据中心,并且我们有了一个公共 URL,以便使用,如下所示的截图所示:
  • https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/9e48115e-fee4-4718-a23c-e575f6c64045.png

在我的示例中,公共 URL 为 emailvalidator20190603055323.azurewebsites.net(你可以自定义它)。

要测试你的函数,使用以下 URL: https://emailvalidator20190603055323.azurewebsites.net/**api/EmailValidator?email=masteringd365bc****@packt.com****。

现在,函数已在 Azure 云中运行,你可以使用它了。

还有一种替代的 Azure 函数开发方式,对于任何 Dynamics 365 Business Central 开发者来说,这非常重要。我们将在下一节中查看这个方法。

使用 Visual Studio Code 开发 Azure 函数

要开始使用 Visual Studio Code 开发 Azure 函数,您需要安装以下扩展:

  • Azure 帐户,您可以从marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account下载。

  • 您可以从marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions下载 Azure Functions。

您还需要安装 Azure Functions Core Tools,这是一个工具集,允许您在本地机器上开发和测试您的函数。

您需要安装支持您正在使用的 Azure Functions 运行时的版本。您可以在github.com/Azure/azure-functions-core-tools安装此版本。

要使用 Visual Studio Code 开发 Azure 函数,请按照以下步骤操作:

  1. 您需要做的第一件事是通过 Visual Studio Code 命令面板中的 Azure: Sign In 命令登录到您的 Azure 订阅:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/1ec18be7-968c-4595-9c2f-af123e6c6cdf.png

输入凭据后,您将在 Visual Studio Code 的底部栏看到与您的订阅关联的帐户。

  1. 现在,在 Visual Studio Code 侧边栏中,选择 Azure Functions 扩展并点击“创建新项目”按钮:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/0d2e84b2-407e-4ef6-b2eb-3bb9572f2ee5.png

选择一个文件夹,将您的 Azure Functions 项目放在其中。

  1. 然后,系统会提示您选择用于开发函数的语言。在支持的语言列表中,我选择了 C#,如图所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/9c3ee88c-e9ab-4521-bbfc-e45e78b492f8.png

  1. 接下来,您需要为您的 Azure 函数选择以下任一运行时版本:

    • Azure Functions v2(.NET Standard):基于 .NET Core(跨平台),这是新的可用运行时。

    • Azure Functions v1(.NET Framework):基于 .NET Framework,仅支持在 Azure 门户或 Windows 计算机上进行开发和托管。

在这里,我选择了 Azure Functions v2(.NET Standard):

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/34fb3c07-942c-4653-b110-d12729c20e95.png

  1. 现在,为您的 Azure Functions 项目设置一个名称(在这里,我将其命名为EmailValidatorCore):

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/558b7f94-7f61-4c2d-b12e-e6676028c982.png

  1. 命名项目后,提供一个命名空间:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/cb46ae58-b5a9-4b9e-955b-e3fba0dddad0.png

  1. 现在,您需要选择函数的认证类型。为了简便起见,我选择了匿名(每个人都可以调用我们的函数):

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/33c9710b-dc04-4072-8c75-8d34eb61917f.png

  1. 现在,选择打开将创建的项目的位置(当前窗口、新窗口和添加到工作区是可用的选项)。

Visual Studio Code 将开始下载你的 Azure Functions 项目所需的包,下载完成后,将创建一组文件。以下是你的 Azure 函数的模板:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/0c3edb55-182d-47a9-b2f1-66b9a9a1fd32.png

在这里,你会看到以下文件:

  • host.json:此文件包含影响项目中所有函数的全局配置选项。在我们的项目中,我们有运行时版本(2.0)。

  • local.settings.json:此文件包含你的项目设置。

  • EmailValidatorCore.cs:这是你的函数源代码(C#)。

请注意,你的代码中可能会出现一些临时错误(缺少引用)。这种情况发生在 Visual Studio Code 需要下载所有 .NET Core 包时。为了获取正确的引用,你需要从命令面板执行 .NET: Restore Project 命令,具体操作如下:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/17c61411-bb61-40a8-bcc5-0607d8ac99a5.png

选择推荐的选项并点击确定,如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/1ad494d1-b0a7-4f97-a16b-9b0b84b1e244.png

现在你的项目引用已修复,你可以开始编写函数代码了。

如同之前的示例一样,我们要创建一个用于验证电子邮件地址的函数,并且可以重用相同的 C# 代码。这里,你已经了解了如何通过直接使用 Visual Studio Code 来开始创建一个 Azure Functions 项目。

本地测试你的 Azure 函数

为了在本地测试你的函数,你需要安装 Azure Functions Core Tools。你可以在命令提示符(或 Visual Studio Code 终端)中使用以下命令安装:

npm i -g azure-functions-core-tools --unsafe-perm true

要在 Visual Studio Code 中使用 npm,你需要在机器上安装 Node.js。你可以从 nodejs.org/en/ 安装它。

当你运行 npm 命令时,某些包会被下载并安装到你的本地机器上:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/6e231f0f-1b58-48a5-8a23-e0b6cfe57bbd.png

安装工具后,你需要重启 Visual Studio Code 以使其生效。

要开始在本地测试你的函数,你可以直接在 Visual Studio Code 中按 F5 键。此时本地的 Azure Function Host 环境将启动,Visual Studio Code 会提供一个本地 URL,供你调用你的函数:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/a78c85f8-0b31-4c91-8d9f-472a7d11516a.png

现在,我们可以通过浏览器中的 URL 传递电子邮件参数来测试该函数(就像我们在 将函数部署到 Azure 部分所做的那样)。

同时,我们可以通过在 Visual Studio Code 中设置断点、逐步调试、检查变量和输出等,直接调试代码,如下图所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/fd3f6abe-1786-4d91-b3f4-2ea341081d7b.png

现在,你已经在本地环境中调试并测试了你的 Azure 函数。在下一部分,我们将学习如何将函数发布到 Azure 云端。

将你的函数发布到 Azure

要将你的 Azure 函数部署到 Azure,按照以下简单步骤进行:

  1. 点击 Visual Studio Code 侧边栏中的 Azure Functions 图标,然后点击名为“部署到 Function App”的蓝色箭头图标,如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/660d64e1-7c5d-4d0f-85cf-0022fe6d2f65.png

  1. Visual Studio Code 将要求您从帐户的可用订阅列表中选择一个 Azure 订阅作为部署函数的目标位置,如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/d2a27f5d-0ec7-4894-a38a-cbe6ff47f4bc.png

  1. 现在,选择在 Azure 中创建新的 Function App 并为其指定一个全球唯一的名称:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/0f86ea16-c64a-43a9-969b-701b643a5ee0.png

我称之为SDEmailValidatorCore*。

  1. 从这里,您可以创建一个新的资源组和一个新的存储帐户。选择您希望部署函数的区域。然后,资源部署将开始。

  2. 部署过程完成后,Visual Studio Code 将在右下角显示确认消息。你可以在左侧的订阅树视图中看到已部署的功能:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/4d737d43-2329-48eb-b2a2-04fc0d429d09.png

现在,您的 Azure 函数已在 Azure 数据中心运行,您可以开始在 AL 代码中使用它。我们将在下一节中更详细地讨论这一点。

从 AL 调用 Azure 函数

现在我们已将函数部署到 Azure,可以在扩展中的 AL 代码中使用它。

正如我们在第六章《高级 AL 开发》中的从 AL 调用 Web 服务和 API一节所解释的那样,我们可以通过使用HttpClient数据类型来调用 Azure 函数,该数据类型提供了一种发送 HTTP 请求和接收来自通过 URI 标识的资源的 HTTP 响应的方式。

为了测试我们的 Azure 函数,我们将创建一个简单的扩展(一个使用**AL:Go!**的新项目),它允许我们验证与客户记录关联的电子邮件地址。我们的CustomerEmailValidation扩展由一个代码单元对象组成,我们在其中定义了一个事件订阅者来监听Customer表中电子邮件字段的OnAfterValidate事件。在这个EventSubscriber过程(名为ValidateCustomerEmail)中,我们做了以下操作:

  • 使用HttpClient对象调用 Azure 函数。

  • 使用HttpResponse对象读取 Azure 函数的响应。

  • 解析 JSON 响应以提取Valid标记。

  • 如果Valid = false,则抛出错误。

这段代码如下所示:

codeunit 50100 EmailValidation_PKT{ [EventSubscriber(ObjectType::table, Database::Customer, \'OnAfterValidateEvent\', \'E-Mail\', false, false)] local procedure ValidateCustomerEmail(var Rec: Record Customer) var httpClient: HttpClient; httpResponse: HttpResponseMessage; jsonText: Text; jsonObj: JsonObject; funcUrl: Label \'https://sdemailvalidatorcore.azurewebsites.net/api/emailvalidatorcore?email=\'; InvalidEmailError: Label \'Invalid email address.\'; InvalidJonError: Label \'Invalid JSON response.\'; validationResult: Boolean; begin if rec.\"E-Mail\"  \'\' then begin httpClient.Get(funcUrl + rec.\"E-Mail\", httpResponse); httpResponse.Content().ReadAs(jsonText); //Response JSON format: {\"Email\":\"test@packt.com\",\"Valid\":true} if not jsonObj.ReadFrom(jsonText) then Error(InvalidJonError); //Read the Valid token from the response validationResult := GetJsonToken(jsonObj, \'Valid\').AsValue().AsBoolean();  if not validationResult then Error(InvalidEmailError); end; end; local procedure GetJsonToken(jsonObject: JsonObject; token: Text) jsonToken: JsonToken var TokenNotFoundErr: Label \'Token %1 not found.\'; begin if not jsonObject.Get(token, jsonToken) then Error(TokenNotFoundErr, token); end;}

按下F5并部署你的扩展。

如果打开客户卡,转到地址和联系方式标签页,并在电子邮件字段中插入一个值,您将收到以下消息:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/46ab5b45-695c-4291-9316-3eccc92eb978.png

这是您第一次这样做时发生的,因为默认情况下,沙箱环境会阻止外部 HTTP 调用。请选择“允许一次”并点击“确定”。

然后,我们的 Azure 函数被调用,JSON 响应被检索并解析,验证过程开始。如果你插入一个有效的电子邮件地址,值会正确插入到电子邮件字段中;否则,你将收到一个错误,如下图所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/e660b2dd-634a-4961-a65e-05c19a4acfdf.png

为了自动允许沙盒环境中的外部调用,在 NAV App Settings 表中,有一个名为 Allow HttpClient Requests 的布尔字段,当用户选择其中一个始终选项(始终允许或始终阻止)时,会在此表中插入一条记录,并将字段设置为 truefalse

你还可以通过 AL 代码以编程方式控制此设置。在我们的扩展中,我们添加了以下过程:

local procedure EnableExternalCallsInSandbox() var NAVAppSetting: Record \"NAV App Setting\"; EnvironmentInformation: Codeunit \"Environment Information\"; ModInfo: ModuleInfo; begin NavApp.GetCurrentModuleInfo(ModInfo); if EnvironmentInformation.IsSandbox() then begin NAVAppSetting.\"App ID\" := ModInfo.Id(); NAVAppSetting.\"Allow HttpClient Requests\" := true; if not NAVAppSetting.Insert() then NAVAppSetting.Modify(); end; end;

上述代码检索当前扩展的信息(ModuleInfo),然后检查扩展是否在沙盒环境中运行。如果是,它将 Allow HttpClient Requests 字段设置为 true

在我们之前的事件订阅者(ValidateCustomerEmail 过程)中,当需要时,我们通过调用 EnableExternalCallsInSandbox 过程来启用外部调用,如下所示:

begin if rec.\"E-Mail\"  \'\' then begin EnableExternalCallsInSandbox(); httpClient.Get(funcUrl + rec.\"E-Mail\", httpResponse);

这样可以避免在每次外部 HTTP 调用时看到确认请求。

在下一节中,我们将学习如何使用 Azure Functions 和 Azure Blob 存储来处理云中的文件。

与 Azure Blob 存储交互以处理云中的文件

我们在 Dynamics 365 Business Central 的云环境中遇到的主要问题之一与文件保存有关。正如我们之前提到的,在云环境中,你没有文件系统,无法访问本地资源,如网络共享或本地磁盘。

如果你想在 Dynamics 365 Business Central SaaS 中保存文件,解决方案是调用 Azure 函数并将文件存储到基于云的存储中。你可以创建一个将文件保存到 Azure Blob 存储的函数,之后你可以将 Azure 存储共享为网络驱动器。这是我们将在本节中介绍的场景。

创建 Azure Blob 存储帐户

实现我们解决方案的第一步是需要在 Azure 上拥有一个存储帐户,并且我们需要在该存储帐户中创建一个 Blob 容器。

要手动创建一个 Azure 存储帐户(在本例中为 d365bcfilestorage),请按照以下步骤操作:

  1. 在 Azure 门户中选择存储帐户,点击创建,并按照屏幕上的指示操作。要在此存储帐户中创建 Blob 容器,请从服务部分选择 Blobs:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/d3c3a374-0559-48a9-ad6c-9172a194b8ab.png

  1. 然后,点击容器,给它命名(在这里,我选择了 d365bcfiles),选择公共访问级别(默认情况下,容器数据对帐户所有者是私密的),然后点击确定:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/8c6c7a05-854a-40cc-9aaa-6bdd7febf6a6.png

现在,Blob 容器已经在你的 Azure 存储帐户中创建。

访问 Azure 存储账户的连接字符串(必须在 Azure 函数中使用)可以通过选择存储账户并点击“访问密钥”来获取:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/402218f9-682b-42f4-beef-412953308047.png

在这里,我们将把连接字符串嵌入到函数项目中,但在生产环境中,您可以将其存储在 Azure Key Vault 中,并从中检索以提高安全性。

在我们的 Blob 容器中,我手动上传了一个文件(PNG 图片)用于存储文件:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/24789192-0a65-4d50-93ab-4d94eb33b680.png

现在,我们已经在 Azure 存储上创建了一个 Blob 容器,以便托管我们的文件。在接下来的部分,我们将创建一个 Azure 函数,用于保存和检索这个 Blob 存储中的文件。

使用 Visual Studio 创建 Azure 函数

打开 Visual Studio,选择 HttpTrigger 模板并创建一个新的 Azure 函数项目。将函数命名为 SaaSFileMgt

在我们的项目中,我们想要创建以下几个函数:

  • UploadFile:此函数将通过 HTTP POST 请求接收一个包含二进制数据(文件内容)和一些元数据(文件详细信息)的对象。它会将文件存储在 Azure Blob 存储账户的容器中。

  • DownloadFile:此函数将接收一个包含要下载文件的 URI 和其详细信息的对象(通过 HTTP POST 请求),并返回存储在 Azure Blob 存储容器中的文件的二进制数据。

  • ListFiles:此函数将通过 HTTP GET 请求检索存储在 Azure Blob 存储容器中的所有文件的 URI 列表。这些文件将在接下来的部分中解释。

上传/下载文件的函数必须只支持 HTTP POST 方法,因此,在 HttpTrigger 定义模板中,我们已经移除了 get 参数。这些函数的签名如下:

[FunctionName(\"UploadFile\")]public static async Task Upload( [HttpTrigger(AuthorizationLevel.Function, \"post\", Route = null)] HttpRequest req, ILogger log)[FunctionName(\"DownloadFile\")]public static async Task Download( [HttpTrigger(AuthorizationLevel.Function, \"post\", Route = null)] HttpRequest req, ILogger log)

列出 Blob 存储中文件的函数必须只支持 GET 方法,函数签名如下:

[FunctionName(\"ListFiles\")]public static async Task Dir( [HttpTrigger(AuthorizationLevel.Function, \"get\", Route = null)] HttpRequest req, ILogger log)

让我们逐一探索这些函数。

UploadFile 函数

UploadFile 函数通过 HTTP POST 请求接收一个 JSON 对象,具体如下:

{ \"base64\": \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACVCAYAAAC3i3MLAA\", \"fileName\": \"MyFile.png\", \"fileType\": \"image/png\", \"fileExt\": \"png\"}

UploadFile 函数解析请求体中的 JSON,然后调用 UploadBlobAsync 函数。在此函数中,我们将文件上传到 Azure Blob 存储容器,并返回上传文件的 URI。

UploadFile 函数的代码如下:

[FunctionName(\"UploadFile\")]public static async Task Upload(  [HttpTrigger(AuthorizationLevel.Function, \"post\", Route = null)] HttpRequest req, ILogger log) { log.LogInformation(\"C# HTTP trigger function processed a request.\"); string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); string base64String = data.base64; string fileName = data.fileName; string fileType = data.fileType; string fileExt = data.fileExt; Uri uri = await UploadBlobAsync(base64String, fileName, fileType, fileExt);  return fileName != null ? (ActionResult)new OkObjectResult($\"File {fileName} stored. URI = {uri}\") : new BadRequestObjectResult(\"Error on input parameter (object)\");}

UploadBlobAsync 是一个将文件上传到 Azure 存储账户中的 d365bcfiles 容器的函数。其代码如下:

public static async Task UploadBlobAsync(string base64String, string fileName, string fileType, string fileExtension){ string contentType = fileType;  byte[] fileBytes = Convert.FromBase64String(base64String); CloudStorageAccount storageAccount = CloudStorageAccount.Parse(BLOBStorageConnectionString); CloudBlobClient client = storageAccount.CreateCloudBlobClient(); CloudBlobContainer container = client.GetContainerReference(\"d365bcfiles\"); await container.CreateIfNotExistsAsync( BlobContainerPublicAccessType.Blob, new BlobRequestOptions(), new OperationContext()); CloudBlockBlob blob = container.GetBlockBlobReference(fileName); blob.Properties.ContentType = contentType; using (Stream stream = new MemoryStream(fileBytes, 0, fileBytes.Length)) { await blob.UploadFromStreamAsync(stream).ConfigureAwait(false); } return blob.Uri;}

DownloadFile 函数

DownloadFile 函数通过 HTTP POST 请求接收一个 JSON 对象,具体如下:

{ \"url\": \"https://d365bcfilestorage.blob.core.windows.net/d365bcfiles/MasteringD365BC.png\", \"fileType\": \"image/png\", \"fileName\": \"MasteringD365BC.png\"}

该函数从请求体中检索文件的详细信息,然后调用 DownloadBlobAsync 函数。接着,它返回下载文件的内容(Base64 编码 字符串):

[FunctionName(\"DownloadFile\")]public static async Task Download( [HttpTrigger(AuthorizationLevel.Function, \"post\", Route = null)] HttpRequest req, ILogger log){ log.LogInformation(\"C# HTTP trigger function processed a request.\"); try { string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); string url = data.url; string contentType = data.fileType; string fileName = data.fileName; byte[] x = await DownloadBlobAsync(url, fileName); //Returns the Base64 string of the retrieved file return (ActionResult)new OkObjectResult($\"{Convert.ToBase64String(x)}\"); } catch(Exception ex) { log.LogInformation(\"Bad input request: \" + ex.Message); return new BadRequestObjectResult(\"Error on input parameter (object): \" + ex.Message); }}

DownloadBlobAsync 是连接到 Azure 存储 Blob 容器、检查文件并(如果文件存在)返回该文件的字节数组(流)的函数:

public static async Task DownloadBlobAsync(string url, string fileName){ CloudStorageAccount storageAccount = CloudStorageAccount.Parse(BLOBStorageConnectionString); CloudBlobClient client = storageAccount.CreateCloudBlobClient(); CloudBlobContainer container = client.GetContainerReference(\"d365bcfiles\"); CloudBlockBlob blob = container.GetBlockBlobReference(fileName); await blob.FetchAttributesAsync(); long fileByteLength = blob.Properties.Length; byte[] fileContent = new byte[fileByteLength]; for (int i = 0; i < fileByteLength; i++) { fileContent[i] = 0x20; } await blob.DownloadToByteArrayAsync(fileContent, 0); return fileContent;}

ListFiles 函数

ListFiles 函数通过 HTTP GET 请求(无参数)调用。它调用 ListBlobAsync 函数,然后返回存储在 Blob 容器中的文件 URI 列表(JSON 格式)。

它的代码定义如下:

[FunctionName(\"ListFiles\")] public static async Task Dir( [HttpTrigger(AuthorizationLevel.Function, \"get\", Route = null)] HttpRequest req, ILogger log) { log.LogInformation(\"C# HTTP trigger function processed a request.\"); string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); var URIfileList = await ListBlobAsync(); string json = JsonConvert.SerializeObject(URIfileList); return URIfileList != null ? (ActionResult)new OkObjectResult($\"{json}\")  : new BadRequestObjectResult(\"Bad request.\"); }

ListBlobAsync 函数连接到 Azure 存储容器,并使用 ListBlobsSegmentedAsync 方法检索存储在其中的 Blob 文件列表。

此方法通过使用 BlobContinuationToken 分段返回文件列表。当此令牌为 null 时,所有文件都会被检索:

public static async Task<List> ListBlobAsync(){ CloudStorageAccount storageAccount = CloudStorageAccount.Parse(BLOBStorageConnectionString); CloudBlobClient client = storageAccount.CreateCloudBlobClient(); CloudBlobContainer container = client.GetContainerReference(\"d365bcfiles\"); List URIFileList = new List(); BlobContinuationToken blobContinuationToken = null; do { var resultSegment = await container.ListBlobsSegmentedAsync(prefix: null,  useFlatBlobListing: true,  blobListingDetails: BlobListingDetails.None,  maxResults: null,  currentToken: blobContinuationToken,  options: null,  operationContext: null); // Get the value of the continuation token returned by the listing call. blobContinuationToken = resultSegment.ContinuationToken; foreach (IListBlobItem item in resultSegment.Results) { URIFileList.Add(item.Uri); } } while (blobContinuationToken != null); //Loop while the continuation token is not null. return URIFileList;}

现在,我们已经创建了 Azure 函数,是时候部署它们了。在下一部分,我们将学习如何进行部署。

部署 Azure 函数

我们的 Azure 函数可以直接从 Visual Studio 部署到我们的 Azure 订阅。要将这些函数部署到 Azure,你需要创建一个新的 Azure App Service 实例,如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/292d5a02-7c3e-406f-b1c1-2eccca5cc001.png

之后,将你的函数发布到这个 Azure App Service:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/e786f1bc-3529-4e23-9ff8-e4e001f556f4.png

如果你进入 Azure 门户,你将看到这些函数已经发布,并且你现在有一个公共 URL 来测试它们:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/70efcc40-93fa-4736-883a-6b578331ae11.png

现在,函数运行在 Azure 数据中心。在下一部分,我们将学习如何管理已部署函数的访问密钥(授权)。

管理 Azure Functions 密钥

Azure Functions 使用授权密钥来保护对 HTTP 触发的函数的访问。当你部署一个函数时,可以在以下授权级别之间进行选择:

  • 匿名:无需访问密钥。

  • 函数:访问该函数需要特定的访问密钥。

  • 管理员:需要主机密钥。

你可以通过从 Azure 门户选择你的函数并点击“管理”来直接管理这些访问密钥:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/f41732fd-9acb-4a01-8baa-3ea5357fb8ba.png

如果你想以编程方式管理密钥,还可以使用密钥管理 API(github.com/Azure/azure-functions-host/wiki/Key-management-API)。

关于如何管理 Azure Functions 授权密钥的更多信息可以在这里找到:docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook#authorization-keys

在本节中,你已经学会了如何处理 Azure 函数的访问密钥。在下一部分,我们将学习如何测试我们之前部署的 Azure 函数(从 Blob 存储上传和下载文件)。

测试 Azure 函数

在我们的场景中,Azure 函数已使用 Function 授权级别部署,因此我们需要通过传递一个 code 参数和从门户中获取的函数密钥来调用所需的函数。例如,要测试 ListFiles 函数,我们需要调用以下 URL:saasfilemgt.azurewebsites.net/api/ListFiles?code=FUNCTIONKEY

这是我们收到的响应(我们的 Blob 文件的 URI 列表):

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/2db0a962-b694-4ef5-848f-0bb31af85d01.png

要测试 DownloadFile 函数,我们需要通过传递以下参数的 JSON 对象(用于标识要检索的文件)发送 POST 请求到函数的 URL。

{ \"url\": \"https://d365bcfilestorage.blob.core.windows.net/d365bcfiles/MasteringD365BC.png\", \"fileType\": \"image/png\", \"fileName\": \"MasteringD365BC.png\"}

从 Visual Studio Code 启动 HTTP 请求,我们得到以下响应:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/bd884e9c-701c-4d9c-bee9-063b5dd6bdbb.png

如您所见,该函数从 Azure Blob 存储下载请求的文件(函数返回 Base64 字符串)。

在下一节中,我们将学习如何从 Dynamics 365 Business Central 的 AL 扩展中调用我们的 Azure 函数。

编写 Dynamics 365 Business Central 扩展

我们想在此创建的扩展是一个简单的应用程序,用于在 项目列表 页面上添加两个操作,用于上传和下载文件到/从 Azure Blob 存储。

此 AL 扩展将定义两个对象:

  • codeunit 对象具有调用处理 Azure Blob 存储文件的 Azure 函数的逻辑

  • 一个 pageextension 对象,为项目列表页面添加操作,并调用我们代码单元中定义的相关过程。

让我们更详细地查看这些内容。

Codeunit 定义

代码单元称为 SaaSFileMgt,包含两个过程:

  • UploadFile:此函数将处理上传到 Azure Blob 存储的文件。

  • DownloadFile:此函数将处理从 Azure Blob 存储下载文件。

在代码单元中,我们有两个全局变量,均包含要调用的 Azure 函数的 URL:

var BaseUrlUploadFunction: Label \'https://saasfilemgt.azurewebsites.net/api/UploadFile?code=YOURFUNCTIONKEY\'; BaseUrlDownloadFunction: Label  \'https://saasfilemgt.azurewebsites.net/api/DownloadFile?code=YOURFUNCTIONKEY\';

这里,YOURFUNCTIONKEY 是我们用于访问 Azure 函数的密钥(从 Azure 门户中选择函数并单击管理获取)。

UploadFile 过程定义如下:

 procedure UploadFile() var fileMgt: Codeunit \"File Management\"; selectedFile: Text; httpClient: HttpClient; httpContent: HttpContent; jsonBody: text; httpResponse: HttpResponseMessage; httpHeader: HttpHeaders; fileName: Text; fileExt: Text; base64Convert: Codeunit \"Base64 Convert\"; instr: InStream; begin UploadIntoStream(\'Select a file to upload\',\'\',\'\',selectedFile,instr); fileName := delchr(fileMgt.GetFileName(selectedFile), \'=\', \'.\' +  fileMgt.GetExtension(selectedFile)); fileExt := fileMgt.GetExtension(selectedFile); jsonBody := \' {\"base64\":\"\' + tempblob.ToBase64String() + \'\",\"fileName\":\"\' + fileName + \'.\' + fileExt + \'\",\"fileType\":\"\' + GetMimeType(selectedFile) + \'\", \"fileExt\":\"\' +  fileMgt.GetExtension(selectedFile) + \'\"}\'; httpContent.WriteFrom(jsonBody); httpContent.GetHeaders(httpHeader); httpHeader.Remove(\'Content-Type\'); httpHeader.Add(\'Content-Type\', \'application/json\'); httpClient.Post(BaseUrlUploadFunction, httpContent, httpResponse); //Here we should read the response to retrieve the URI message(\'File uploaded.\'); end;

从前述代码可以看到以下内容:

  1. 我们要求上传文件。

  2. 我们将文件读入 Stream 对象中。

  3. 我们检索与文件相关的一些参数(名称和扩展名)。

  4. 我们按照函数请求创建 JSON 消息(如前所述)。

  5. 然后,我们通过使用 HttpClient 对象向我们的 Azure 函数发送 HTTP POST 请求,将 JSON 放在主体中。

DownloadFile 过程定义如下:

 procedure DownloadFile(fileName: Text; blobUrl: Text) var tempblob: Codeunit \"Temp Blob\"; httpClient: HttpClient; httpContent: HttpContent; jsonBody: text; httpResponse: HttpResponseMessage; httpHeader: HttpHeaders; base64: Text; fileType: Text; fileStream: InStream; base64Convert: Codeunit \"Base64 Convert\"; outstr: OutStream; begin fileType := GetMimeType(fileName); jsonBody := \' {\"url\":\"\' + blobUrl + \'\",\"fileName\":\"\' + fileName + \'\", \"fileType\":\"\' +  fileType + \'\"}\'; httpContent.WriteFrom(jsonBody); httpContent.GetHeaders(httpHeader); httpHeader.Remove(\'Content-Type\'); httpHeader.Add(\'Content-Type\', \'application/json\'); httpClient.Post(BaseUrlDownloadFunction, httpContent, httpResponse); httpResponse.Content.ReadAs(base64); base64 := DelChr(base64, \'=\', \'\"\'); base64Convert.FromBase64(base64); tempblob.CreateOutStream(outstr); outstr.WriteText(base64); tempblob.CreateInStream(fileStream); DownloadFromStream(fileStream, \'Download file from Azure Storage\', \'\', \'\', fileName); end;

此过程接收要检索的文件名称作为输入。其工作方式如下:

  1. 它调用一个自定义函数(GetMimeType),返回此文件的内容类型,然后我们为请求组合 JSON 消息。

  2. 然后,我们发送一个 HTTP POST 请求到我们的 Azure 函数(通过使用HttpClient对象),并将 JSON 放入请求体中。

  3. 我们读取HttpResponse对象(获取文件的 Base64 编码),并通过使用InStream对象并调用DownloadFromStream方法将其下载到客户端:

GetMimeType工具的定义如下:

local procedure GetMimeType(selectedFile: Text): Textvar fileMgt: Codeunit \"File Management\"; mimeType: Text;begin case lowercase(fileMgt.GetExtension(selectedFile)) of \'pdf\': mimeType := \'application/pdf\'; \'txt\': mimeType := \'text/plain\'; \'csv\': mimeType := \'text/csv\'; \'png\': mimeType := \'image/png\'; \'jpg\': mimeType := \'image/jpg\'; \'bmp\': mimeType := \'image/bmp\'; \'gif\': mimeType := \'image/gif\'; else Error(\'File Format not supported!\'); end; EXIT(mimeType);end;

这个操作只是接收一个文件名作为输入,获取文件扩展名,然后返回与文件关联的 MIME 类型。

pageextension定义

pageextension对象通过添加我们之前描述的两个操作来扩展 Item List 页面。该对象的定义如下:

pageextension 50103 ItemListExt extends \"Item List\"{ actions { addlast(Creation) { Action(Upload) { ApplicationArea = All; Caption = \'Upload file to Azure Blob Storage\'; Image = Add; Promoted = true; trigger OnAction(); var  SaaSFileMgt: Codeunit SaaSFileMgt; begin  SaaSFileMgt.UploadFile(); end; } Action(Download) { ApplicationArea = All; Caption = \'Download file from Azure Blob Storage\'; Image = MoveDown; Promoted = true; trigger OnAction(); var  SaaSFileMgt: Codeunit SaaSFileMgt; begin  SaaSFileMgt.DownloadFile(\'TEST.txt\', \'https://d365bcfilestorage.blob.core.windows.net/d365bcfiles/TEST.txt\'); end; } } }}

这两个操作简单地通过传递所需的参数调用我们代码单元中定义的方法。

在下一节中,我们将测试集成的解决方案(由 Dynamics 365 Business Central 调用的 Azure 函数)。

测试我们的应用程序

现在,是时候测试我们的应用程序并查看它是如何工作的了。发布后,我们的扩展将在Item List页面上添加两个文件上传/下载的功能:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/603088b6-2b73-4541-bd2a-f47057f92fd9.png

如果你点击“上传文件到 Azure Blob 存储”操作,你可以从本地计算机选择一个文件:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/e21b0c5b-f7ce-4a8c-bc58-b905753d9c2d.png

这个文件将通过 HTTP POST 请求上传到我们的 Azure Blob Storage 容器。我们可以调试httpResponse对象,看到它返回HttpStatusCode = 200 (成功)

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/d315bb5a-8ebf-449e-b794-29c16b150cf2.png

我们将收到一条消息,告诉我们文件已成功上传到 Azure Blob Storage,如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/234275d8-b469-407b-ab85-bc5ba1908627.png

如果我们检查 Azure 存储账户中的 blob 容器,我们会看到文件已经正确上传到 blob 存储中:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/2f4d69e6-1705-4465-8f16-30013b6a7718.png

当你点击“从 Azure Blob Storage 下载文件”操作时,将执行一个 HTTP POST 请求到DownloadFile Azure 函数(通过传递 JSON 请求体,如我们之前所描述的),并且httpResponse对象返回HttpStatusCode = 200 (成功)

httpResponse对象是获取的文件的 Base64 编码。在这里,Base64 字符串被解码,文件通过使用InStream下载到客户端:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/ms-ms-dyna365/img/5295bfba-9cdf-4ee7-9aec-83f649c36721.png

如你所见,文件是从流中获取的,浏览器提示用户将其下载到本地。

现在,你可以在云环境中处理文件(上传和下载)。Azure 还允许我们将 Blob 存储映射到我们的本地网络,从而实现一个对最终用户完全透明的无服务器文件系统。

总结

在本章中,我们了解了什么是 Azure 函数,以及如何在我们的 Dynamics 365 Business Central 扩展中使用它们,以便在云环境中执行.NET 代码,并实现无服务器的流程。

我们学习了如何使用 Visual Studio 和 Visual Studio Code 创建一个简单的 Azure 函数,以及如何将 Azure Functions 与其他 Azure 服务一起使用(特别是如何结合 Azure Functions 和 Azure Blob Storage,在 Dynamics 365 Business Central 中实现云端文件系统)。

阅读本章后,你应该理解如何开发、部署和使用 Azure 函数,在完全无服务器的方式下在云中实现业务任务。在现代基于云的 ERP 中,这是一个非常重要的功能,需要掌握以扩展平台功能并融入其他云服务。

在下一章,我们将学习如何在云中监控和扩展我们的函数,并通过使用 Azure DevOps,将 DevOps 技术(持续集成和持续部署)应用到我们的 Azure Functions 项目中。

虫部落快搜