參考文章
基本概念
D365平台與傳統平台
D365平台與傳統平台,在功能和頁面開發層面均有不同之處。比如說在功能開發層面:傳統平台需要自己開發,而365平台是系統標准的功能,不需要開發。在頁面開發層面:傳統平台需要自己開發,而365平台只需簡單配置即可;
插件
Plugin
(插件),它是一種事件處理程序,通過它可以修改或擴充Dynamics 365標准的業務流程,如創建時執行自定義邏輯。另外是SDK Message
(SDK消息),通過它可以獲取CRM標准功能中Create
,Update
,Delete
等相關操作的事件信息;
執行順序
有消息就會有事件先后的順序,所以這邊就會涉及兩個名詞,Pre-operation
和Post-operation
;Pre-operation
:消息事件觸發前執行一個動作;Post-operation
:消息事件觸發后執行一個動作;
開發插件
1.新建VS解決方案項目類庫
首先需要創建一個 .NET Framework
的 Class Library
類庫項目,這里要為不同版本的Dynamics365
選擇的Framework
不盡相同,請根據官方文檔說明;
2.添加項目依賴包
通過NuGet
添加對Microsoft.CrmSdk.CoreAssemblies
的引用,如下圖,當然也要選擇合適的版本。如果不能上網的話,就需要添加對 Microsoft.Xrm.Sdk.dll
和 Microsoft.Crm.Sdk.Proxy.dll
的引用;
3.新建插件
插件文件本質也是類文件,只不過這個類繼承IPlugin
接口,且實現Execute
方法,在此方法中編寫插件代碼,實現業務邏輯;建議:插件命名:執行時機+功能/作用英文+實體名+Plugin
,例:CreateNew_StudentPlugin
;
代碼可以看到Execute
方法只有一個輸入參數serviceProvider
,該參數的類型是IServiceProvider
,是事件執行管道傳遞給當前插件的所有消息的容器,存儲了在插件中可能要使用到的各類對象。通過IServiceProvider
接口的GetService
方法,可以獲取執行上下文IPluginExecutionContext
、組織服務工廠IOrganizationServiceFactory
以及跟蹤服務ITracingService
等實例;
public void Execute(IServiceProvider serviceProvider)
{
// 獲取插件執行上下文
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
// 獲取組織服務工廠實例
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
// 獲取組織服務實例
IOrganizationService service = factory.CreateOrganizationService(context.UserId);
// 獲取跟蹤服務
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
try
{
// 插件業務邏輯代碼
}
catch (FaultException<OrganizationServiceFault> ex)
{
// 異常處理代碼
}
}
4.插件簽名
在Visual Studio
中右擊該項目,選擇屬性(Properties
) > 簽名(Signing
),選中 Sign the assembly
,我這里新建一個Key file
Key file
我的設置如下,為了簡便,我就不設置密碼保護了,保存后編譯插件項目,確定沒有編譯錯誤
注冊插件
1.下載注冊工具
從Dynamics 365 Customer Engagement (V9.0)
開始,不再像以前一樣提供SDK
下載了,應該學習在線文檔 中下載方式,以下下載方式主要是根據在線文章中命令方式下載:
本文提到下載的工具包括如下:
Tool | NuGet Package |
---|---|
Code generation tool CrmSvcUtil.exe |
Microsoft.CrmSdk.CoreTools |
Configuration Migration tool DataMigrationUtility.exe |
Microsoft.CrmSdk.XrmTooling.ConfigurationMigration.Wpf |
Package Deployer PackageDeployer.exe |
Microsoft.CrmSdk.XrmTooling.PackageDeployment.WPF |
Plug-in Registration Tool PluginRegistration.exe |
Microsoft.CrmSdk.XrmTooling.PluginRegistrationTool |
SolutionPackager tool SolutionPackager.exe |
Microsoft.CrmSdk.CoreTools |
-
打開
Windows PowerShell
,最好是以管理員身份打開 -
切換到你要下載工具的目錄
-
執行如下的命令,執行完畢后記得回車
$sourceNugetExe = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" $targetNugetExe = ".\nuget.exe" Remove-Item .\Tools -Force -Recurse -ErrorAction Ignore Invoke-WebRequest $sourceNugetExe -OutFile $targetNugetExe Set-Alias nuget $targetNugetExe -Scope Global -Verbose ## ##Download Plugin Registration Tool ## ./nuget install Microsoft.CrmSdk.XrmTooling.PluginRegistrationTool -O .\Tools md .\Tools\PluginRegistration $prtFolder = Get-ChildItem ./Tools | Where-Object {$_.Name -match 'Microsoft.CrmSdk.XrmTooling.PluginRegistrationTool.'} move .\Tools\$prtFolder\tools\*.* .\Tools\PluginRegistration Remove-Item .\Tools\$prtFolder -Force -Recurse ## ##Download CoreTools ## ./nuget install Microsoft.CrmSdk.CoreTools -O .\Tools md .\Tools\CoreTools $coreToolsFolder = Get-ChildItem ./Tools | Where-Object {$_.Name -match 'Microsoft.CrmSdk.CoreTools.'} move .\Tools\$coreToolsFolder\content\bin\coretools\*.* .\Tools\CoreTools Remove-Item .\Tools\$coreToolsFolder -Force -Recurse ## ##Download Configuration Migration ## ./nuget install Microsoft.CrmSdk.XrmTooling.ConfigurationMigration.Wpf -O .\Tools md .\Tools\ConfigurationMigration $configMigFolder = Get-ChildItem ./Tools | Where-Object {$_.Name -match 'Microsoft.CrmSdk.XrmTooling.ConfigurationMigration.Wpf.'} move .\Tools\$configMigFolder\tools\*.* .\Tools\ConfigurationMigration Remove-Item .\Tools\$configMigFolder -Force -Recurse ## ##Download Package Deployer ## ./nuget install Microsoft.CrmSdk.XrmTooling.PackageDeployment.WPF -O .\Tools md .\Tools\PackageDeployment $pdFolder = Get-ChildItem ./Tools | Where-Object {$_.Name -match 'Microsoft.CrmSdk.XrmTooling.PackageDeployment.Wpf.'} move .\Tools\$pdFolder\tools\*.* .\Tools\PackageDeployment Remove-Item .\Tools\$pdFolder -Force -Recurse ## ##Download Package Deployer PowerShell module ## ./nuget install Microsoft.CrmSdk.XrmTooling.PackageDeployment.PowerShell -O .\Tools $pdPoshFolder = Get-ChildItem ./Tools | Where-Object {$_.Name -match 'Microsoft.CrmSdk.XrmTooling.PackageDeployment.PowerShell.'} move .\Tools\$pdPoshFolder\tools\*.* .\Tools\PackageDeployment.PowerShell Remove-Item .\Tools\$pdPoshFolder -Force -Recurse ## ##Remove NuGet.exe ## Remove-Item nuget.exe
-
可以看到前面提到的工具都下載好了,一共是5個。如果要獲取最新版本的工具,重復執行前面的步驟即可
2.注冊/附加插件
-
打開插件注冊工具
-
登錄-注冊項目
-
新建插件
點擊菜單欄 新建(Register) 選項,依次點擊新建插件(Register New Assembly)
按照示例圖片步驟一中選擇DLL文件,在步驟二中勾選添加的插件(類),最后點擊下方添加
-
新建步驟(插件執行時機)
點擊菜單欄 新建(Register) 選項,依次點擊新建步驟(Register New Step)
Message:執行時機,Primary Entity:關聯實體,在什么時候執行
調試插件
-
安裝調試工具
-
選中要調試的插件,選中調試的
step
,點擊Start Profiling
-
去CRM中操作實體,下載日志文件
-
回到插件注冊器關掉
profiling
-
點擊
Debug
-
到VS中,設置斷點,點擊調試=>附加PluginRegistration進程
-
再回到插件工具點擊執行
-
這時就會看到斷點生效,進行調試
開啟/配置實例鏡像
打開注冊工具,展開注冊的插件項目,右鍵點擊創建的執行項,點擊 創建新鏡像選項
開發技巧和問題
注冊
update
插件獲取entity
踩過的坑
-
Update
插件獲取的entity
只包含該條記錄id
和修改的字段值Create
; -
插件獲取的
entity
包含該條記錄的所有字段
注冊
delete
插件獲取entity
踩過的坑
-
Update,Create
插件獲取的類型為entity
; -
Delete
插件 獲取的實體為entityreference
;
除了
CUR
以外還有那些事件可以注冊?
-
Associate & Disassociate (N:N)
多對多關系; -
Win
&Lose
商機贏單和丟單
類型屬性
IPluginExecutionContext
插件執行上下文IPluginExecutionContext
中,包括有事件處理管道傳遞給插件的各類信息,包括執行插件的運行時環境、執行管道相關信息以及觸發Web服務的實體實例信息。IPluginExecutionContext
接口中的成員列表如下所示:
名稱 | 說明 |
---|---|
ParentContext | 從父管道操作中獲取執行上下文信息。父子管道產生原因在於CRM系統中某些消息請求可能會產生其他消息請求。舉例來說AssignRequest請求會產生一個UpdateRequest請求,如果兩個插件A和U分別訂閱了AssignRequest消息和UpdateRequest消息,那么在AssignRequest產生時,插件A、插件U將依次執行,此時插件U的執行上下文的ParentContext屬性將被賦予插件A的執行上下文。 |
Stage | 獲取同步執行模式插件在執行管道中所處的階段 |
IExecutionContext
IPluginExecutionContext
接口繼承自IExecutionContext
接口,在IExecutionContext
接口中,包含了大量的有關上下文的信息,如下表所示:
名稱 | 說明 |
---|---|
BusinessUnitId | 獲取執行管道所處理的實體實例的業務部門GUID。 |
CorrelationId | 該屬性的用途CRM平台為了避免出現無限死循環 |
Depth | 獲取當前插件在調用堆棧中的深度。也是為了避免出現無限死循環。插件開發人員可以在插件代碼中對該屬性進行判斷從而避免出現無限死循環。經常的一種使用情景是,訂閱了UpateRequest的插件代碼中還執行了Update操作。 |
InitiatingUserId | 獲取當前執行事件管道的系統用戶的GUID. |
InputParameters | 獲取觸發插件執行的請求消息參數. |
IsExecutingOffline | 獲取當前插件是否運行在脫機環境中 |
IsInTransaction | 獲取插件是否執行在數據庫事務中。 |
IsOfflinePlayback | 如果插件可以運行在脫機環境中,那么在客戶端與CRM服務器同步時,很可能由於數據同步造成服務器端同一插件再次執行一遍,從而導致數據出現了錯誤,此時,就需要在插件中使用本數據判斷,是否是由於離線客戶端與CRM服務器同步觸發了本插件的運行。 |
IsolationMode | 判斷插件是否執行在沙盒Sandbox中。 |
MessageName | 獲取當前事件管道所處理的請求消息 |
Mode | 獲取插件執行模式:同步執行還是異步執行. |
OperationCreatedOn | 在與Azure雲進行集成的時候用到的。 |
OperationId | |
OrganizationId | 獲取實體實例所屬組織的GUID. |
OrganizationName | 獲取實體實例所屬組織的唯一名稱. |
OutputParameters | 獲取平台核心操作完成后的響應消息參數. |
OwningExtension | 獲取相關聯的SdkMessageProcessingingStep對象. |
PostEntityImages | 主要用於UpdateRequest消息中,分別獲取核心操作之前以及之后實體的快照、映像信息 |
PreEntityImages | |
PrimaryEntityId | 事件管道正在處理的實體實例的GUID |
PrimaryEntityName | 事件管道正在處理的實體的邏輯名稱. |
RequestId | 事件管道正在處理的請求的GUID. |
SharedVariables | 獲取/設置插件之間傳遞的自定義屬性值. |
CURD
提示:
- 刪除中注意
targer
是否可用
插件基本初始化類
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using System;
namespace T4
{
public abstract class PluginBase : IPlugin
{
#region 相關服務
//調試沙箱插件使用的跟蹤服務
protected ITracingService tracingservice;
//插件的上下文
protected IPluginExecutionContext context;
//組織服務工廠
protected IOrganizationServiceFactory serviceFactory;
//組織服務
protected IOrganizationService service;
//組織服務
protected IOrganizationService serviceAdmin;
protected OrganizationServiceContext orgServiceContext;
//相關記錄
protected Entity targer;
protected Entity pretarger;
protected Entity posttarger;
protected EntityReference targerref;
protected EntityReferenceCollection targerrefc;
protected Relationship targerrel;
protected EntityReference assignee;
//觸發操作
protected bool isCreate;
protected bool isUpdate;
protected bool isDelete;
protected bool isAssociate;
protected bool isDisassociate;
protected bool isAssign;
#endregion
public void Execute(IServiceProvider serviceProvider)
{
Initialize(serviceProvider);
DoExecute(serviceProvider);
}
public abstract void DoExecute(IServiceProvider serviceProvider);
/// <summary>
/// 插件入口
/// </summary>
/// <param name="serviceProvider"></param>
public void Initialize(IServiceProvider serviceProvider)
{
#region 相關服務的初始化
tracingservice = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
// service = serviceFactory.CreateOrganizationService(context.UserId);
service = serviceFactory.CreateOrganizationService(null);
//service = serviceFactory.CreateOrganizationService(null);
serviceAdmin = serviceFactory.CreateOrganizationService(null);
//orgServiceContext = new OrganizationServiceContext(service);
isCreate = context.MessageName == "Create";
isUpdate = context.MessageName == "Update";
isDelete = context.MessageName == "Delete";
string contextMessage = context.MessageName;
if (contextMessage == "Assign" || context.MessageName == "Associate" || context.MessageName == "Disassociate") return;
if (context.InputParameters.Contains("Target"))
{
if (context.InputParameters["Target"] is Entity)
targer = (Entity)context.InputParameters["Target"];
else if (context.InputParameters["Target"] is EntityReference)
targerref = (EntityReference)context.InputParameters["Target"];
}
#endregion
}
/// <summary>
/// 插件入口
/// </summary>
/// <param name="serviceProvider"></param>
public void InitializeAssociate(IServiceProvider serviceProvider)
{
#region 相關服務的初始化
tracingservice = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
service = serviceFactory.CreateOrganizationService(context.UserId);
serviceAdmin = serviceFactory.CreateOrganizationService(null);
orgServiceContext = new OrganizationServiceContext(service);
isAssociate = context.MessageName == "Associate";
isDisassociate = context.MessageName == "Disassociate";
targerref = (EntityReference)context.InputParameters["Target"];
targerrefc = (EntityReferenceCollection)context.InputParameters["RelatedEntities"];
targerrel = (Relationship)context.InputParameters["Relationship"];
#endregion
}
/// <summary>
/// 插件入口
/// </summary>
/// <param name="serviceProvider"></param>
public void InitializeAssign(IServiceProvider serviceProvider)
{
#region 相關服務的初始化
tracingservice = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
service = serviceFactory.CreateOrganizationService(context.UserId);
serviceAdmin = serviceFactory.CreateOrganizationService(null);
orgServiceContext = new OrganizationServiceContext(service);
//tracingservice.Trace("相關服務的初始化");
isAssign = context.MessageName == "Assign";
//tracingservice.Trace("相關對象初始化Target");
targerref = (EntityReference)context.InputParameters["Target"];
//tracingservice.Trace("相關對象初始化Assignee");
assignee = (EntityReference)context.InputParameters["Assignee"];
//tracingservice.Trace("相關對象初始化 ok");
#endregion
}
}
}
新建插件
提示:
- 日期類型為:
DateTime
類型 - 貨幣類型為:
Money
類型 - 選項集類型為:
OptionSetValue
類型 - 查找類型為:
EntityReference
類型 - 實體字段名區分大小寫
using Microsoft.Xrm.Sdk;
using System;
namespace T4
{
public class CreatePlugin : PluginBase
{
public override void DoExecute(IServiceProvider serviceProvider)
{
if (context.Depth > 1) return;
// 示例一
Random random = new Random();
Entity entity = new Entity(targer.LogicalName);
entity["new_age"] = random.Next(18, 99);
entity["new_name"] = $"學員:"+ random.Next(1000,9999);
entity["new_gender"] = true;
EntityReference entityReference = new EntityReference(targer.LogicalName, Guid.Parse("2867B927-B12F-EB11-B392-005056993F73"));
entity["new_search"] = entityReference;
entity["new_admissiondate"] = DateTime.UtcNow;
service.Create(entity);
// 示例二
Entity entitys = new Entity(targer.LogicalName);
entitys["new_name"] = "YH";//單行文本類型
entitys["new_client_name"] = "Hello Word";//單行文本類型
entitys["new_client_id"] = 123456;//整形
entitys["new_client_float"] = 54.8;//浮點類型
entitys["new_client_sex"] = false;//兩個選擇
entitys["new_client_addtime"] = DateTime.Parse("2019-1-1 12:30:12");//日期類型
entitys["new_client_money"] = new Money(3000);//貨幣類型
entitys["new_client_cardnumber"] = decimal.Parse("50000");//十進制類型
entitys["new_client_summary"] = "全力以赴";//多行文本類型
entitys["new_client_a"] = new OptionSetValue(100000000);//單項選項集
entitys["new_client_select"] = new EntityReference(targer.LogicalName, Guid.Parse("596B44E4-AF24-EB11-956F-E03F49115DFE"));//查找類型
service.Create(entitys);
}
}
}
修改插件
提示:
- 修改時如需使用修改前后實體鏡像,需要插件注冊工具中開啟(配置)
using Microsoft.Xrm.Sdk;
using System;
namespace T4
{
public class UpdatePlugin : PluginBase
{
public override void DoExecute(IServiceProvider serviceProvider)
{
if (context.Depth > 1) return;
// 獲取修改后實體鏡像(修改后數據)
posttarger = context.PostEntityImages["image"];
// 獲取修改前實體鏡像(修改前數據)
pretarger = context.PreEntityImages["image"];
//判斷字段是否存在
if (!posttarger.Contains("new_name")) return;
Random random = new Random();
posttarger["new_age"] = random.Next(18, 99);
posttarger["new_name"] = $"學員:" + random.Next(1000, 9999);
posttarger["new_gender"] = true;
// 根據實體名稱,id創建查找類型值
EntityReference entityReference = new EntityReference(posttarger.LogicalName, Guid.Parse("2867B927-B12F-EB11-B392-005056993F73"));
posttarger["new_search"] = entityReference;
// 返回實體查找類型字段值,值為 EntityReference 類型
var entityReference = posttarger.GetAttributeValue<EntityReference>("new_search");
service.Update(posttarger);
}
}
}
查詢插件
// 單查詢
var entity = service.Retrieve("實體名", Guid.NewGuid(), new Microsoft.Xrm.Sdk.Query.ColumnSet(true));
// 多查詢
// 1.創建查詢表達式,並執行查詢實體
QueryExpression query = new QueryExpression(context.PrimaryEntityName);
// 2.查詢顯示列名集合,true為全部顯示
query.ColumnSet = new ColumnSet(true);
// 2.條件篩選,格式:字段名,表達式,條件值
query.Criteria.AddCondition("字段名", ConditionOperator.Equal, "值");
// 3.執行查詢
var collection = service.RetrieveMultiple(query);
// 4.獲取實體集合
var entity_list = collection.Entities;
// 3.FetchXML查詢
private Entity GetSequenceEntity(IOrganizationService service, Guid code)
{
var fetchXml = $@"
<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='mcs_tc_order'>
<attribute name='jk_lockstatus'/>
<filter type='and'>
<condition attribute='mcs_tc_orderid' operator='eq' value='{code}' />
</filter>
</entity>
</fetch>";
var response = service.RetrieveMultiple(new FetchExpression(fetchXml));
if (response != null && response.Entities.Count > 0)
{
var entity = response.Entities[0];
return entity;
}
return null;
}
刪除插件
service.Delete(targer.LogicalName, targer.Id);// 當前實體名和實體名id