关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复239或者20161203可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong.me 。
以前我出过一个悬赏题目,就是定制Dynamics CRM标准的导出功能,让某些列不能导出,比如这些列可能包括敏感信息,比如消费者的姓名和电话等等,如何定制呢?我到现在还没有收到答案。某个周末的晚上,我研究了一下,初步有些效果,所以本博文记下来。
最容易想到的是注册插件,与导出有关的消息至少有四个,分别是Export, ExportAll, ExportCompressed 和ExportCompressedAll,可是注册在这四个消息上不管用。于是想到了在RetrieveMultiple消息上动刀,这个消息经常使用,除了根据主键和备用键查询数据以外,应该都是通过这个消息查询数据,导出也要查询数据。可是如果每次查询数据都执行的话,很消耗性能,后来我发现导出的时候执行的这个消息和普通的查询似乎有一个区别,就是导出的时候执行这个消息的上下文的Depth属性值为2,所以可以利用这个属性做点文章,尽量少执行点代码。
我先使用如下代码来查看打开一个普通的视图,插件中获取的各种参数,我这个代码是注册在罗勇测试实体RetrieveMultiple的Pre阶段。
using System; using Microsoft.Xrm.Sdk; using System.Text; using Microsoft.Xrm.Sdk.Query; using System.Linq; using System.Collections.Generic; using System.Xml.Serialization; using System.IO; namespace CrmVSSolution.Plugins { public class PreTestRetrieveMultiple : IPlugin { public void Execute(IServiceProvider serviceProvider) { // Extract the tracing service for use in debugging sandboxed plug-ins. // If you are not registering the plug-in in the sandbox, then you do // not have to add any tracing service related code. ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); // Obtain the execution context from the service provider. IPluginExecutionContext context = (IPluginExecutionContext) serviceProvider.GetService(typeof(IPluginExecutionContext)); IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId); StringBuilder sb = new StringBuilder(); sb.Append("Depth="); sb.Append(context.Depth); sb.Append(";PrimaryEntityName="); sb.Append(context.PrimaryEntityName); sb.Append(";PrimaryEntityId="); sb.Append(context.PrimaryEntityId); sb.Append(";BusinessUnitId="); sb.Append(context.BusinessUnitId); sb.Append(";InitiatingUserId="); sb.Append(context.InitiatingUserId); sb.Append(";IsExecutingOffline"); sb.Append(context.IsExecutingOffline); sb.Append(";IsInTransaction="); sb.Append(context.IsInTransaction); sb.Append(";IsOfflinePlayback="); sb.Append(context.IsOfflinePlayback); sb.Append(";IsolationMode="); sb.Append(context.IsolationMode); sb.Append(";MessageName="); sb.Append(context.MessageName); sb.Append(";OperationId="); sb.Append(context.OperationId); sb.Append(";OrganizationId="); sb.Append(context.OrganizationId); sb.Append(";OrganizationName="); sb.Append(context.OrganizationName); sb.Append(";OutputParameters="); if (context.OutputParameters != null) { sb.Append("{"); foreach (var outputpara in context.OutputParameters) { sb.Append(outputpara.Key); sb.Append("="); sb.Append(outputpara.Value.ToString()); } sb.Append("}"); } else { sb.Append("null"); } sb.Append(";OwningExtension="); if (context.OwningExtension != null) { sb.Append("{LogicalName="); sb.Append(context.OwningExtension.LogicalName); sb.Append(";Name="); sb.Append(context.OwningExtension.Name); sb.Append(";Id="); sb.Append(context.OwningExtension.Id); } else { sb.Append("null"); } sb.Append(";ParentContext="); if (context.ParentContext != null) { sb.Append(context.ParentContext.ToString()); } else { sb.Append("null"); } sb.Append(";PostEntityImages="); if (context.PostEntityImages != null) { sb.Append("{"); foreach (var postimg in context.PostEntityImages) { sb.Append("Key="); sb.Append(postimg.Key); sb.Append(";Value.Id="); sb.Append(postimg.Value.Id); sb.Append(";Value.LogicalName="); sb.Append(postimg.Value.LogicalName); foreach (var attr in postimg.Value.Attributes) { sb.Append("Attr="); sb.Append(attr.Key); sb.Append(";Value="); sb.Append(attr.Value.ToString()); } } sb.Append("}"); } else { sb.Append("null"); } sb.Append(";PreEntityImages="); if (context.PreEntityImages != null) { sb.Append("{"); foreach (var preimg in context.PreEntityImages) { sb.Append("Key="); sb.Append(preimg.Key); sb.Append(";Value.Id="); sb.Append(preimg.Value.Id); sb.Append(";Value.LogicalName="); sb.Append(preimg.Value.LogicalName); foreach (var attr in preimg.Value.Attributes) { sb.Append("Attr="); sb.Append(attr.Key); sb.Append(";Value="); sb.Append(attr.Value.ToString()); } } sb.Append("}"); } else { sb.Append("null"); } sb.Append(";SecondaryEntityName="); sb.Append(context.SecondaryEntityName); sb.Append(";RequestId="); sb.Append(context.RequestId.HasValue ? context.RequestId.Value.ToString() : "null"); sb.Append(";Stage="); sb.Append(context.Stage); sb.Append(";UserId="); sb.Append(context.UserId); sb.Append(";InputParameters="); if (context.InputParameters != null) { sb.Append("{"); foreach (var inputpara in context.InputParameters) { sb.Append(inputpara.Key); sb.Append("="); sb.Append(inputpara.Value.ToString()); if (inputpara.Key == "Query") { var queryexp = inputpara.Value as QueryExpression; sb.Append("ColumnSet={"); foreach (var col in queryexp.ColumnSet.Columns) { sb.Append(col); sb.Append(","); } sb.Append("}"); sb.Append("LinkEntities={"); if (queryexp.LinkEntities != null) { sb.Append("LinkEntity={"); foreach (var linkentity in queryexp.LinkEntities.Where(x => x.LinkToEntityName == "ly_test")) { sb.Append("LinkToEntityName="); sb.Append(linkentity.LinkToEntityName); sb.Append(";LinkFromEntityName="); sb.Append(linkentity.LinkFromEntityName); sb.Append(";LinkColumnSet={"); sb.Append(string.Join(",", linkentity.Columns.Columns)); sb.Append("}"); } sb.Append("}"); } else { sb.Append("null"); } sb.Append("}"); } } sb.Append("}"); } else { sb.Append("null"); } sb.Append("当前用户拥有角色:"); sb.Append(string.Join(",", GetUserRoles(service, context.UserId))); if (context.InputParameters.Contains("Query") && context.InputParameters["Query"] is QueryExpression) { //throw new InvalidPluginExecutionException("不是管理员不能导出!");//这个错误提示不会展示在前台给用户看到 QueryExpression objQueryExpression = (QueryExpression)context.InputParameters["Query"]; sb.Append("查找的主实体是:"); sb.Append(objQueryExpression.EntityName); sb.Append("查询表达式是:"); XmlSerializer xmlSerializer = new XmlSerializer(objQueryExpression.GetType()); using (StringWriter textWriter = new StringWriter()) { xmlSerializer.Serialize(textWriter, objQueryExpression); sb.Append(textWriter.ToString()); } } var entity = new Entity("annotation"); entity["subject"] = "execute retrievemultiple"; entity["objectid"] = new EntityReference("ly_test", Guid.Parse("B707DE1B-CF99-E611-8161-000D3A80C8B8")); entity["notetext"] = sb.ToString(); service.Create(entity); } public IEnumerable<string> GetUserRoles(IOrganizationService service, Guid userId) { string fetchXml = string.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true' no-lock='true'> <entity name='role'> <attribute name='name' /> <link-entity name='systemuserroles' from='roleid' to='roleid' visible='false' intersect='true'> <link-entity name='systemuser' from='systemuserid' to='systemuserid' alias='ad'> <filter type='and'> <condition attribute='systemuserid' operator='eq' value='{0}' /> </filter> </link-entity> </link-entity> </entity> </fetch>", userId); EntityCollection entities = service.RetrieveMultiple(new FetchExpression(fetchXml)); return entities.Entities.Select(x => x.GetAttributeValue<string>("name")); } } }
得到的结果如下:
Depth=1;PrimaryEntityName=ly_test;PrimaryEntityId=00000000-0000-0000-0000-000000000000;BusinessUnitId=487cdd4b-26a3-e511-80c6-000d3a807ec7;InitiatingUserId=e9cd027f-26a3-e511-80c6-000d3a807ec7;IsExecutingOffline:False;IsInTransaction=False;IsOfflinePlayback=False;IsolationMode=2;MessageName=RetrieveMultiple;OperationId=00000000-0000-0000-0000-000000000000;OrganizationId=bd2a5c49-6b08-4eda-8a15-84159d9fd349;OrganizationName=Demo;OutputParameters={};OwningExtension={LogicalName=sdkmessageprocessingstep;Name=CrmVSSolution.Plugins.PreTestRetrieveMultiple: RetrieveMultiple of ly_test;Id=544ba42f-baae-e611-816e-000d3a80c8b8;ParentContext=null;PostEntityImages={};PreEntityImages={};SecondaryEntityName=none;RequestId=null;Stage=20;UserId=e9cd027f-26a3-e511-80c6-000d3a807ec7;InputParameters={Query=Microsoft.Xrm.Sdk.Query.QueryExpressionColumnSet={ly_name,modifiedon,ly_minutessincecreated,createdon,ly_testid,processid,ly_name,ly_minutessincecreated,modifiedon,createdon,}LinkEntities={LinkEntity={}}}当前用户拥有角色:System Customizer,计划经理,计划员,系统管理员;查找的主实体是:ly_test;查询表达式是:<?xml version="1.0" encoding="utf-16"?> <QueryExpression _tmplitem="3" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ExtensionData _tmplitem="3" ></ExtensionData> <Distinct _tmplitem="3" >false</Distinct> <NoLock _tmplitem="3" >true</NoLock> <PageInfo _tmplitem="3" > <PageNumber _tmplitem="3" >1</PageNumber> <Count _tmplitem="3" >50</Count> <ReturnTotalRecordCount _tmplitem="3" >true</ReturnTotalRecordCount> <ExtensionData _tmplitem="3" ></ExtensionData> </PageInfo> <LinkEntities _tmplitem="3" > <LinkEntity _tmplitem="3" > <LinkFromAttributeName _tmplitem="3" >processid</LinkFromAttributeName> <LinkFromEntityName _tmplitem="3" >ly_test</LinkFromEntityName> <LinkToEntityName _tmplitem="3" >workflow</LinkToEntityName> <LinkToAttributeName _tmplitem="3" >workflowid</LinkToAttributeName> <JoinOperator _tmplitem="3" >LeftOuter</JoinOperator> <LinkCriteria _tmplitem="3" > <FilterOperator _tmplitem="3" >And</FilterOperator> <Conditions _tmplitem="3" ></Conditions> <Filters _tmplitem="3" ></Filters> <IsQuickFindFilter _tmplitem="3" >false</IsQuickFindFilter> <ExtensionData _tmplitem="3" ></ExtensionData> </LinkCriteria> <LinkEntities _tmplitem="3" /> <Columns _tmplitem="3" > <AllColumns _tmplitem="3" >false</AllColumns> <Columns _tmplitem="3" > <string _tmplitem="3" >versionnumber</string> </Columns> <ExtensionData _tmplitem="3" ></ExtensionData> </Columns> <EntityAlias _tmplitem="3" >processidworkflowworkflowid</EntityAlias> <Orders _tmplitem="3" ></Orders> <ExtensionData _tmplitem="3" ></ExtensionData> </LinkEntity> </LinkEntities> <Criteria _tmplitem="3" > <FilterOperator _tmplitem="3" >And</FilterOperator> <Conditions _tmplitem="3" ></Conditions> <Filters _tmplitem="3" ></Filters> <IsQuickFindFilter _tmplitem="3" >false</IsQuickFindFilter> <ExtensionData _tmplitem="3" ></ExtensionData> </Criteria> <Orders _tmplitem="3" > <OrderExpression _tmplitem="3" > <AttributeName _tmplitem="3" >ly_name</AttributeName> <OrderType _tmplitem="3" >Ascending</OrderType> <ExtensionData _tmplitem="3" ></ExtensionData> </OrderExpression> </Orders> <EntityName _tmplitem="3" >ly_test</EntityName> <ColumnSet _tmplitem="3" > <AllColumns _tmplitem="3" >false</AllColumns> <Columns _tmplitem="3" > <string _tmplitem="3" >ly_name</string> <string _tmplitem="3" >modifiedon</string> <string _tmplitem="3" >ly_minutessincecreated</string> <string _tmplitem="3" >createdon</string> <string _tmplitem="3" >ly_testid</string> <string _tmplitem="3" >processid</string> <string _tmplitem="3" >ly_name</string> <string _tmplitem="3" >ly_minutessincecreated</string> <string _tmplitem="3" >modifiedon</string> <string _tmplitem="3" >createdon</string> </Columns> <ExtensionData _tmplitem="3" ></ExtensionData> </ColumnSet> <TopCount _tmplitem="3" xsi:nil="true" ></TopCount> </QueryExpression>
然后到主题了,我这假设限制罗勇测试实体的十进制数列(ly_decimal)不允许导出,就在这个实体的RetrieveMultiple消息的Pre阶段使用如下代码,当然我还做了个限制,没有系统管理员角色不能导出,只是为了证明这里面可以做很多事情,用权限来控制导出功能,很简单吧:
using System; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; using System.Linq; using System.Collections.Generic; namespace CrmVSSolution.Plugins { public class PreTestRetrieveMultiple : IPlugin { public void Execute(IServiceProvider serviceProvider) { ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); IPluginExecutionContext context = (IPluginExecutionContext) serviceProvider.GetService(typeof(IPluginExecutionContext)); IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId); //context.Depth = 2是导出的时候执行 if (context.Depth == 2 && context.InputParameters.Contains("Query") && context.InputParameters["Query"] is QueryExpression) { if (!GetUserRoles(service, context.UserId).Contains("系统管理员")) { throw new InvalidPluginExecutionException("不是管理员不能导出!");//这个错误提示不会展示在前台给用户看到 } QueryExpression objQueryExpression = (QueryExpression)context.InputParameters["Query"]; objQueryExpression.ColumnSet = new ColumnSet(objQueryExpression.ColumnSet.Columns.Where(col => col != "ly_decimal").ToArray()); } } public IEnumerable<string> GetUserRoles(IOrganizationService service, Guid userId) { string fetchXml = string.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true' no-lock='true'> <entity name='role'> <attribute name='name' /> <link-entity name='systemuserroles' from='roleid' to='roleid' visible='false' intersect='true'> <link-entity name='systemuser' from='systemuserid' to='systemuserid' alias='ad'> <filter type='and'> <condition attribute='systemuserid' operator='eq' value='{0}' /> </filter> </link-entity> </link-entity> </entity> </fetch>", userId); EntityCollection entities = service.RetrieveMultiple(new FetchExpression(fetchXml)); return entities.Entities.Select(x => x.GetAttributeValue<string>("name")); } } }
在界面上可以看到十进制数列有值:
导出来的时候这个列就没有值:
这样虽然可以导出罗勇测试实体的时候不到处十进制字段,但是我如果查询的是它的子实体,然后加上这个实体的十进制字段显示,这个用高级查找很容易做到,导出的话就可以看到这个列了。
这个时候我需要在这个实体的1:N关系的子实体中也在RetrieveMultiple消息的Pre阶段注册类似如下插件代码即可:
using System; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; using System.Linq; namespace CrmVSSolution.Plugins { public class PreTestSubRetrieveMultiple : IPlugin { public void Execute(IServiceProvider serviceProvider) { ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); IPluginExecutionContext context = (IPluginExecutionContext) serviceProvider.GetService(typeof(IPluginExecutionContext)); IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId); //context.Depth=2是导出的时候执行 if (context.Depth == 2 && context.InputParameters.Contains("Query") && context.InputParameters["Query"] is QueryExpression) { QueryExpression objQueryExpression = (QueryExpression)context.InputParameters["Query"]; if (objQueryExpression.LinkEntities != null) //当这个不能导出的实体作为关联的父实体加入了视图列,也需要过滤 { foreach (var linkentity in objQueryExpression.LinkEntities.Where(x => x.LinkToEntityName == "ly_test")) { linkentity.Columns = new ColumnSet(linkentity.Columns.Columns.Where(col => col != "ly_decimal").ToArray()); } } } } } }
根据 Distinguish page load and Export to Excel in CRM 2015 的答案,最好是查看其ParentContext是否为null,若不为null再看其名称是否为ExportToExcel或者ExportDynamicToExcel,这个应该更加靠谱。
public void Execute(IServiceProvider serviceProvider) { var executionContext = serviceProvider.GetService<IPluginExecutionContext>(); var parentContext = executionContext.ParentContext; if (parentContext != null && (parentContext.MessageName == "ExportToExcel" || parentContext.MessageName == "ExportDynamicToExcel")) { // Place your logic here } }
当然可能还有其他方法可以导出,我说的利用标准的功能不需要开发,欢迎各位看官提出来,看看能否封堵住。