Skip to main content

Make Dynamics Crm Advanced Find meet your client needs.


         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

Most popular

Dynamics 365 online and Adx portal on-premise. Are they compatible?

Adxstudio Portal how to: Incremental (Sequential) Deployment and backup. Part I

Custom plugin exception output for crm form