Microsoft Dynamics 365 Blog

Today CRM MVP Andriy Butenko talks about a solution he created using the plug-in technology. Andriy blogs regularly here.

When you open Microsoft Dynamics CRM and select a view that contains more records than can be shown on one page, you don’t know how many records are in this view. My Record Counter creates a ‘dummy’ record (just emulates it without creation of this record in CRM) and it shows the count of records and pages in the primary field. It works even if the count of records in fetch response is greater then 5000.

Every time this plugin is called, it calculates the count of records and pages.

Here is my code:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using Microsoft.Crm.Sdk;
   5:  using Microsoft.Crm.SdkTypeProxy;
   6:  using Microsoft.Win32;
   7:  using System.Xml;
   8:  using Microsoft.Crm.SdkTypeProxy.Metadata;
   9:  using Microsoft.Crm.Sdk.Metadata;
  10:   
  11:  namespace RecordCounter
  12:  {
  13:      public class ExecuteHandler : IPlugin
  14:      {
  15:   
  16:          #region IPlugin Members
  17:   
  18:          public void Execute(IPluginExecutionContext context)
  19:          {
  20:              if (context.Depth != 1) //To calculate count of pages and records another one fetch will be executed
  21:                  return;//so to avoid infinite loops i need to check the depth of request - if it more then 2 - return
  22:   
  23:              if (context.MessageName == "Execute" && context.InputParameters.Contains("FetchXml"))
  24:              {
  25:                  XmlDocument indoc = new XmlDocument();
  26:                  indoc.LoadXml((string)context.InputParameters["FetchXml"]);
  27:   
  28:                  //Retrieve name of entity to display
  29:                  string entityName = indoc.SelectSingleNode("//fetch/entity").Attributes["name"].InnerText;
  30:                  
  31:                  if (entityName == EntityName.savedquery.ToString() ||//To make Advanced Find Work
  32:                      entityName == EntityName.businessunitnewsarticle.ToString() ||//To make Literature work
  33:                      entityName == EntityName.resource.ToString() ||//To make Service calendar work
  34:                      entityName == EntityName.systemuser.ToString() ||//To make Service calendar work
  35:                      entityName == EntityName.equipment.ToString() ||//To make Service calendar work
  36:                      entityName == EntityName.asyncoperation.ToString())
  37:                      return;
  38:   
  39:                  //Creation of Metadata service - it will be need for retrieving of main attribute of entity
  40:                  IMetadataService mservice = context.CreateMetadataService(false);
  41:   
  42:                  RetrieveEntityRequest request = new RetrieveEntityRequest();
  43:                  request.RetrieveAsIfPublished = false;
  44:                  request.LogicalName = entityName;
  45:                  request.EntityItems = EntityItems.EntityOnly;
  46:                  string primaryFieldName = ((RetrieveEntityResponse)mservice.Execute(request)).EntityMetadata.PrimaryField;                
  47:                  //CrmService Creation
  48:                  ICrmService crmService = context.CreateCrmService(true);
  49:   
  50:                  //Count of records by page - for calculation of pages count
  51:                  int pagecount = int.Parse(indoc.DocumentElement.Attributes["count"].InnerText);
  52:   
  53:                  //I remove this attributes for retrieve of all records in current view
  54:                  indoc.DocumentElement.Attributes.Remove(indoc.DocumentElement.Attributes["count"]);
  55:                  indoc.DocumentElement.Attributes.Remove(indoc.DocumentElement.Attributes["page"]);
  56:                  
  57:                  foreach (XmlNode node in indoc.SelectNodes("//fetch/entity/attribute"))
  58:                      indoc.SelectSingleNode("//fetch/entity").RemoveChild(node);
  59:   
  60:                  foreach (XmlNode node in indoc.SelectNodes("//fetch/entity/order"))
  61:                      indoc.SelectSingleNode("//fetch/entity").RemoveChild(node);
  62:   
  63:                  foreach (XmlNode node in indoc.SelectNodes("//fetch/entity/link-entity"))
  64:                      foreach(XmlNode subnode in node.SelectNodes("./attribute"))
  65:                          node.RemoveChild(subnode);
  66:                  
  67:                  XmlAttribute aggrAttr = indoc.CreateAttribute("aggregate");
  68:                  aggrAttr.Value = "true";
  69:                  indoc.DocumentElement.Attributes.Append(aggrAttr);
  70:                  
  71:                  XmlNode field = indoc.CreateNode(XmlNodeType.Element, "attribute", null);
  72:   
  73:                  XmlAttribute nameAttr = indoc.CreateAttribute("name");
  74:                  nameAttr.Value = string.Format("{0}id", (entityName == EntityName.activitypointer.ToString() ? "activity" : entityName));
  75:                  field.Attributes.Append(nameAttr);
  76:   
  77:                  XmlAttribute aggregateAttr = indoc.CreateAttribute("aggregate");
  78:                  aggregateAttr.Value = "count";
  79:                  field.Attributes.Append(aggregateAttr);
  80:   
  81:                  XmlAttribute aliasAttr = indoc.CreateAttribute("alias");
  82:                  aliasAttr.Value = "C";
  83:                  field.Attributes.Append(aliasAttr);
  84:   
  85:                  indoc.SelectSingleNode("//fetch/entity").AppendChild(field);
  86:   
  87:                  //Xml of full result (without paging)
  88:                  string fullResult = crmService.Fetch(indoc.OuterXml);
  89:   
  90:                  XmlDocument fullResultDocument = new XmlDocument();
  91:                  fullResultDocument.LoadXml(fullResult);
  92:                  
  93:                  //Total record count by fetch
  94:                  int totalRecordCount = int.Parse(fullResultDocument.SelectSingleNode("//resultset/result/C").InnerText);
  95:                  int totalPageCount = (totalRecordCount / pagecount) + ((totalRecordCount % pagecount) == 0 ? 0 : 1);
  96:   
  97:                  string result = string.Format("Total records = {0}, Total pages = {1}", totalRecordCount, totalPageCount);
  98:   
  99:                  //Result XML which is the result shown in Grid
 100:                  XmlDocument outdoc = new XmlDocument();
 101:                  outdoc.LoadXml((string)context.OutputParameters["FetchXmlResult"]);
 102:   
 103:                  //Creation of record which will show totals
 104:                  XmlNode ResultNodeText = outdoc.CreateNode(XmlNodeType.Element, primaryFieldName, null);
 105:                  ResultNodeText.InnerText = result;
 106:                  
 107:                  XmlNode ResultNodeId = outdoc.CreateNode(XmlNodeType.Element, string.Format("{0}id", (entityName == EntityName.activitypointer.ToString() ? "activity" : entityName)), null);
 108:                  ResultNodeId.InnerText = Guid.Empty.ToString();
 109:   
 110:                  XmlNode res = outdoc.CreateNode(XmlNodeType.Element, "result", null);
 111:                  res.AppendChild(ResultNodeText);
 112:                  res.AppendChild(ResultNodeId);
 113:                  
 114:                  XmlNode ResultNodeType;
 115:                  
 116:                  //Following code repair icon for record counter icon
 117:                  if (entityName == EntityName.activitypointer.ToString())
 118:                  {
 119:                      ResultNodeType = outdoc.CreateNode(XmlNodeType.Element, "activitytypecode", null);
 120:                      ResultNodeType.InnerText = "4212";
 121:                      res.AppendChild(ResultNodeType);
 122:                  }
 123:                  
 124:                  //This code repair report view
 125:                  if (entityName == EntityName.report.ToString())
 126:                  {
 127:                      ResultNodeType = outdoc.CreateNode(XmlNodeType.Element, "reporttypecode", null);
 128:                      ResultNodeType.InnerText = "1";
 129:                      res.AppendChild(ResultNodeType);
 130:                  }
 131:   
 132:                  //Adding record with label of count of pages and records as a first record in recordset
 133:                  outdoc.SelectSingleNode("//resultset").InsertBefore(res, outdoc.SelectSingleNode("//resultset").FirstChild);
 134:                  context.OutputParameters["FetchXmlResult"] = outdoc.OuterXml;
 135:              }
 136:          }
 137:   
 138:          #endregion
 139:   
 140:      }
 141:  }

Registration of the step for this plugin:

The following screen shots tell the rest of the story.

Advanced find:

Lookup:

Public and private views:

Quick find:

Form assistant:

Auto resolve lookup view:

Source code you can download here. There are some additional comments that you might find interesting at my blog site for this post.

Cheers,

CRM MVP Andriy Butenko


We're always looking for feedback and would like to hear from you. Please head to the Dynamics 365 Community to start a discussion, ask questions, and tell us what you think!