Dynamics 365中自定义工作流活动获取的上下文分析及注意事项

关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复244或者20170306可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong.me 。

 
为了方便说明,我首先创建一个自定义工作流活动,使用的代码如下。
using System;

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using Microsoft.Xrm.Sdk.Workflow;
using Microsoft.Xrm.Sdk;
using System.Reflection;

namespace CrmVSSolution.Workflow
{
    public sealed class PostTestUpdate : CodeActivity
    {
        protected override void Execute(CodeActivityContext executionContext)
        {
            ITracingService tracingService = executionContext.GetExtension<ITracingService>();
            tracingService.Trace("进入自定义工作流活动CrmVSSolution.Workflow.PostTestUpdate");
            IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService orgService = serviceFactory.CreateOrganizationService(context.UserId);
            StringBuilder sb = new StringBuilder();
            PropertyInfo[] properties = context.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo proInfo in properties)
            {
                sb.Append(proInfo.Name);
                sb.Append(" = ");
                if (proInfo.CanRead)
                {
                    sb.Append(proInfo.GetValue(context, null));
                }
                else
                {
                    sb.Append("Cannot read value");
                }
                sb.Append("(");
                sb.Append(proInfo.PropertyType.ToString());
                sb.Append(");");
                sb.Append("\n");
            }
            tracingService.Trace("工作流中context的所有参数如下:\n");
            tracingService.Trace(sb.ToString());
            sb.Clear();
            foreach(var item in context.InputParameters)
            {
                sb.Append(item.Key);
                sb.Append(" = ");
                if (item.Value != null)//注意有的属性的值为null,比如InputArguments
                {
                    sb.Append(item.Value.ToString());
                }
                else
                {
                    sb.Append("null");
                }
                sb.Append("\n");
            }
            tracingService.Trace("工作流中context.InputParameter的参数如下:\n");
            tracingService.Trace(sb.ToString());
            sb.Clear();
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                sb.Append("工作流中包括了Target参数,它是Entity类型.\n");
                var currentEntity = context.InputParameters["Target"] as Entity;
                sb.Append("Target参数包括如下属性:\n");
                foreach (var attr in currentEntity.Attributes)
                {
                    sb.Append(attr.Key);
                    sb.Append(" = ");
                    if (attr.Value != null)//注意有的属性值为null,比如modifiedonbehalfby,所以要加上判断
                    {
                        sb.Append(attr.Value.ToString());
                    }
                    else
                    {
                        sb.Append("null");
                    }
                    sb.Append("\n");
                }
                tracingService.Trace(sb.ToString());
            }
            tracingService.Trace("结束自定义工作流活动CrmVSSolution.Workflow.PostTestUpdate");
            throw new InvalidWorkflowException("有时候不抛出异常不行啊!");
        }
    }
}

 

然后使用 SDK\Tools\PluginRegistration\PluginRegistration.exe 来注册自定义工作流活动,连接方式如下,我这里是做了IFD的我自己的CRM实验环境。
 
点击 Register > Register New Assembly,注意注册之前,你的程序集要签名哦。
 
 注册如下,Dynamics 365 Online不支持注册到None中,所以Step 3要选择 Sandbox。但是我的代码中使用了反射来读取参数值,使用的也是本地部署的Dynamics 365,所以注册到None中,注册到Sandbox中的程序集具有的权限要小得多。点击下面的Register Selected Plugins 按钮。电脑分辨率低的童鞋注意,可能要用tab键跳转到这个按钮。
 
一会儿告诉我注册成功。
 
根据需要,最好是调整下这个自定义工作流活动的WorkflowActivityGroupName和Name,我这里调整如下。然后点击保存按钮保存就可以了。这个保存按钮好小啊。
 
 然后我们在工作流中就可以使用了,我这里做一个工作流如下,监控的是单行文本字段的变更。我这也也勾选了 作为按需工作流,所以是可以手工启动运行的。
 
当然需要激活工作流,因为我要看的东西都是写在工作流的trace里面,所以我还去 设置 > 管理 > 系统设置 > 自定义里面,将启用插件跟踪日志的日志记录这个选项设置为所有。
 
变更字段值之后我们去看插件日志,在设置>自定义>插件跟踪日志里面查看。不过还真有时候看不到,一般是因为你注册在None中,而不是注册在Sandbox中为了能看到跟踪信息,我在自定义工作流活动最后抛出异常,这样在工作流的详细信息中肯定可以看到Trace到的消息。我这里先做自动触发的来看看,我更新了单行文本字段值和多行文本字段的值来触发这个工作流,看到的消息如下:

 

进入自定义工作流活动CrmVSSolution.Workflow.PostTestUpdate
工作流中context的所有参数如下:
 
PreEntityImages = Microsoft.Xrm.Sdk.EntityImageCollection(Microsoft.Xrm.Sdk.EntityImageCollection);
PostEntityImages = Microsoft.Xrm.Sdk.EntityImageCollection(Microsoft.Xrm.Sdk.EntityImageCollection);
InputParameters = Microsoft.Xrm.Sdk.ParameterCollection(Microsoft.Xrm.Sdk.ParameterCollection);
OutputParameters = Microsoft.Xrm.Sdk.ParameterCollection(Microsoft.Xrm.Sdk.ParameterCollection);
SharedVariables = Microsoft.Xrm.Sdk.ParameterCollection(Microsoft.Xrm.Sdk.ParameterCollection);
WorkflowCategory = 0(System.Int32);
Mode = 1(System.Int32);
LegacyContext = Microsoft.Crm.Workflow.LegacyWorkflowContext(Microsoft.Crm.Workflow.ILegacyWorkflowContext);
OperationStatus = Microsoft.Crm.Workflow.WorkflowOperationInProgressResult(Microsoft.Crm.IGenericHandlerResult);
PluginTypeCache = Microsoft.Crm.Caching.PluginTypeCache(Microsoft.Crm.Caching.PluginTypeCache);
PrimaryEntityName = ly_test(System.String);
PrimaryEntityId = b707de1b-cf99-e611-8161-000d3a80c8b8(System.Guid);
MessageName = Update(System.String);
RequestId = (System.Nullable`1[System.Guid]);
UserId = e9cd027f-26a3-e511-80c6-000d3a807ec7(System.Guid);
InitiatingUserId = e9cd027f-26a3-e511-80c6-000d3a807ec7(System.Guid);
CorrelationId = 7d92631b-2f5f-4d71-b6c2-16608ba82b0c(System.Guid);
Depth = 1(System.Int32);
IsolationMode = 1(System.Int32);
OwningExtension = Microsoft.Xrm.Sdk.EntityReference(Microsoft.Xrm.Sdk.EntityReference);
BusinessUnitId = 487cdd4b-26a3-e511-80c6-000d3a807ec7(System.Guid);
IsExecutingOffline = False(System.Boolean);
IsOfflinePlayback = False(System.Boolean);
IsInTransaction = False(System.Boolean);
OperationId = e697feec-b901-e711-8178-000d3a80c8b8(System.Guid);
OrganizationId = bd2a5c49-6b08-4eda-8a15-84159d9fd349(System.Guid);
OrganizationName = Demo(System.String);
SecondaryEntityName = (System.String);
OperationCreatedOn = 3/5/2017 3:39:32 PM(System.DateTime);
StageName = (System.String);
WorkflowMode = 0(System.Int32);
ExtensionParameters = Microsoft.Xrm.Sdk.ParameterCollection(Microsoft.Xrm.Sdk.ParameterCollection);
PrimaryEntityImage = Microsoft.Xrm.Sdk.Entity(Microsoft.Xrm.Sdk.Entity);
IsCrmUIWorkflow = True(System.Boolean);
IsAutoDeleteSet = False(System.Boolean);
IsLoggingEnabled = True(System.Boolean);
GoingIdle = False(System.Boolean);
WorkflowStageProperty = Microsoft.Crm.Workflow.WorkflowStageProperty(Microsoft.Crm.Workflow.WorkflowStageProperty);
WorkflowLogsProperty = Microsoft.Crm.Workflow.WorkflowLogsProperty(Microsoft.Crm.Workflow.WorkflowLogsProperty);
CorrelationToken = Microsoft.Crm.Sdk.CorrelationToken(Microsoft.Crm.Sdk.CorrelationToken);
EntityDependencies = System.Collections.ObjectModel.Collection`1[Microsoft.Crm.Workflow.EntityDependencyBase](System.Collections.ObjectModel.Collection`1[Microsoft.Crm.Workflow.EntityDependencyBase]);
WorkflowTracingService = Microsoft.Crm.Workflow.WorkflowTracingService(Microsoft.Crm.Workflow.WorkflowTracingService);
SdkService = (Microsoft.Crm.Workflow.IWorkflowSdkServiceFactory);
Event = Microsoft.Crm.Asynchronous.AsyncEvent(Microsoft.Crm.Asynchronous.IGenericEventData);
ChildWorkflowInstanceId = 00000000-0000-0000-0000-000000000000(System.Guid);
InstanceState = Microsoft.Crm.Workflow.AsyncWorkflowInstanceState(Microsoft.Crm.Workflow.WorkflowInstanceStateBase);
ProxyTypesAssembly = (System.Reflection.Assembly);
CallerOrigin = Microsoft.Crm.Sdk.ApplicationOrigin(Microsoft.Crm.Sdk.CallerOrigin);
CorrelationUpdateTime = 3/5/2017 3:39:32 PM(System.DateTime);
TransactionContextId = e697feec-b901-e711-8178-000d3a80c8b8(System.Guid);
ParentPluginExecutionId = 00000000-0000-0000-0000-000000000000(System.Guid);
Arguments = (Microsoft.Xrm.Sdk.Workflow.ArgumentsCollection);
ConversionContext = Microsoft.Crm.BusinessEntities.ConversionContext(Microsoft.Crm.BusinessEntities.ICrmConversionContext);
LegacyConversionContext = Microsoft.Crm.BusinessEntities.ConversionContext(Microsoft.Crm.BusinessEntities.ICrmConversionContext);
 
工作流中context.InputParameter的参数如下:
 
Target = Microsoft.Xrm.Sdk.Entity
ConcurrencyBehavior = Default
 
工作流中包括了Target参数,它是Entity类型.
Target参数包括如下属性:
ly_singlelinetext = 新的单行文本字段值
ly_testid = b707de1b-cf99-e611-8161-000d3a80c8b8
modifiedon = 3/5/2017 3:39:28 PM
modifiedby = Microsoft.Xrm.Sdk.EntityReference
modifiedonbehalfby = null
 
结束自定义工作流活动CrmVSSolution.Workflow.PostTestUpdate
然后我手工启动工作流来触发该工作流,得到的结果如下:

 

 

进入自定义工作流活动CrmVSSolution.Workflow.PostTestUpdate
工作流中context的所有参数如下:
 
PreEntityImages = Microsoft.Xrm.Sdk.EntityImageCollection(Microsoft.Xrm.Sdk.EntityImageCollection);
PostEntityImages = Microsoft.Xrm.Sdk.EntityImageCollection(Microsoft.Xrm.Sdk.EntityImageCollection);
InputParameters = Microsoft.Xrm.Sdk.ParameterCollection(Microsoft.Xrm.Sdk.ParameterCollection);
OutputParameters = Microsoft.Xrm.Sdk.ParameterCollection(Microsoft.Xrm.Sdk.ParameterCollection);
SharedVariables = Microsoft.Xrm.Sdk.ParameterCollection(Microsoft.Xrm.Sdk.ParameterCollection);
WorkflowCategory = 0(System.Int32);
Mode = 1(System.Int32);
LegacyContext = Microsoft.Crm.Workflow.LegacyWorkflowContext(Microsoft.Crm.Workflow.ILegacyWorkflowContext);
OperationStatus = Microsoft.Crm.Workflow.WorkflowOperationInProgressResult(Microsoft.Crm.IGenericHandlerResult);
PluginTypeCache = Microsoft.Crm.Caching.PluginTypeCache(Microsoft.Crm.Caching.PluginTypeCache);
PrimaryEntityName = ly_test(System.String);
PrimaryEntityId = b707de1b-cf99-e611-8161-000d3a80c8b8(System.Guid);
MessageName = ExecuteWorkflow(System.String);
RequestId = (System.Nullable`1[System.Guid]);
UserId = e9cd027f-26a3-e511-80c6-000d3a807ec7(System.Guid);
InitiatingUserId = e9cd027f-26a3-e511-80c6-000d3a807ec7(System.Guid);
CorrelationId = 32f4fcb1-425c-453b-8eb1-902b328635bf(System.Guid);
Depth = 1(System.Int32);
IsolationMode = 1(System.Int32);
OwningExtension = Microsoft.Xrm.Sdk.EntityReference(Microsoft.Xrm.Sdk.EntityReference);
BusinessUnitId = 487cdd4b-26a3-e511-80c6-000d3a807ec7(System.Guid);
IsExecutingOffline = False(System.Boolean);
IsOfflinePlayback = False(System.Boolean);
IsInTransaction = False(System.Boolean);
OperationId = 8597e77d-ba01-e711-8178-000d3a80c8b8(System.Guid);
OrganizationId = bd2a5c49-6b08-4eda-8a15-84159d9fd349(System.Guid);
OrganizationName = Demo(System.String);
SecondaryEntityName = (System.String);
OperationCreatedOn = 3/5/2017 3:43:35 PM(System.DateTime);
StageName = (System.String);
WorkflowMode = 0(System.Int32);
ExtensionParameters = Microsoft.Xrm.Sdk.ParameterCollection(Microsoft.Xrm.Sdk.ParameterCollection);
PrimaryEntityImage = Microsoft.Xrm.Sdk.Entity(Microsoft.Xrm.Sdk.Entity);
IsCrmUIWorkflow = True(System.Boolean);
IsAutoDeleteSet = False(System.Boolean);
IsLoggingEnabled = True(System.Boolean);
GoingIdle = False(System.Boolean);
WorkflowStageProperty = Microsoft.Crm.Workflow.WorkflowStageProperty(Microsoft.Crm.Workflow.WorkflowStageProperty);
WorkflowLogsProperty = Microsoft.Crm.Workflow.WorkflowLogsProperty(Microsoft.Crm.Workflow.WorkflowLogsProperty);
CorrelationToken = Microsoft.Crm.Sdk.CorrelationToken(Microsoft.Crm.Sdk.CorrelationToken);
EntityDependencies = System.Collections.ObjectModel.Collection`1[Microsoft.Crm.Workflow.EntityDependencyBase](System.Collections.ObjectModel.Collection`1[Microsoft.Crm.Workflow.EntityDependencyBase]);
WorkflowTracingService = Microsoft.Crm.Workflow.WorkflowTracingService(Microsoft.Crm.Workflow.WorkflowTracingService);
SdkService = (Microsoft.Crm.Workflow.IWorkflowSdkServiceFactory);
Event = Microsoft.Crm.Asynchronous.AsyncEvent(Microsoft.Crm.Asynchronous.IGenericEventData);
ChildWorkflowInstanceId = 00000000-0000-0000-0000-000000000000(System.Guid);
InstanceState = Microsoft.Crm.Workflow.AsyncWorkflowInstanceState(Microsoft.Crm.Workflow.WorkflowInstanceStateBase);
ProxyTypesAssembly = (System.Reflection.Assembly);
CallerOrigin = Microsoft.Crm.Sdk.ApplicationOrigin(Microsoft.Crm.Sdk.CallerOrigin);
CorrelationUpdateTime = 3/5/2017 3:43:35 PM(System.DateTime);
TransactionContextId = 8597e77d-ba01-e711-8178-000d3a80c8b8(System.Guid);
ParentPluginExecutionId = 00000000-0000-0000-0000-000000000000(System.Guid);
Arguments = (Microsoft.Xrm.Sdk.Workflow.ArgumentsCollection);
ConversionContext = Microsoft.Crm.BusinessEntities.ConversionContext(Microsoft.Crm.BusinessEntities.ICrmConversionContext);
LegacyConversionContext = Microsoft.Crm.BusinessEntities.ConversionContext(Microsoft.Crm.BusinessEntities.ICrmConversionContext);
 
工作流中context.InputParameter的参数如下:
 
EntityId = b707de1b-cf99-e611-8161-000d3a80c8b8
WorkflowId = 6bebc426-f722-4b64-ae5d-0da379f8a8c4
InputArguments = null
 
结束自定义工作流活动CrmVSSolution.Workflow.PostTestUpdate
我们可以看到一些东西:
1. 自动启动的工作流,MessageName是触发这个工作流运行的消息,比如第一个是Update。而手工启动的工作流,MessageName则是固定的ExecuteWorkflow。
 
2. 自动启动的工作流,context.InputParameter中包括了Target参数,该参数是Entity类型,该Entity包括的属性中包括了触发该该工作流的属性的值。而如果是手动运行工作流的话,则context.InputParameter中不包括Target参数,
包括的是EntityId参数。所以如果一个工作流,既要可以自动触发,也允许手动运行,写代码时候不要认为context.InputParameter中一定包括了Target参数,这样会导致空引用异常。如果要拿实体名称和当前记录的ID,使用 context.PrimaryEntityName 和 context.PrimaryEntityId 即可。
 
3.虽然自动启动的工作流,context.InputParameter中包括了Target参数,该参数是Entity,但是并不会包括所有的变更属性的值(这和插件不一样),只会包括监控的字段的值。要获取触发工作流后变更后属性(字段)的值,如果是自动触发,则最靠谱的是context.InputParameter中Target实体的该属性的值,当然也要监控这个字段才行。通过工作流参数传递过来的变化字段的值,或者在自定义工作里活动中查询变化字段的值则是工作流运行时刻该字段的值。但是对于有值更改为无值,在自定义工作流活动中查询到的是最新的值也就是无值,而参数传递过来的却是变化之前的值,奇怪。如果要做变化前后的对比就只有使用插件了,当然审核(audit)功能也能记载下变化前后的值。如果要以最新的该实体字段的值来做,最好的是在工作流活动中查询一遍。
 
4.对于自动启动和手工运行的工作流,context.InitiatingUserId 拿到的始终是触发(启动)该工作流运行的操作者的ID,而context.UserId对于自动启动运行的工作流拿到的则是工作流负责人的ID,这个工作流负责人一般是具有系统管理员角色的超级用户。context.UserId对于手工启动运行的工作流拿到的是运行该工作流的用户的ID。所以在获取组织服务的时候我建议使用IOrganizationService orgService = serviceFactory.CreateOrganizationService(context.UserId); 这样容易避免因为触发该工作流的用户权限不够而带来工作流运行失败的问题。
 
posted @ 2019-03-06 11:17  微软MVP(15-18)罗勇  阅读(866)  评论(0编辑  收藏  举报