Dynamics 365 CE将自定义工作流活动程序集注册到磁盘并引用其他类库
我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),欢迎关注我的微信公众号 MSFTDynamics365erLuoYong ,回复347或者20190723可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!
本文参考了官方文档: Register a plug-in to be deployed on-premise 。
我们知道本地部署的Dynamics 365 Customer Engagement可以将自定义工作流活动程序集/插件程序集可以注册到非沙盒中(None) + 磁盘(Disk)中,这样注册有什么注意事项呢?一起来看看。
我这里使用如下的代码,准备在工作流或者操作中调用外部API:
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using Newtonsoft.Json.Linq;
using System;
using System.Activities;
using System.Net.Http;
using System.Net.Http.Headers;
using System.ServiceModel;
using System.Threading.Tasks; namespace ActivitiesRegisteredInNoneDisk
{
public class CallWebAPISample : CodeActivity
{
protected override void Execute(CodeActivityContext executionContext)
{
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
tracingService.Trace("Enter CallWebAPISample on {0}", DateTime.Now.ToString());
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService orgService = serviceFactory.CreateOrganizationService(context.UserId);
try
{
var responseContent = GetApiAsync("").Result;
tracingService.Trace("HTTP POST RESPONSE CONTENT = {0}", responseContent);
JObject jo = JObject.Parse(responseContent);
throw new InvalidPluginExecutionException(jo["result"][]["orderEntryDate"].ToString());
}
catch (FaultException<OrganizationServiceFault> ex)
{
tracingService.Trace(ex.Message + ex.StackTrace);
throw new InvalidPluginExecutionException("CallWebAPISample encountered fault exception." + ex.Message);
}
catch (Exception e)
{
tracingService.Trace(e.Message + e.StackTrace);
throw new InvalidPluginExecutionException("CallWebAPISample encountered general exception." + e.Message);
}
tracingService.Trace("Leave CallWebAPISample on {0}", DateTime.Now.ToString());
} private async Task<string> GetApiAsync(string OrderNumber)
{
string returnVal = string.Empty;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetAsync(string.Format("https://thomaswebapi.azurewebsites.net/api/Order?OrderNo={0}", OrderNumber)).Result;
if (response.IsSuccessStatusCode)
{
returnVal = await response.Content.ReadAsStringAsync();
}
else
{
throw new Exception(response.Content.ToString());
}
}
return returnVal;
}
}
}
为了展示引用其他类库,我这里特意引用了额外的类库Newtonsoft.Json,如下:

如果直接注册的话,我的注册界面如下,注意如果插件的isolation mode为 None,注册插件时候使用的账号除了需要据悉D365 CE的系统管理员角色外,需要属于部署管理员(Deployment Administrators)这个组:

这样注册很容易报错,报错如下:

报错的详细信息如下:
Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=9.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: Unable to load plug-in assembly.
Detail: <OrganizationServiceFault xmlns="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ActivityId>9beef9f0-82da-4792-bb0e-48540c2a1d84</ActivityId>
<ErrorCode>-2147204719</ErrorCode>
<ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
<KeyValuePairOfstringanyType>
<a:key>ApiExceptionSourceKey</a:key>
<a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Plugin/Microsoft.Crm.ObjectModel.PluginAssemblyService</a:value>
</KeyValuePairOfstringanyType>
<KeyValuePairOfstringanyType>
<a:key>ApiOriginalExceptionKey</a:key>
<a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Microsoft.Crm.CrmException: Unable to load plug-in assembly. ---> Microsoft.Crm.CrmException: Unable to load plug-in assembly.
at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.LoadCrmPluginAssemblyMetadata(IBusinessEntity pluginAssembly, ExecutionContext context, Boolean loadAllMetadata)
at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.RetrieveAssemblyMetadata(IBusinessEntity pluginAssembly, ExecutionContext context, Boolean retrieveFromExisting, Boolean forSystemAssembly)
at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.ValidateOperation(String operationName, IBusinessEntity entity, ExecutionContext context)
at Microsoft.Crm.ObjectModel.SdkEntityServiceBase.CreateInternal(IBusinessEntity entity, ExecutionContext context, Boolean verifyAction)
--- End of inner exception stack trace ---
at Microsoft.Crm.Extensibility.VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)
at Microsoft.Crm.Extensibility.PipelineInstrumentationHelper.Execute(Boolean instrumentationEnabled, String stopwatchName, ExecuteWithInstrumentation action, PipelineExecutionContext context)
at Microsoft.Crm.Extensibility.Pipeline.<>c__DisplayClass2_1.<Execute>b__0()</a:value>
</KeyValuePairOfstringanyType>
<KeyValuePairOfstringanyType>
<a:key>ApiStepKey</a:key>
<a:value i:type="b:guid" xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/">3ecabb1b-ea3e-db11-86a7-000a3a5473e8</a:value>
</KeyValuePairOfstringanyType>
<KeyValuePairOfstringanyType>
<a:key>ApiDepthKey</a:key>
<a:value i:type="b:int" xmlns:b="http://www.w3.org/2001/XMLSchema">1</a:value>
</KeyValuePairOfstringanyType>
<KeyValuePairOfstringanyType>
<a:key>ApiActivityIdKey</a:key>
<a:value i:type="b:guid" xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/">9beef9f0-82da-4792-bb0e-48540c2a1d84</a:value>
</KeyValuePairOfstringanyType>
<KeyValuePairOfstringanyType>
<a:key>ApiPluginSolutionNameKey</a:key>
<a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">System</a:value>
</KeyValuePairOfstringanyType>
<KeyValuePairOfstringanyType>
<a:key>ApiStepSolutionNameKey</a:key>
<a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">System</a:value>
</KeyValuePairOfstringanyType>
</ErrorDetails>
<Message>Unable to load plug-in assembly.</Message>
<Timestamp>2019-07-22T14:12:57.9063414Z</Timestamp>
<ExceptionRetriable>false</ExceptionRetriable>
<ExceptionSource i:nil="true" />
<InnerFault>
<ActivityId>9beef9f0-82da-4792-bb0e-48540c2a1d84</ActivityId>
<ErrorCode>-2147204719</ErrorCode>
<ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
<Message>Unable to load plug-in assembly.</Message>
<Timestamp>2019-07-22T14:12:57.9063414Z</Timestamp>
<ExceptionRetriable>false</ExceptionRetriable>
<ExceptionSource i:nil="true" />
<InnerFault i:nil="true" />
<OriginalException i:nil="true" />
<TraceText i:nil="true" />
</InnerFault>
<OriginalException i:nil="true" />
<TraceText i:nil="true" />
</OrganizationServiceFault> Server stack trace:
at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) Exception rethrown at [0]:
at Microsoft.Crm.Tools.Libraries.RegistrationHelper.RegisterAssembly(CrmOrganization org, String pathToAssembly, CrmPluginAssembly assembly)
at Microsoft.Crm.Tools.AssemblyRegistration.PluginRegistrationViewModel.btnregisterClick()
开启CRM服务器端日志,如果包括了类似如下错误信息:
>Crm Exception: Message: Failed to load plugin assembly with exception System.IO.DirectoryNotFoundException: The system cannot find the path specified. (Exception from HRESULT: 0x80070003)
at Microsoft.Crm.IMetaDataDispenserEx.OpenScope(String szScope, UInt32 dwOpenFlags, Guid& riid)
at Microsoft.Crm.CrmPluginAssemblyMetadata.OpenScopeForAssemblyOnDisk(String fullFilePath)
at Microsoft.Crm.CrmPluginAssemblyMetadata.LoadMetadataFromAssemblyFile(String fullFilePath, Boolean loadAllMetadata)
at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.LoadCrmPluginAssemblyMetadata(IBusinessEntity pluginAssembly, ExecutionContext context, Boolean loadAllMetadata). PluginInfo => Crm plugin assembly info : sourcetype = 1, description = , ismanaged = False, pluginassemblyid = c21af9cf-0313-4af0-8c80-7d9644e4c581, ispasswordset = False, publickeytoken = 440C114085C1E28B, path = D:\Codes\ThomasLuoCRM\ActivitiesRegisteredInNoneDisk\bin\Debug\ActivitiesRegisteredInNoneDisk.dll, name = ActivitiesRegisteredInNoneDisk, culture = neutral, isolationmode = 1, version = 1.0.0.0, Location = D:\Codes\ThomasLuoCRM\ActivitiesRegisteredInNoneDisk\bin\Debug\ActivitiesRegisteredInNoneDisk.dll, ErrorCode: -2147220970, InnerException: System.IO.DirectoryNotFoundException: The system cannot find the path specified. (Exception from HRESULT: 0x80070003)
at Microsoft.Crm.IMetaDataDispenserEx.OpenScope(String szScope, UInt32 dwOpenFlags, Guid& riid)
at Microsoft.Crm.CrmPluginAssemblyMetadata.OpenScopeForAssemblyOnDisk(String fullFilePath)
at Microsoft.Crm.CrmPluginAssemblyMetadata.LoadMetadataFromAssemblyFile(String fullFilePath, Boolean loadAllMetadata)
at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.LoadCrmPluginAssemblyMetadata(IBusinessEntity pluginAssembly, ExecutionContext context, Boolean loadAllMetadata)
那就在自己的开发机器上简历和服务器安装目录一样的文件夹,比如我这里是在自己的开发机器上建立 C:\Program Files\Dynamics 365\Server\bin\assembly 文件夹,然后将要部署的dll复制到这个文件夹中,注册程序集的时候从这个文件中中选择要注册的dll。还有需要将程序集DLL拷贝到CRM安装目录\Server\bin\assembly 中了,我这里完整的路径是:C:\Program Files\Dynamics 365\Server\bin\assembly 。我这里自定义工作流活动程序集文件名是ActivitiesRegisteredInNoneDisk.dll,我需要将这个文件复制到CRM前端,后端所有服务器的C:\Program Files\Dynamics 365\Server\bin\assembly文件夹中。
还需要授予 everyone 账号对 C:\Program Files\Dynamics 365\Server\bin\assembly 文件夹的Full control权限,根据 Unable to load Microsoft plug-in on a fresh CRM 9 on-premise organization 的解决方法,应该授予 Users 这个账号 Read & execute 权限即可,若这个权限足够的话就不要设置 everyone的权限了。记得改了以后重启前端服务器的IIS。

注册完了以后直接运行的话会报错:Assembly file name (ActivitiesRegisteredInNoneDisk.dll) is in invalid format. Only file name is allowed.
具体错误信息如下:
Unhandled Exception: Microsoft.Crm.CrmException: Assembly file name (ActivitiesRegisteredInNoneDisk.dll) is in invalid format. Only file name is allowed.
at Microsoft.Crm.Extensibility.PluginAssemblyFactory.LoadAssembly(String assemblyFile)
at Microsoft.Crm.Extensibility.PluginAssemblyFactory.LoadAssemblyWithoutMetadataValidation(PluginAssemblyDescription assemblyDescription, String assemblyName)
at Microsoft.Crm.Extensibility.PluginAssemblyFactory.CreateInstance(Guid pluginAssemblyId, PluginAssemblyDescription& assemblyDescription, IOrganizationContext context)
at Microsoft.Crm.Caching.PluginAssemblyCacheLoader.LoadCacheData(Guid key, ExecutionContext context)
at Microsoft.Crm.Caching.ObjectModelCacheLoader`2.LoadCacheDataInternal(TKey key, Object existingDataContainer, IOrganizationContext context)
at Microsoft.Crm.Caching.ObjectModelCacheLoader`2.LoadCacheData(TKey key, IOrganizationContext context)
at Microsoft.Crm.Caching.CrmMultiOrgCacheBase`2.CreateEntry(TKey key, IOrganizationContext context)
at Microsoft.Crm.Caching.CrmEntitySharedMultiOrgCache`2.LookupEntry(TKey key, IOrganizationContext context)
at Microsoft.Crm.Caching.PluginTypeCacheLoader.LoadCacheData(Guid key, ExecutionContext context)
at Microsoft.Crm.Caching.ObjectModelCacheLoader`2.LoadCacheDataInternal(TKey key, Object existingDataContainer, IOrganizationContext context)
at Microsoft.Crm.Caching.ObjectModelCacheLoader`2.LoadCacheData(TKey key, IOrganizationContext context)
at Microsoft.Crm.Caching.CrmMultiOrgCacheBase`2.CreateEntry(TKey key, IOrganizationContext context)
at Microsoft.Crm.Caching.CrmEntitySharedMultiOrgCache`2.LookupEntry(TKey key, IOrganizationContext context)
at Microsoft.Crm.Workflow.AsyncCustomActivityLoaderDirect.CustomActivityTypeAndAssemblyData(Guid pluginTypeId)
at Microsoft.Crm.Workflow.Services.ActivityReferenceService.ResolveType(String assemblyQualifiedName)
at Microsoft.Crm.Workflow.Services.ActivityReferenceService.InitializeActivity(ActivityReferenceBase activityReferenceBase)
at Microsoft.Xrm.Sdk.Workflow.Activities.ActivityReferenceBase.CacheMetadata(NativeActivityMetadata metadata)
at System.Activities.NativeActivity.OnInternalCacheMetadata(Boolean createEmptyBindings)
at System.Activities.Activity.InternalCacheMetadata(Boolean createEmptyBindings, IList`1& validationErrors)
at System.Activities.ActivityUtilities.ProcessActivity(ChildActivity childActivity, ChildActivity& nextActivity, Stack`1& activitiesRemaining, ActivityCallStack parentChain, IList`1& validationErrors, ProcessActivityTreeOptions options, ProcessActivityCallback callback)
at System.Activities.ActivityUtilities.ProcessActivityTreeCore(ChildActivity currentActivity, ActivityCallStack parentChain, ProcessActivityTreeOptions options, ProcessActivityCallback callback, IList`1& validationErrors)
at System.Activities.ActivityUtilities.CacheRootMetadata(Activity activity, LocationReferenceEnvironment hostEnvironment, ProcessActivityTreeOptions options, ProcessActivityCallback callback, IList`1& validationErrors)
at System.Activities.Hosting.WorkflowInstance.ValidateWorkflow(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.Hosting.WorkflowInstance.RegisterExtensionManager(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.WorkflowApplication.EnsureInitialized()
at System.Activities.WorkflowApplication.Enqueue(InstanceOperation operation, Boolean push)
at System.Activities.WorkflowApplication.InternalRun(TimeSpan timeout, Boolean isUserRun)
at System.Activities.WorkflowApplication.Run()
at Microsoft.Crm.Workflow.ActivityHost.StartWorkflowExecution(Activity workflow, ICommonWorkflowContext context)
at Microsoft.Crm.Workflow.ActivityHostBase.StartWorkflow(ICommonWorkflowContext context, Activity preLoadedActivity)
这个根据 Problems adding Step to Disk based Plugin Assembly 的说法,执行类似如下的SQL就可以了:
update PluginAssemblybase set Path='ActivitiesRegisteredInNoneDisk.dll'
where Name ='ActivitiesRegisteredInNoneDisk'
这里还涉及到引用了外部程序集,需要将该程序集注册到CRM服务器的GAC。
服务器上没有 gacutil 的话,我这里从安装了 Visual Stuido 的我的机器上复制如下文件(文件夹)到服务器上:
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\gacutil.exe
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\gacutil.exe.config
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\1033 文件夹中的所有文件
然后我用类似如下命令将其复制到GAC, gacutil 的使用请参考 Gacutil.exe (Global Assembly Cache Tool)
cd C:\Software\gacutil
gacutil /i "C:\Program Files\Dynamics 365\Server\bin\assembly\Newtonsoft.Json.dll" /f

然后我就可以顺利的调用Web API,并拿到结果了。

写了那么多,我只想说,不要将插件/自定义工作流程序集注册存储到Disk或者GAC中,而是遵从官方文档Register a plug-in to be deployed on-premise 的建议注册到Database中,官方原文是:We strongly recommend that you store your production-ready plug-ins in the Dynamics 365 for Customer Engagement apps database, instead of on-disk。
关于注册到数据库中相对与其他两种(磁盘和GAC)的优势,CRM 2015 – plugin deployment options 的说的比较简单明了,如下:
- The plugin is backed up when the database is backed up
- For multiple server configurations you only need to deploy once to the database and not individually to each CRM server.
- Plugins in the database can be added to solutions, Disk, GAC plugins cannot
- Plugins deployed to the GAC or Disk will need an IISRESET to refresh, plugins deployed in the database do not.
- Sandboxed and CRM Online plugins have to be deployed in the database
Dynamics 365 CE将自定义工作流活动程序集注册到磁盘并引用其他类库的更多相关文章
- Dynamics 365 CE的插件/自定义工作流活动中调用Web API示例代码
微软动态CRM专家罗勇 ,回复325或者20190428可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me! 现在Web API越来越流行,有时候为了程序更加健壮,需要在插件 ...
- Dynamics 365中自定义工作流活动获取的上下文分析及注意事项
关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复244或者20170306可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong. ...
- 自定义工作流活动报错:您无法登陆系统。原因可能是您的用户记录或您所属的业务部门在Microsoft Dynamics 365中已被禁用。
本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复265或者20170926可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong.me ...
- Dynamics 365中自定义工作流活动更新了输入输出参数后获取的方法
关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复245或者20170309可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong. ...
- 自定义工作流活动运行产生System.Security.SecurityException
摘要: 微软动态CRM专家罗勇 ,回复305或者20190224可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . 最近碰到一个 ...
- Dynamics 365 CE Update消息PostOperation阶段Image的尝试
我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...
- 使用Dynamics 365 CE Web API查询数据加点料及选项集字段常用查询
微软动态CRM专家罗勇 ,回复336或者20190516可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me. 紧接上文:配置Postman通过OAuth 2 implicit ...
- Use SQL to Query Data from CDS and Dynamics 365 CE
from : https://powerobjects.com/2020/05/20/use-sql-to-query-data-from-cds-and-dynamics-365-ce/ Have ...
- Dynamics 365 CE中AsyncOperationBase表记录太多,影响系统性能怎么办?
微软动态CRM专家罗勇 ,回复311或者20190311可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . 本文主要是根据微软官 ...
随机推荐
- 【编译系统02】编译器 - 语义分析器(semantic)的简单设计思路(变量类与变量表)
当我们分析到 "int n;",说明其已经定义了一个变量,之后又遇到一个 "n=3",我们从哪里去找这个n并且赋值呢? 答案是:通过我们定义的 变量表(Tabl ...
- Spring Cloud(一):入门篇
Spring Cloud 简介 Spring Cloud 是一个基于 Spring Boot 实现的微服务架构开发工具,可以快速构建分布式系统中的某些常用模式,如配置管理.服务治理.断路器.智能路由. ...
- 解析innodb中的MVCC
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
- iOS开发中,获取图片之后保存或下载到本地相册中
#pragma mark 先获取本地图片或者网络图片 - (void)saveHeaderImageWith:(NSString *)path { UIImage *img = [UIImage im ...
- [b0043] python 归纳 (二八)_python测试使用快速上手
参考 Python必会的单元测试框架 —— unittest # -*- coding: utf-8 -*- """ 测试代码组织,整理( 非可执行) "&qu ...
- 记录NodeJs常用工具
安装模块 npm install [-g] module express supervisor:改代码自动重启服务 用法>> supervisor xxx.js 搜索模块 npm sear ...
- Pymysql的常见使用方法
cursor.fetchone()与cursor.fetchall()的区别: cursor.fetchone():只能显示一个数据 cursor.fetchall():才能显示查出来的所有数据 P ...
- Flask/Tornado/Django
深入学习Python ,用Django做Web后端开发现在Python的用途愈来愈广,服务器.Web.游戏.爬虫.数据分析 以及人工智能 学习之路还很长 技术之路 不能回头 陷进去 就出不来 就跟恋 ...
- Codeforces Round #602 (Div. 2, based on Technocup 2020 Elimination Round 3) B. Box 贪心
B. Box Permutation p is a sequence of integers p=[p1,p2,-,pn], consisting of n distinct (unique) pos ...
- java类生命周期,类的“加载,连接,初始化,使用,卸载过程”详解
“ 如果说核心类库的 API 比做数学公式的话,那么 Java 虚拟机的知识就好比公式的推导过程” 每本Java入门书籍在介绍Java这门语言的时候都会提到Java跨平台,“一次解释,到处运行的特点“ ...