上一篇文章《MS CRM 2011的自定義和開發(11)——插件(plugin)開發(一)》,介紹了Microsoft Dynamics CRM 2011中插件plugin的基本概念,事件處理子系統的概念,本篇文章將介紹插件的開發的方法。
可以使用與.Net Framework 4.0 CLR兼容的任何開發語言編寫插件代碼,筆者我只會C#,所以后續代碼都是C#代碼,如果是VB.Net或者精通其他語言的程序員同學,我就愛莫能助啦。
為了編寫插件,需要在插件的Project中添加Microsoft.Xrm.SDK.dll以及Microsoft.Crm.Sdk.Proxy.dll兩個程序集的引用。這兩個程序集可以在SDK\bin目錄下面找到。
插件都是Microsoft.Xrm.Sdk.IPlugin接口的實現類,所有的插件類都必須實現IPlugin接口。IPlugin接口只有一個Execute方法。該接口的代碼如下所示:
1 public interface IPlugin
2 {
3 void Execute(IServiceProvider serviceProvider);
4 }
從上面代碼可以看出Execute方法只有一個輸入參數serviceProvider,該參數的類型是IServiceProvider,是事件執行管道傳遞給當前插件的所有消息的容器,存儲了在插件中可能要使用到的各類對象。通過IServiceProvider接口的GetService方法,可以獲取執行上下文IPluginExecutionContext、組織服務工廠IOrganizationServiceFactory以及跟蹤服務ITracingService等實例。
一個插件的樣例代碼如下所示:
1 public class SamplePlugin: IPlugin
2 {
3 public void Execute(IServiceProvider serviceProvider)
4 {
5 // 獲取插件執行上下文
6
7 IPluginExecutionContext context =
8 (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
9
10 // 獲取組織服務工廠實例
11
12 IOrganizationServiceFactory factory =
13 (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
14 //獲取組織服務實例
15 IOrganizationService service = factory.CreateOrganizationService(context.UserId);
16
17 //獲取跟蹤服務
18 ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
19
20 try
21 {
22 // 插件業務邏輯代碼
23 }
24 catch (FaultException<OrganizationServiceFault> ex)
25 {
26 //異常處理代碼
27 }
28 }
29 }
插件執行上下文IPluginExecutionContext中,包括有事件處理管道傳遞給插件的各類信息,包括執行插件的運行時環境、執行管道相關信息以及觸發Web服務的實體實例信息。IPluginExecutionContext接口中的成員列表如下所示:
名稱 |
說明 |
ParentContext |
從父管道操作中獲取執行上下文信息。父子管道產生原因在於CRM系統中某些消息請求可能會產生其他消息請求。舉例來說AssignRequest請求會產生一個UpdateRequest請求,如果兩個插件A和U分別訂閱了AssignRequest消息和UpdateRequest消息,那么在AssignRequest產生時,插件A、插件U將依次執行,此時插件U的執行上下文的ParentContext屬性將被賦予插件A的執行上下文。 |
Stage |
獲取同步執行模式插件在執行管道中所處的階段 |
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 |
獲取/設置插件之間傳遞的自定義屬性值. |
當觸發了插件訂閱的事件時,CRM會創建、填充執行上下文,並將其作為Execute方法的輸入參數,傳遞給Execute方法,從而,插件執行代碼就可以使用這些上下文信息了。
在上下文中,有幾個非常重要的屬性,是插件代碼中經常會使用到的:
輸入參數InputParameters以及輸出參數OutputParameters,這兩個屬性的基本信息已經在上面的列表中列出了,在此不贅述。下面主要看一下這兩個屬性的類型:ParameterCollection,ParameterCollection究其根本而言,是IEnumerable<KeyValuePair<TKey, TValue>>類型,即可遍歷的鍵值對集合。由此,在插件代碼中通過給定鍵值,就可以訪問對應的數據值了,而鍵值就是各類請求消息中的公共屬性的名稱。例如CreateRequest中,有一個屬性名為Target,包含了待創建的實體實例信息,是Entity類型,那么若要在CreateRequest觸發的插件中訪問帶創建的實體實例信息,就可以使用如下代碼獲取:
Entity entity = (Entity)context.InputParameters[“Target”];
對於OutputParameters屬性,也是類似的,只不過鍵值名稱信息來自於相關的響應信息屬性,而不再是請求消息中的屬性名稱了,其訪問的方式和InputParameters屬性的訪問類似,不再贅述。
前期映像PreEntityImages和后期映像PostEntityImages,存儲了平台核心操作之前與之后的快照。可以在插件注冊的過程中,指定快照中需要存儲的屬性。需要注意的是,某些事件是缺少前期或者后期映像的。例如對於創建,是沒有前期映像的,對於Delete是沒有后期映像的。映像的類型是EntityCollection,本質和ParameterCollection類型是類似的,可以通過給定鍵值訪問對應的映像數據,而鍵值的名稱是在注冊插件,設定映像的名稱時候設定的。
除了執行上下文以外,絕對大多數情況下,還需要訪問MS CRM系統的組織服務,那么可以通過從serviceProvider對象中獲取組織服務工廠,而后由組織服務工廠的CreateOrganizationService方法創建組織服務的實例。代碼可以參看上面的樣例代碼。在此不贅述。
下面列出了一個簡單的插件,執行於客戶Account的Pre Create事件上。處理的業務邏輯是,如果End User沒有為客戶編碼字段賦值,那么插件就為該字段賦一個隨機值。
1 using System;
2
3 // Microsoft Dynamics CRM的命名空間之一
4 using Microsoft.Xrm.Sdk;
5
6 namespace Microsoft.Crm.Sdk.Samples
7 {
8 public class AccountNumberPlugin: IPlugin
9 {
10 public void Execute(IServiceProvider serviceProvider)
11 {
12 // 獲取執行上下文
13 IPluginExecutionContext context =
14
15 (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
16
17 // InputParameters屬性包含所有輸入參數數據
18 if (context.InputParameters.Contains("Target") &&
19 context.InputParameters["Target"] is Entity)
20 {
21 // 從輸入參數中獲取Target參數
22 Entity entity = (Entity)context.InputParameters["Target"];
23 //檢測輸入的Target參數,判斷其邏輯名稱是否是account.
24 if (entity.LogicalName == "account")
25 {
26 // 判斷客戶記錄的客戶編碼字段accountnumber是否有值
27 if (entity.Attributes.Contains("accountnumber") == false)
28 {
29 // 沒有客戶編碼,那么賦一個隨機數字
30 Random rndgen = new Random();
31 entity.Attributes.Add("accountnumber", rndgen.Next().ToString());
32 }
33 else
34 {
35 // 拋出一個錯誤,注意,錯誤的類型!
36 throw new InvalidPluginExecutionException("The account number can only be set by the system.");
37 }
38 }
39 }
40 }
41 }
42 }
上面就是一個簡單的插件類的代碼,僅僅是一個樣例,具體的插件代碼編寫,還是要根據實際的業務情況確定的。