1. 程式人生 > 實用技巧 >課時二:外掛開發

課時二:外掛開發

課時二:外掛開發

參考文章:

基本概念

D365平臺與傳統平臺

​ D365平臺與傳統平臺,在功能和頁面開發層面均有不同之處。比如說在功能開發層面:傳統平臺需要自己開發,而365平臺是系統標準的功能,不需要開發。在頁面開發層面:傳統平臺需要自己開發,而365平臺只需簡單配置即可;

外掛

Plugin(外掛),它是一種事件處理程式,通過它可以修改或擴充Dynamics 365標準的業務流程,如建立時執行自定義邏輯。另外是SDK Message(SDK訊息),通過它可以獲取CRM標準功能中CreateUpdate

Delete等相關操作的事件資訊;

執行順序

​ 有訊息就會有事件先後的順序,所以這邊就會涉及兩個名詞,Pre-operationPost-operationPre-operation :訊息事件觸發前執行一個動作;Post-operation :訊息事件觸發後執行一個動作;

開發外掛

1.新建VS解決方案專案類庫

​ 首先需要建立一個 .NET FrameworkClass Library類庫專案,這裡要為不同版本的Dynamics365 選擇的Framework不盡相同,請根據官方文件說明;

2.新增專案依賴包

​ 通過NuGet新增對Microsoft.CrmSdk.CoreAssemblies

的引用,如下圖,當然也要選擇合適的版本。如果不能上網的話,就需要新增對 Microsoft.Xrm.Sdk.dllMicrosoft.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:關聯實體,在什麼時候執行

3.除錯外掛

  • 安裝除錯工具

  • 選中要除錯的外掛,選中除錯的step,點選Start Profiling

  • 去CRM中操作實體,下載日誌檔案

  • 回到外掛註冊器關掉profiling

  • 點選Debug

  • 到VS中,設定斷點,點選除錯=>附加PluginRegistration程序

  • 再回到外掛工具點選執行

  • 這時就會看到斷點生效,進行除錯

開啟/配置例項映象

開啟註冊工具,展開註冊的外掛專案,右鍵點選建立的執行項,點選 建立新映象選項

型別屬性

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;

刪除外掛

service.Delete(targer.LogicalName, targer.Id);// 當前實體名和實體名id