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.
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