I've been requested to modify Advanced Find filters and results programmatically probably five times in my Dynamics CRM career. There were different reasons like monitoring/auditing of users searches, restricting of sensitive information, filtering search results, hiding parts of returning data from selected users etc.
Most popular scenario for interference into Advanced Find is to keep field security consistent. Given you have two forms for Contact entity. One form contains sensitive info like address, phone etc. Other form is for public use. Access to forms is role based. Security profile cannot be set due to design limitations. The catch here in Advanced Find. Any user who has permissions for Contact entity can create AF view and see the contact phone.
From customization perspectives we can do nothing. But there are some code tricks available.
Most difficult part in development was to find a way to determine source of Retrieve Multiple message. Let's think simple, we're in plugin, plugin is a part of pipeline - it's just a web request.
Here is the code that will allow you to remove "Mobile Phone" (telephone1) from Advanced Find results. I haven't wrote the criteria - it should be custom. With some modifications the code allow you to achieve all tasks that were mentioned in the first paragraph. Page names were changed since CRM 2011. Here is the code for crm 2013+, register the plugin step on Pre of Retrieve Multiple, none.
If you register the plugin on post then you can change the returned results in
(EntityCollection)context.OutputParameters["BusinessEntityCollection"];
!Keep in mind: The plugin is called on each Retrieve Multiple request. Examine incoming parameters before execute the logic. If your examination or logic are too complicated you might impact the performance. If you use "Retrieve Multiple" in the plugin than set context Deep property verification like: if (context.Depth > 1) return. It prevents from infinite loop.
//
using Microsoft.Xrm.Sdk;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web; //here is the trick :)
using System.IO;
using System.Xml;
using Microsoft.Xrm.Sdk.Query;
//Execution
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
//examine parameters
if (!ReferenceEquals(HttpContext.Current.CurrentHandler, null)
&& (HttpContext.Current.CurrentHandler.ToString().Contains("advancedfind")
|| HttpContext.Current.CurrentHandler.ToString().Contains("advfind"))
&& context.MessageName == "RetrieveMultiple"
&& context.PrimaryEntityName != "savedquery"
&& context.PrimaryEntityName != "userquery")
{
if (ReferenceEquals(context.InputParameters, null)) return;
ParameterCollection prms = context.InputParameters;
//check empty
if (prms.Count == 0) return;
QueryExpression current = ((QueryExpression)prms.FirstOrDefault().Value);
//check query target entity
if (ReferenceEquals(current, null) || current.EntityName != "contact") return;
//find user
Entity user = service.Retrieve("systemuser", context.UserId, new ColumnSet(new string[] { "domainname", "fullname" }));
string userName = user.GetAttributeValue<string>("domainname");
string fullName = user.GetAttributeValue<string>("fullname");
//use helper function to determine custom criteria
//for example user permissions:
//if(hasRestrictedCredentials(user)) return;
//the next part builds query info for testing/auditing purpose
StringBuilder sb = new StringBuilder("User " + fullName + " (" + userName + ")" + " queried entity '" + current.EntityName + "' on ");
sb.Append(DateTime.Now.ToString());
sb.Append(". Columns to retrieve: ");
for (int i = 0; i < current.ColumnSet.Columns.Count; i++)
{
sb.Append(current.ColumnSet.Columns[i] + ", ");
}
//try to remove info from field telephone1
ColumnSet newer = new ColumnSet();
for (int i = 0; i < current.ColumnSet.Columns.Count; i++)
{
if (current.ColumnSet.Columns[i] != "telephone1") newer.AddColumn (current.ColumnSet.Columns[i]);
}
//override current column set
current.ColumnSet = newer;
//override request
context.InputParameters[context.InputParameters.FirstOrDefault().Key] = (object)current;
}
return;
}
For CRM 3 and 4 you need to override Fetch Xml:
ICrmService crmService; public void Execute(IPluginExecutionContext context)
{
crmService = context.CreateCrmService(true);
string entityName = string.Empty;
if (!context.InputParameters.Contains("FetchXml"))
return;
XmlDocument doc = new XmlDocument();
doc.LoadXml((string)context.InputParameters["FetchXml"]);
XmlNode entityNode = doc.SelectSingleNode("//entity");
entityName = entityNode.Attributes["name"].Value;
if (entityName == "savedquery")
return;
//get your FetchXml query
XmlNode n = getFetchXml(my params);
context.InputParameters["FetchXml"] = n.OuterXml;
}
Comments