Lab 導覽
從零開始,建立一條跨
多租戶(Tenant)的 Dynamics 365 交付流水線
即使你從沒用過 Azure DevOps,也能照著本站一步步:註冊帳號 → 版控方案 → 建立 Pipeline → 佈署到 Sandbox 測試 → 核准上線正式環境,最後統一佈署到多個獨立 Tenant。
左側是章節導覽。第一次接觸建議從上到下讀:先建立名詞概念(01–03),再進入「04 Lab 逐步操作手冊」實作。手機上點左上角 ☰ 開選單。
本站涵蓋什麼
01 · 入門
先懂這些名詞
在動手前,先花五分鐘把這些字認熟。之後每個操作步驟都會用到它們。
圖:Tenant ⊃ Environment ⊃ Solution。開發在左邊 Dev 改,Pipeline 打包後送到右邊 Prod。
| 名詞 | 白話解釋 |
|---|---|
| Tenant | 一間公司在微軟雲上的「帳號世界」。每間子公司各有一個,彼此獨立。 |
| Environment | Power Platform 的一個「資料庫+容器」。同一個 Tenant 內可以有多個,用來分 Dev / Sandbox / Prod。 |
| Dataverse | Environment 背後存資料的平台(表格、欄位都放這)。 |
| Solution 方案 | 把你做的客製(表格、表單、App、流程、程式)包成一包,方便搬到別的環境。 |
| Unmanaged | 「可編輯」的方案,只存在 Dev。這是你版控與修改的來源。 |
| Managed | 「唯讀、可乾淨解除安裝」的方案,由 Pipeline 打包產生,只進 Test / Prod。 |
| Azure DevOps | 微軟的開發協作平台:放程式碼(Repos)、跑自動化(Pipelines)、管測試(Test Plans)。 |
| Repo | Git 版控倉庫,存放拆解後的方案原始碼,可以看每次改了什麼。 |
| Pipeline | 一段自動化流程腳本(YAML)。例如「把方案打包再佈署到某環境」。 |
| Service Principal (SPN) | 給機器用的「應用程式帳號」,讓 Pipeline 不需要人輸入密碼就能登入某個 Tenant 佈署。 |
| Application User | 把上面那個 SPN 加進某個 Environment,賦予它權限。 |
| Service Connection | Azure DevOps 裡設定的一條「通往某環境的連線」,Pipeline 靠它連上目標 Tenant。 |
| pac CLI | Power Platform 的命令列工具,本機用來匯出 / 匯入方案。 |
你在 Dev 環境改 Solution(Unmanaged)→ 存進 Repo → Pipeline 打包成 Managed → 透過 Service Connection(背後是 SPN)佈署到各 Tenant 的 Prod。
02 · 概念
整體架構
一個集中的 Azure DevOps 管所有版控與自動化;透過各租戶的專屬連線,把方案佈署到多個獨立的 Tenant。
圖:單一 DevOps 起點,透過各租戶專屬連線統一佈署到多個獨立 Tenant,每個 Tenant 內有 Sandbox 與 Prod。
混合模式:共用核心 × 在地擴充
你們的情境同時有「集團共用邏輯」與「各子公司差異」,因此方案分兩層:
集團共用核心方案
中央團隊維護,一次開發,透過 Pipeline 同一版本統一佈到多個 Tenant,確保一致。
各子公司區域方案
各子公司自行擴充在地化欄位、報表、整合,各自一套 Dev→Sandbox→Prod,只推自己的 Tenant。
Regional 方案相依於 Core,因此佈署順序永遠是先 Core、後 Regional。Pipeline 已內建此順序。
四階段流水線
前兩段(01、02)不碰任何環境,是純版控與建置;後兩段(03、04)才真正佈署到租戶。04 Lab 手冊會帶你把這四段全部建起來。
03 · 概念
ALM 與方案策略
ALM(應用程式生命週期管理)的核心就四條規則。記住它們,後面的 Pipeline 設計就都合理了。
Dev 只放 Unmanaged
只有 Dev 環境可以編輯;它是你版控的唯一來源。
Test / Prod 只收 Managed
由 Pipeline 打包產出,唯讀、可乾淨解除安裝。
絕不在正式環境手改
要改就回 Dev 改,重走流程。
一切走 Git → PR → Pipeline
不走人工匯出匯入,才有稽核與品質關卡。
為什麼要拆解成原始碼?
方案匯出是一個 .zip,直接進 Git 無法比對差異。所以我們用 pac solution unpack 把它拆成上千個檔案,這樣每次改了哪個表單、哪個欄位,PR 上一目了然。
# 匯出並拆解(Lab 會用腳本 export-local.ps1 幫你做)
pac solution export --name ContosoCore --managed false
pac solution unpack --folder solutions/ContosoCore/src
跨環境最容易踩雷:連線與環境變數
Flow 的連線(Connection Reference)與環境相依的設定(Environment Variable),在搬到不同環境時不能把 Dev 的值帶過去。做法是用一份 deployment settings JSON,在匯入時重新綁定目標環境的正確值。
同一份打包好的方案,換一份 settings 檔 + 換一條 Service Connection,就能佈到不同 Tenant 的 Sandbox 或 Prod —— 這正是「一次建置、多處佈署」的關鍵。
04 · 動手做
Lab 逐步操作手冊
假設你完全沒用過 Azure DevOps。我們從註冊帳號開始,一路做到跨租戶正式上線。建議先用「一到兩間子公司」跑通,再複製到其餘子公司。全程約半天。
每張畫面都是「schematic 示意」並附圖片編號(如 image08)。當你實際操作後截圖,只要告訴我「image08 換這張」,我就能把對應的示意圖替換成你的真實截圖。點下方任一卡片可直接跳到該階段。
動手前:需要哪些授權?
做這個 Lab(以及日後正式導入)需要下列授權與角色到位。多間子公司各自獨立 Tenant,下列項目每個 Tenant 都要各自具備。Lab 練習可先用免費方案在一到兩間跑通。
| 項目 | 需要的授權 / 角色 | 用途 | Lab 免費起步 |
|---|---|---|---|
| Azure DevOps | Basic 授權(每 Org 前 5 位使用者免費) | Repos 版控、Pipelines 自動化 | ✓ 免費 |
| Azure Test Plans | Basic + Test Plans 授權(付費) | Sandbox 手動 UAT / 迴歸測試 | 試用 30 天 |
| Power Platform 環境 | Power Apps Developer Plan,或 Per-app / Per-user 授權;正式另需 Dataverse 容量 | Dev / Sandbox / Prod 環境 | ✓ Developer Plan 免費 |
| Power Platform 系統管理員 | Power Platform Administrator 角色 | 建環境、加 Application User | 需管理員指派 |
| Microsoft Entra ID | Application Developer 角色(或全域管理員) | 建立跨租戶佈署用的 App 註冊(SPN) | 需管理員指派 |
| Premium 連接器(視需要) | Power Automate Premium / Per-user | Flow 若用到自訂 API、HTTP、Approvals 等進階連接器 | 試用可用 |
| Azure 訂用帳戶(建議) | 可建立 Key Vault 的訂用帳戶 | 正式環境存放密鑰(免密鑰聯合認證亦需) | 正式階段 |
| 開發工具 | —(免授權) | VS / VS Code、Git、pac CLI、.NET SDK | ✓ 全免費 |
只想先跑通流程:1 個 Azure DevOps Org(免費)+ 2 個 Power Platform Developer 環境(免費)+ 你自己是該租戶管理員即可完成 Phase 0–7。Test Plans 與 Key Vault 可先用試用或跳過自動化測試。
八個階段
準備帳號與工具
⏱ 約 60 分鐘 · 一次性設定
動手前,請先對照「概覽」頁的授權需求表,確認 Azure DevOps、Power Platform 環境與管理員角色到位。最低可跑組合:1 個免費 DevOps Org + 2 個 Developer 環境 + 你是該租戶管理員。
建立 Azure DevOps 帳號與組織(Organization)
用瀏覽器開 dev.azure.com,點「Start free」,用你的微軟帳號登入。第一次登入會請你建立一個 Organization(就是你團隊的最上層容器)。
image01 Azure DevOps 首頁 · 點 Start free 登入建立 Organization
開始免費使用,管理程式碼、Pipeline 與測試
Start free 🖱Organization 名稱建議用集團代號,如 contoso-group。之後所有子公司的 Repo 與 Pipeline 都在這底下。
為每個 Tenant 建立 Power Platform 環境
到 admin.powerplatform.microsoft.com(Power Platform 系統管理中心),為練習先建立三個環境:Core-Dev、SubA-Sandbox、SubA-Prod。沒有付費環境可用免費的 Developer 類型起步。
image02 Power Platform 系統管理中心 · 建立環境(Sandbox/Developer)
正式練習跨租戶時,SubA-* 要建在 SubA 那個 Tenant 底下(用該 Tenant 的帳號登入)。先在單一 Tenant 練也可以,之後再複製。
安裝本機工具
安裝 Git、VS Code,以及 Power Platform CLI(pac)。開一個終端機執行:
# 安裝 pac CLI(需先有 .NET SDK)
dotnet tool install --global Microsoft.PowerApps.CLI.Tool
# 驗證安裝成功
pac help
pac help 有列出指令清單,代表安裝成功,Phase 0 完成。
建立 Project 與 Repo
⏱ 約 20 分鐘
在 Organization 裡建立一個 Project
進入你的 Organization,右上角點「New project」。命名 Contoso-PowerPlatform,Visibility 選 Private。
image03 建立 Project(New project · Private)
把本 Lab 的 repo 內容放進 Repos
左側選單點 Repos。把配套檔案(pipelines/、docs/、config/、scripts/)推上來。若你手邊已有本 Lab 資料夾:
git init
git add .
git commit -m "初始化 ALM Lab"
git remote add origin https://dev.azure.com/contoso-group/Contoso-PowerPlatform/_git/ContosoCore
git push -u origin main
image04 Repos 檔案結構(push 後)
安裝 Power Platform Build Tools 擴充
點右上齒輪 → Organization settings → Extensions → Browse marketplace,搜尋「Power Platform Build Tools」(發行者 microsoft-IsvExpTools),安裝到你的 Organization。裝一次,全 Org 的 Pipeline 都能用。
image05 安裝 Power Platform Build Tools 擴充
Extensions 清單看得到已安裝的 Build Tools,Phase 1 完成。
跨租戶身分(最關鍵的一段)
⏱ 約 45 分鐘 · 每個目標環境做一次
這是整個 Lab 最容易卡關、也最重要的一段。目標:讓 Pipeline 不用人輸入密碼,就能登入某個 Tenant 的環境去佈署。做法是建立一個「機器人帳號」(Service Principal),讓三方彼此認得。
身分驗證有「三隻腳」:① Entra ID 裡的 App(機器人本體 + 密鑰)② Dataverse 環境把它認成一個有權限的使用者 ③ Azure DevOps 用一條 Service Connection 拿著密鑰去登入。三者都對上,Pipeline 才佈得動。
進入該 Tenant 的 App 註冊頁
用該 Tenant 的系統管理員登入 portal.azure.com,上方搜尋列打「Microsoft Entra ID」進入 → 左側點 App registrations → 上方 + New registration。
image06 Entra ID › App registrations › New registration
登錯 Tenant!右上角確認目前是 SubA 的 Tenant。各子公司 Tenant 各自獨立,App 一定要建在「要佈署的那個 Tenant」裡。
註冊 App 並記下兩組 ID
Name 填 PP-DevOps-SubA;「Supported account types」選第一個「僅此組織目錄」;Redirect URI 留空。按 Register。建立後在 Overview 記下 Application (client) ID 與 Directory (tenant) ID。
image07 註冊 App 並記下 Client ID / Tenant ID
✓ Application (client) ID: a1b2c3d4-…-4444
✓ Directory (tenant) ID: t1t2t3t4-…-9999
建立 Client Secret(密鑰)
左側 Certificates & secrets → + New client secret → 填說明、選到期日(Lab 選 90 天或 1 年)→ Add。畫面會出現一組 Value,立刻複製。
image08 建立 Client Secret(Value 只顯示一次)
離開頁面後 Value 就再也看不到。沒複製到只能刪掉重建。正式環境強烈建議改用免密鑰的 Workload Identity Federation(見「08 治理與資安」)。
把這個 App 加進目標環境,當 Application User
現在這個機器人存在於 Entra ID,但 Dataverse 環境還不認得它。到 Power Platform 系統管理中心把它加進去。兩種做法,擇一:
做法 A — 用畫面(推薦新手)
image09 於目標環境新增 Application User + 角色
做法 B — 用腳本(快):先用系統管理員 pac auth create 登入該 Tenant,再跑本 Lab 腳本:
./scripts/setup-appuser.ps1 \
-EnvironmentUrl https://subA-sbx.crm5.dynamics.com \
-AppId a1b2c3d4-…-4444
這步等於「幫機器人在該環境開一個管理員使用者」。Lab 用 System Administrator 最省事;正式環境建議改成剛好夠匯入方案的自訂角色。
在 Azure DevOps 建立 Service Connection
回到 DevOps 的 Project → 左下 Project settings → Service connections → New service connection → 選 Power Platform,把前面收集的四樣東西填進去:環境 URL、Tenant ID、Client ID、Client Secret。
image10 建立 Power Platform Service Connection
PP-<範圍>-<子公司>-<階段>,例如 PP-Core-SubA-Prod、PP-Regional-SubA-Sandbox。一致命名讓 Pipeline 參數化很乾淨。每個環境一條連線。
驗證連線是否真的通
別急著往下!先確認三隻腳有對上。最快的方式:建一個一次性 Pipeline 用該連線跑 pac org who,或直接在 Service connection 頁點 Verify。若失敗,對照下表排查。
| 錯誤訊息 | 原因 | 回哪一步 |
|---|---|---|
| 401 / 未授權 | Secret 貼錯或已過期 | 步驟 3 重建 secret |
| 找不到使用者 / 無權限 | App 未加進環境 | 步驟 4 |
| 環境找不到 | URL 打錯或登錯 Tenant | 步驟 5 檢查 Server URL |
Verify 成功、或 pac org who 印出目標環境資訊 → Phase 2 完成。之後每多一個目標環境(SubA-Prod、SubB-Sandbox…)就重複這 6 步。
用範例程式碼建立方案並驗證
⏱ 約 90 分鐘
本階段會用配套 repo 的範例程式碼(Plugin、表單 JS、Power Automate 流程),在 Core-Dev 建立一個真正能動的方案,並逐項驗證。程式碼位置:solutions/ContosoCore/。
請在 Core-Dev 建立 Solution 與 Publisher
請以瀏覽器開啟 make.powerapps.com,右上角切換到 Core-Dev 環境,左側點 Solutions → New solution。名稱填 ContosoCore,並新增 Publisher,前綴填 contoso,按 Create。
image11 建立 Solution 與 Publisher(前綴 contoso)
Publisher 前綴(如 contoso_)會出現在所有欄位名稱裡,Lab 一開始就要想好。
請建立 Customer 資料表與四個欄位(對應範例 Entity.xml)
在方案內按 New → Table,名稱 Customer。接著按 + New column 依下表建立四個欄位——這些正是範例程式碼會用到的欄位。同時新增一個 Environment Variable contoso_ApiBaseUrl(示範跨環境重新綁定)。
| 顯示名稱 | 邏輯名稱 | 類型 | 必填 |
|---|---|---|---|
| 客戶名稱 | contoso_name | 單行文字(主要欄位) | 是 |
| 客戶編號 | contoso_accountnumber | 單行文字 | 否 |
| 電子郵件 | contoso_email | 單行文字(Email 格式) | 否 |
| 信用額度 | contoso_creditlimit | 貨幣 | 否 |
在資料表的 Columns 清單看到以上四個 contoso_ 欄位,即完成。
請先在本機建置並測試範例 Plugin(伺服器端邏輯)
Plugin 是手寫的 C# 程式碼,先在本機驗證邏輯再上線。請開終端機進到 Plugin 專案,執行建置與單元測試:
# 首次:產生強式簽署金鑰
sn -k solutions/ContosoCore/plugins/ContosoCore.Plugins/ContosoCore.Plugins.snk
# 建置 + 跑 FakeXrmEasy 單元測試(不需連線環境)
dotnet test solutions/ContosoCore/plugins/ContosoCore.Plugins.Tests
image12 本機 dotnet test 結果(4 個測試通過)
看到 Passed: 4 代表 CustomerPreCreatePlugin 的邏輯正確。這一步的驗證之後也會由 Pipeline(Phase 5)自動重跑。
請把 Plugin 註冊到 Core-Dev,並掛在客戶建立事件上
用 pac 將組件推上環境,再於方案的 Customizations 依範例 Customizations.xml 的設定新增處理步驟(Message = Create、Table = contoso_customer、Stage = PreOperation、同步):
pac auth create --url https://core-dev.crm5.dynamics.com
pac plugin push --path solutions/ContosoCore/plugins/ContosoCore.Plugins/bin/Release/net462/ContosoCore.Plugins.dll
開 Model-driven App 新增一筆客戶:
① 不填客戶編號按 Save → 客戶編號自動變成 CUST-yyyyMMdd-nnnn。
② 再建一筆同名客戶 → 出現錯誤「已存在相同名稱的客戶」被擋下。
請加入表單 JavaScript(用戶端驗證)
在方案內按 New → More → Web resource,類型選 JavaScript (JS),名稱 contoso_/customer/form.js,內容上傳範例檔 src/WebResources/contoso_/customer/form.js。接著打開 Customer 主表單,於 Form libraries 加入此 Web Resource,並掛事件:OnLoad → Contoso.Customer.onLoad、OnSave → Contoso.Customer.onSave、Email 欄位 OnChange → Contoso.Customer.validateEmail。
image13 表單事件掛載(OnLoad / OnSave / OnChange)
在客戶表單的 Email 欄輸入 abc(錯誤格式)→ 欄位即時出現紅字提示;按 Save 會被攔截無法儲存。改成 a@contoso.com 後即可存檔。
請加入 Power Automate 訂單核准流程(低程式碼)
在方案內按 New → Automation → Cloud flow,參照範例 Workflows/OrderApproval/definition.json 的邏輯:觸發「當訂單建立」,判斷金額,> 50000 送 Approvals 核准、否則自動核准。連線請用 Connection Reference(contoso_dataverse、contoso_approvals),不要直接綁個人連線。
這樣跨環境搬移時,才能由 deployment settings 重新綁定各環境的連線(Phase 6 會用到)。直接綁個人連線會導致佈署後流程失效。
手動建立一筆金額 60000 的訂單 → 流程執行記錄顯示已送出核准要求;建立金額 1000 的訂單 → 狀態直接標記為已核准。
請發佈所有客製,準備進版控
回到方案,按 Publish all customizations。至此你已用範例程式碼做出一個能動的方案,下一階段(Phase 4)會把它匯出進 Git。
資料表/欄位 → Columns 清單可見 ✓
Plugin(C#) → 本機 dotnet test 4 綠 + 真環境自動編號/擋重複 ✓
表單 JS → Email 錯誤即時提示、存檔被擋 ✓
Power Automate → 大額訂單送核准、小額自動核准 ✓
版控:匯出、Commit、開 PR
⏱ 約 25 分鐘
把 Dev 的方案匯出成原始碼
用本 Lab 腳本一鍵完成(也可用 Pipeline 01-export-and-commit.yml):
./scripts/export-local.ps1 -EnvironmentUrl https://core-dev.crm5.dynamics.com -SolutionName ContosoCore
它會匯出 Unmanaged 並拆解到 solutions/ContosoCore/src/。
Commit 並開 Pull Request
git checkout -b feature/init-core
git add solutions/ContosoCore/src
git commit -m "新增 Customer 表與訂單流程"
git push origin feature/init-core
回 DevOps Repos → 會出現「Create a pull request」提示,點它,Target 選 main,建立 PR。
image14 開 Pull Request(feature → main)
開 PR 會自動觸發下一階段的 Build Pipeline 當作品質關卡。
Build Pipeline:打包 + 檢查
⏱ 約 20 分鐘
建立指向 YAML 的 Pipeline
左側 Pipelines → New pipeline → 選 Azure Repos Git → 選你的 repo → 選「Existing Azure Pipelines YAML file」→ 指向 /pipelines/02-build-managed.yml。
image15 建立 Pipeline 指向 02-build-managed.yml
觀察它跑並產出 artifact
Pipeline 會:打包 Managed → 跑 Solution Checker → 蓋版本號 → 發佈 drop_ContosoCore artifact。若 Checker 發現嚴重問題,Pipeline 會失敗(這就是品質關卡)。修正後重跑通過,合併 PR。
Pipeline 綠燈,且在該次執行的 Artifacts 看到 ContosoCore_managed.zip。
佈署到 Sandbox + 觸發測試
⏱ 約 40 分鐘
填好 deployment settings
編輯 config/deployment-settings/regional.subA.test.json,把連線 ID 與環境變數值填成 SubA-Sandbox 的正確值。
建立 release-sandbox Pipeline
同樣方式指向 /pipelines/03-release-sandbox.yml,設定為 Build 完成後觸發。它會用 PP-Regional-SubA-Sandbox 連線匯入方案,並套用 settings 重新綁定連線。
同一份 artifact,換 Service Connection + 換 settings 檔,就佈到不同 Tenant 的 Sandbox。要換成 SubB?換連線名稱即可。
在 Azure Test Plans 建立測試
左側 Test Plans → New Test Plan(SubA-Sandbox-UAT)→ 加 Test Suite 與 Test Case(一步一步的驗收步驟)。Pipeline 佈署完成會建立 Test Run,通知測試人員執行。
image16 Azure Test Plans 測試套件與結果
SubA-Sandbox 環境出現該方案,且 Flow 連線指向測試環境的值而非 Dev。測試案例可執行並標記通過。
核准後佈署到正式
⏱ 約 30 分鐘
建立帶核准的 Environment
Pipelines → Environments → New environment,命名 SubA-Prod。進去後 → 右上 ⋯ → Approvals and checks → 加 Approvals,指定核准人(先指定你自己)。
image17 設定 Environment 核准人(Approvals)
執行 release-production Pipeline
指向 /pipelines/04-release-production.yml,手動觸發。它會在佈署 SubA-Prod 前停下等待核准;你按核准後才匯入。
image18 佈署前等待核准 → 按 Approve
SubA-Prod 出現方案;DevOps 上可完整追溯「哪個 commit → 哪個 build → 誰核准」。
統一佈署到更多 Tenant
⏱ 約 20 分鐘 · 每多一間重複一次
為 SubB 重複身分設定
回到 Phase 2,為 SubB 的環境建立 SPN、Application User、Service Connection(PP-Core-SubB-Prod),並建 Variable Group env-subB。
啟用統一佈署 stage
在 04-release-production.yml 啟用 Deploy_SubB_Prod stage,觸發一次,觀察同一版 Core 依序統一佈署:先導試佈 SubA,通過後再推 SubB 的正式環境。
「新增下一間子公司?建環境 + 一組 Service Connection + 複製一個 stage,一天內就能上線流程。」擴到更多 Tenant 只是重複這兩步。
卡關時看這裡
| 症狀 | 可能原因 | 解法 |
|---|---|---|
| 匯入失敗 missing dependency | Regional 先於 Core 匯入 | 確保先佈 Core |
| Flow 匯入後不能跑 | Connection Reference 未綁定 | 檢查 settings 的 ConnectionId |
| Pipeline 連不上環境 | SPN 未加為 Application User | 重跑 Phase 2 步驟 4 |
| Checker 一直失敗 | ruleset 太嚴或真有問題 | 調 ErrorLevel 或修正方案 |
你已經跑通一條從註冊帳號到跨租戶上線的完整流水線。要複製到其餘子公司,只需重複 Phase 2 的身分設定與新增一個佈署 stage。
程式碼範例
Dynamics 365 / Power Platform 程式碼
Power Platform 的「程式碼」有四種形式,分屬不同層級。配套 repo 的 solutions/ 內都有實際範例;以下摘錄重點,完整檔案見對應路徑。
| 形式 | 語言 | 範例檔 | 誰寫 / 怎麼進 Git |
|---|---|---|---|
| 宣告式方案 | XML | Solution.xml · Entity.xml | 平台匯出,pac unpack 拆檔 |
| 伺服器端邏輯 | C# | CustomerPreCreatePlugin.cs | 開發者手寫,dotnet 專案 |
| 用戶端邏輯 | JavaScript | customer/form.js | 開發者手寫,掛表單事件 |
| 低程式碼流程 | JSON | OrderApproval/definition.json | Power Automate 設計器,unpack |
XML / Flow JSON 是平台產生的 —— 在 Dev 設計器點好、export 才變檔案,不要手改。C# Plugin 與 JS Web Resource 才是你在 IDE 真正手寫、可直接編譯執行的程式碼。
① C# Plugin —— 伺服器端商業邏輯
solutions/ContosoCore/plugins/ContosoCore.Plugins/CustomerPreCreatePlugin.cs。建立客戶前:名稱必填、擋重複、自動產生客戶編號。註冊於 Create / contoso_customer / PreOperation / 同步。
public sealed class CustomerPreCreatePlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider
.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
// 只處理 Target 是 Entity 的情況
if (!context.InputParameters.TryGetValue("Target", out var obj)
|| !(obj is Entity target)) return;
if (target.LogicalName != "contoso_customer") return;
var name = target.GetAttributeValue<string>("contoso_name");
if (string.IsNullOrWhiteSpace(name))
throw new InvalidPluginExecutionException("客戶名稱為必填。");
if (CustomerWithNameExists(service, name))
throw new InvalidPluginExecutionException($"已存在相同名稱的客戶「{name}」。");
if (string.IsNullOrWhiteSpace(
target.GetAttributeValue<string>("contoso_accountnumber")))
{
target["contoso_accountnumber"] =
$"CUST-{DateTime.UtcNow:yyyyMMdd}-{new Random().Next(1000, 9999)}";
}
}
// … CustomerWithNameExists 用 QueryExpression 查同名啟用中的客戶
}
單元測試(FakeXrmEasy,不需連線環境)
[Fact(DisplayName = "未填客戶編號時,應自動產生 CUST- 開頭的編號")]
public void Generates_account_number_when_empty()
{
var ctx = new XrmFakedContext();
var target = new Entity("contoso_customer") { ["contoso_name"] = "Contoso 電子" };
var pluginCtx = ctx.GetDefaultPluginContext();
pluginCtx.InputParameters = new ParameterCollection { { "Target", target } };
ctx.ExecutePluginWith<CustomerPreCreatePlugin>(pluginCtx);
Assert.StartsWith("CUST-", target.GetAttributeValue<string>("contoso_accountnumber"));
}
CI 會執行 dotnet test,四個測試不過就擋 PR(見「07 測試」)。
② JavaScript Web Resource —— 表單邏輯
solutions/ContosoCore/src/WebResources/contoso_/customer/form.js。用 Xrm Client API 做即時驗證與儲存攔截。
function onSave(executionContext) {
var formContext = executionContext.getFormContext();
if (!isEmailValid(formContext)) {
executionContext.getEventArgs().preventDefault(); // 取消儲存
formContext.ui.setFormNotification(
"Email 格式不正確,請確認後再儲存。", "ERROR", "contoso_email_invalid");
}
}
function isEmailValid(formContext) {
var value = formContext.getAttribute("contoso_email").getValue();
return !value || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
③ Power Automate 流程 —— 低程式碼(JSON)
solutions/ContosoCore/src/Workflows/OrderApproval/definition.json。訂單金額 > 50000 走主管核准,否則自動核准;連線用 Connection Reference,由 deployment settings 綁定各環境。
// 條件節點(節錄)
"Condition_amount_over_threshold": {
"type": "If",
"expression": { "greater": [ "@outputs('Get_order_total')?['body/contoso_totalamount']", 50000 ] },
"actions": { "Start_and_wait_for_approval": { /* 送 Approvals 連接器 */ } },
"else": { "actions": { "Auto_approve_small_order": { /* 直接標記核准 */ } } }
}
④ 區域方案擴充(混合模式重點)
solutions/ContosoSubA/src/WebResources/csa_/order/localtax.js。SubA 在集團共用的訂單表單上,加自己的「當地稅額」計算——讀 Core 的欄位 contoso_totalamount、寫 SubA 的 csa_localtaxamount,稅率來自環境變數 csa_localtaxrate。
SubA 的程式參照了 Core 的欄位,Core 必須先存在——這正是佈署順序固定「先 Core、後 Regional」的原因。
這些程式碼如何進 CI
對應 pipelines/02-build-managed.yml:先測 Plugin(不過就擋 PR),再打包整個方案,最後靜態分析。
05 · 參考手冊
環境與租戶策略
要規模化到多間子公司之前,先把環境數量、命名規範與連線對照表定清楚。這一章是實作時的查表依據。
環境數量規劃
混合模式下,環境分成 Core(集團)與 Regional(各子公司)兩類。
Core(集團共用)
| 環境 | 類型 | 位置 | 用途 |
|---|---|---|---|
| Core-Dev | Developer | 集團主 Tenant | 中央團隊唯一開發環境,Unmanaged |
| Core-Build | Sandbox | 集團主 Tenant | (選用)打包驗證 Managed 的乾淨環境 |
| Core 的 Test/Prod | — | 在各子公司 Tenant 內 | Core managed 佈到各 Tenant 的 Sandbox / Prod |
Core 沒有自己獨立的「Test/Prod」—— 它的測試與正式,就是跨多個 Tenant 的 Sandbox 與 Prod。
Regional(每間子公司 ×5)
| 環境 | 類型 | 用途 |
|---|---|---|
| SubX-Dev | Developer | 該子公司開發,Unmanaged |
| SubX-Sandbox | Sandbox | 整合測試 / UAT,跑 Azure Test Plans |
| SubX-Prod | Production | 正式營運 |
一間子公司 3 個 × 5 = 15 個 Regional 環境,加上 Core 1–2 個,總計約 16–17 個環境。Lab 練習先用一到兩間跑通即可。
Service Connection 命名規範
一致的命名讓 Pipeline 參數化與統一佈署迭代很乾淨:
# 格式
PP-<Scope>-<Sub>-<Stage>
# 範例
PP-Core-SubA-Sandbox # Core 佈到 SubA 的 Sandbox
PP-Core-SubA-Prod
PP-Regional-SubA-Dev # SubA 自己的 Dev(給 export 用)
PP-Regional-SubA-Sandbox
PP-Regional-SubA-Prod
環境對照表(填入客戶實際值)
| Sub | Tenant ID | Dev URL | Sandbox URL | Prod URL |
|---|---|---|---|---|
| SubA | tenantA-guid | subA-dev… | subA-sbx… | subA-prod… |
| SubB | tenantB-guid | … | … | … |
| SubC | … | … | … | … |
| SubD | … | … | … | … |
| SubE | … | … | … | … |
這張表建議放進 Azure DevOps 的 Variable Group 或 Key Vault,Pipeline 以變數引用,方便維護也較安全。
06 · 參考手冊
Azure DevOps 設定參考
Lab 手冊帶你點過一遍;這一章把 Project 結構、Variable Group、Environments 與核准的設定原則整理成查表用的參考。
Project 與 Repo 結構
用單一 Project + Repo/Pipeline 權限做子公司隔離,通常已足夠;若集團要更強隔離,也可「一個 Project 一間子公司」。
Variable Group(集中管理環境值)
Pipelines → Library → 新增 Variable Group,例如 env-subA:
| 變數 | 值 | 加密 |
|---|---|---|
| devServiceConnection | PP-Regional-SubA-Dev | — |
| sandboxServiceConnection | PP-Regional-SubA-Sandbox | — |
| prodServiceConnection | PP-Regional-SubA-Prod | — |
| sandboxUrl | https://subA-sbx… | — |
| testPlanId | 1234 | — |
Secret、connection id 這類敏感值放 Azure Key Vault 並在 Variable Group 連結,而非明文寫在群組裡。
Environments 與核准關卡
在 Pipelines → Environments 建立 SubA-Sandbox、SubA-Prod 等,並在 Prod 環境設定 Approvals and checks:
指定核准人
如 SubA 負責人 + 集團架構師;佈署前強制人工放行。
只允許 main
避免有人用非正式分支的內容佈到正式。
條件式放行
只在維護時窗佈署、或查詢變更單 / 測試通過率達標才放行。
把 Service Connection 設為「需核准才能被新 Pipeline 使用」,防止有人寫個 Pipeline 亂用正式連線。詳見「08 治理與資安」。
07 · 進階
測試與 Azure Test Plans
越底層的測試越快、越多、越早跑;Sandbox 用 Test Plans 做人工驗收與迴歸把關。
| 層級 | 工具 | 何時跑 |
|---|---|---|
| 單元測試 | xUnit + FakeXrmEasy | Build 階段(不需環境) |
| 靜態分析 | Solution Checker | Build 階段 |
| 自動化 UI | Power Apps Test Engine | Sandbox 佈署後 |
| 手動 UAT / 迴歸 | Azure Test Plans | Sandbox 佈署後 |
與 Pipeline 串接
Sandbox 佈署成功後,Pipeline 呼叫 REST API 建立 Test Run、通知測試人員在 Test Plans 執行手動驗收;進階可把 Test Case 關聯自動化測試由 Pipeline 直接跑並回填結果。
正式佈署(Phase 7)的核准可設定條件:Test Plan 通過率 ≥ 95% 才放行,未過就擋在 Sandbox。
08 · 進階
治理與資安
跨多個獨立 Tenant 的自動化佈署,身分與可追溯特別重要。
| 面向 | Lab 起步 | 正式建議 |
|---|---|---|
| Pipeline 登入 | Client Secret | Workload Identity Federation(免密鑰 OIDC) |
| 密鑰儲存 | DevOps Variable Group(加密) | Azure Key Vault + Variable Group 連結 |
| SPN 權限 | System Administrator | 最小權限自訂角色 |
| 正式佈署 | 單人核准 | 多方核准 + 分支控制 + 維護時窗 Gate |
| 回滾 | 重新匯入前一版 Managed | 方案 / 環境備份 + 保留前版 artifact |
為何建議免密鑰(Workload Identity Federation)
讓 DevOps 與 Entra ID 建立聯合信任,佈署時換發短期 token,沒有長期 secret 可外洩,設定一次後也免除輪替維護。每個 Tenant 各建一次。
各子公司各自獨立 Tenant,若跨法遵區域(如 GDPR),資料落地與隔離天生分離,反而有利合規。
09 · 配套
配套檔案
本學習站對應一套可直接取用的 repo。網頁是視覺導覽,實作細節與可複製的程式碼在這些檔案裡。
九份中文說明
名詞、架構、環境策略、ALM、DevOps 設定、Pipeline 設計、測試、Lab 步驟、治理。
四支主 Pipeline + 樣板
export / build / release-sandbox / release-production,加三個共用樣板。
deployment settings
各環境的連線參照 / 環境變數綁定 JSON。
pac CLI 腳本
建立 Application User、本機匯出、身分除錯。
方案原始碼與程式碼
C# Plugin + 單元測試、表單 JS、Power Automate 流程、方案 XML。見「程式碼範例」章。
先讀網頁建立概念與跟著 Lab 操作,需要精確參數或要複製貼上時,回去對應的 docs/ 與 pipelines/ 檔案。
Dynamics 365 · Power Platform ALM Lab — 混合模式多租戶參考範例