上一篇文章《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 }
上面就是一个简单的插件类的代码,仅仅是一个样例,具体的插件代码编写,还是要根据实际的业务情况确定的。