Curious what Dynamic People can do for you?

+ 31 (0) 20 303 24 70
Logo
CE Tech Blogs 02 June 2021 Jaco Janse

How to easily create complex dynamic views in Dynamics 365 CRM

sales

Views in Dynamics are based on the definition of a filter. Via a filter a query is in fact defined. However, the possibilities are limited when defining a filter. In this blog I will explain an approach with which it is possible to create more complex view filters. This approach uses a pre-operation RetrieveMultiple plugin, which modifies the view filter query before it is executed in Dynamics. In this way much more complex views can be created than is normally possible.

 

The case

In this example, we will create a dynamic view, which allows you to see a list of reports from the last 30 days, but should only show reports on topics you are interested in.

 

A report can be linked to 0 or more tags (N:N relationship), which can be used to indicate for a report which topics it is about.

 

You can also add tags to your own interests, to indicate which topics you are interested in (N:N relationship).

The approach

As said a view in Dynamics is nothing else than a query. When you, as a Dynamics user, use a view to look at certain records, a RetrieveMultiple call takes place in Dynamics where the filter query is given, executed and the found records are returned and shown.

The way we are going to make the view logic dynamic consists of the following steps:

  1. Defining the view filter, containing a certain specific condition
  2. Building a RetrieveMultiple plugin, which recognises the view filter from step 1 and adapts it
  3. Adding the plugin step, so the plugin will be used

 

Step 1: Definition of the view filter

In this step, we define the view filter (as System View, so it is available to all users). We include a dummy condition [Name Equals CallReportsOfInterestToCurrentUser] to make the filter query recognizable to our plugin to be built.

Step 2: building the plugin

In this step, we first create the plugin code:

          
            public class RetrieveMultiplePreOperation : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            var service = serviceFactory.CreateOrganizationService(context.UserId);

            var query = GetQuery(context);
            if (query == null)
            {
                return;
            }
            var factory = new QueryModifierFactory(context, service, tracingService);
            var modifier = factory.GetModifier(query);
            context.InputParameters["Query"] = modifier.ModifyQuery(query);
        }

        private QueryBase GetQuery(IPluginExecutionContext context) => 
            (context.InputParameters.Contains("Query") ? (QueryBase)context.InputParameters["Query"]) : null;
    }
          
      
        
        
      

The class QueryModifierFactory defines the modifier class to be used, which will modify the original query via the call modifier.ModifyQuery(query).

As a last step, we overwrite the original query input parameter with the (possibly modified) new query.

The QueryModifierFactory GetModifier method is relatively simple.

          
            public class QueryModifierFactory
    {
        private readonly IPluginExecutionContext _context;
        private readonly ITracingService _tracingService;
        private readonly IOrganizationService _service;

        public QueryModifierFactory(
            IPluginExecutionContext context,
            IOrganizationService service,
            ITracingService tracingService)
        {
            _context = context;
            _tracingService = tracingService;
            _service = service;
        }

        public IQueryModifier GetModifier(QueryBase query)
        {
            if (query is FetchExpression)
            {
                var fe = query as FetchExpression;
                if (fe.Query.Contains(CallReportQueryModifier.Identifier))
                {
                    return new CallReportQueryModifier(_context, _service, _tracingService);
                }
            }
            return new NoModificationQueryModifier();
        }
    }
          
      
        
        
      

We check if the original query is a fetch query and if so, use the query XML to look for the text CallReportsOfInterestToCurrentUser (the dummy condition from step 1). If this text is found, a CallReportQueryModifier is returned that can describe the query. Otherwise, a NoModificationQueryModifier is returned, which does not modify the original query:

          
            public class NoModificationQueryModifier : IQueryModifier
    {
        public QueryBase ModifyQuery(QueryBase query)
        {
            return query;
        }
    }
          
      
        
        
      

Now let’s look at the CallReportQueryModifier class, which modifies the query:

          
            public class CallReportQueryModifier : IQueryModifier
    {
        internal static string Identifier = "CallReportsOfInterestToCurrentUser";

        private readonly IPluginExecutionContext _context;
        private readonly IOrganizationService _service;
        private readonly ITracingService _tracingService;

        public CallReportQueryModifier(
            IPluginExecutionContext context,
            IOrganizationService service,
            ITracingService tracingService)
        {
            _context = context;
            _service = service;
            _tracingService = tracingService;
        }

        public QueryBase ModifyQuery(QueryBase query)
        {
            // if this is not a fetch expresion then return the original query
            var fetchQuery = query as FetchExpression;
            if (fetchQuery == null) return query;

            // find the interest tags for the current user 
            var tags = GetTagsForUser(_context.InitiatingUserId);

            // use LINQ to XML to access the query XML elements
            XDocument fetchXmlDoc = XDocument.Parse(fetchQuery.Query);
            var entityElement = fetchXmlDoc.Descendants("entity").FirstOrDefault();
            var entityName = entityElement.Attributes("name").FirstOrDefault().Value;

            // only modify the query if the query entity is dp_callreport, otherwise return the original query
            if (entityName != "dp_callreport") return query;

            // find the identifying filter condition for this query modifier class and remove it if found
            var filterElements = entityElement.Descendants("filter");
            filterElements
                .Descendants("condition")
                .Where(c => c.Attribute("attribute").Value.Equals("dp_name") && c.Attribute("value").Value.Equals(ModifierFlag))
                .ToList()
                .ForEach(x => x.Remove());

            // extend original query with a new IN-condition using the interest tags of the current user
            var tagsCondition = new XElement("condition",
                                    new XAttribute("attribute", "dp_tagid"),
                                    new XAttribute("operator", "in"));
            foreach (var tag in tags)
            {
                tagsCondition.Add(new XElement("value", tag.ToString()));
            }
            entityElement.Add(
                new XElement("link-entity",
                    new XAttribute("name", "dp_callreport_dp_tag"),
                    new XAttribute("from", "dp_callreportid"),
                    new XAttribute("to", "dp_callreportid"),
                    new XAttribute("link-type", "inner"),
                    new XAttribute("intersect", "true"),
                    new XElement("filter", tagsCondition))
                );
            _tracingService.Trace(fetchXmlDoc.ToString());
            return new FetchExpression(fetchXmlDoc.ToString());
        }

        private IEnumerable GetTagsForUser(Guid userId)
        {
            var results = new List();
            // perform CRM query to get the tags for the current user and return the Guids 
            return results;
        }
          
      
        
        
      

As you can see, the dummy condition, which we had added in step 1 only to make the view query recognizable for our plugin code, is first removed. Then the interest tags of the currently logged in user are retrieved and these tags are added to the query. LINQ-to-XML is used to modify the original query XML.

We then need to upload the plugin DLL into our Dynamics environment.

 

Step 3: adding the plugin step

Finally, we need to add a plugin step for the message retrieveMultiple, on the dp_callreport entity, during the pre-operation stage, because then the query can still be modified.

 

Voilà

And voilà, after this last step, our view created in step 1 is now dynamically modified by plugin code and expanded with the interest areas of the logged in user. In this way dynamic views can be created in Dynamics.