I've upgraded the code and now it works perfectly.
UPD: according to comment of Moti Mendelovich to this thread i've one more time updated the code of the plugin and now it works even count of records in fetch response greater then 5000.
Code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Win32;
using System.Xml;
using Microsoft.Crm.SdkTypeProxy.Metadata;
using Microsoft.Crm.Sdk.Metadata;
namespace RecordCounter
{
public class ExecuteHandler : IPlugin
{
#region IPlugin Members
public void Execute(IPluginExecutionContext context)
{
if (context.Depth != 1) //To calculate count of pages and records another one fetch will be executed
return;//so to avoid infinite loops i need to check the depth of request - if it more then 2 - return
if (context.MessageName == "Execute" && context.InputParameters.Contains("FetchXml"))
{
XmlDocument indoc = new XmlDocument();
indoc.LoadXml((string)context.InputParameters["FetchXml"]);
//Retrieve name of entity to display
string entityName = indoc.SelectSingleNode("//fetch/entity").Attributes["name"].InnerText;
if (entityName == EntityName.savedquery.ToString() ||//To make Advanced Find Work
entityName == EntityName.businessunitnewsarticle.ToString() ||//To make Literature work
entityName == EntityName.resource.ToString() ||//To make Service calendar work
entityName == EntityName.systemuser.ToString() ||//To make Service calendar work
entityName == EntityName.equipment.ToString() ||//To make Service calendar work
entityName == EntityName.asyncoperation.ToString())
return;
//Creation of Metadata service - it will be need for retrieving of main attribute of entity
IMetadataService mservice = context.CreateMetadataService(false);
RetrieveEntityRequest request = new RetrieveEntityRequest();
request.RetrieveAsIfPublished = false;
request.LogicalName = entityName;
request.EntityItems = EntityItems.EntityOnly;
string primaryFieldName = ((RetrieveEntityResponse)mservice.Execute(request)).EntityMetadata.PrimaryField;
//CrmService Creation
ICrmService crmService = context.CreateCrmService(true);
//Count of records by page - for calculation of pages count
int pagecount = int.Parse(indoc.DocumentElement.Attributes["count"].InnerText);
//I remove this attributes for retrieve of all records in current view
indoc.DocumentElement.Attributes.Remove(indoc.DocumentElement.Attributes["count"]);
indoc.DocumentElement.Attributes.Remove(indoc.DocumentElement.Attributes["page"]);
foreach (XmlNode node in indoc.SelectNodes("//fetch/entity/attribute"))
indoc.SelectSingleNode("//fetch/entity").RemoveChild(node);
foreach (XmlNode node in indoc.SelectNodes("//fetch/entity/order"))
indoc.SelectSingleNode("//fetch/entity").RemoveChild(node);
foreach (XmlNode node in indoc.SelectNodes("//fetch/entity/link-entity"))
foreach(XmlNode subnode in node.SelectNodes("./attribute"))
node.RemoveChild(subnode);
XmlAttribute aggrAttr = indoc.CreateAttribute("aggregate");
aggrAttr.Value = "true";
indoc.DocumentElement.Attributes.Append(aggrAttr);
XmlNode field = indoc.CreateNode(XmlNodeType.Element, "attribute", null);
XmlAttribute nameAttr = indoc.CreateAttribute("name");
nameAttr.Value = string.Format("{0}id", (entityName == EntityName.activitypointer.ToString() ? "activity" : entityName));
field.Attributes.Append(nameAttr);
XmlAttribute aggregateAttr = indoc.CreateAttribute("aggregate");
aggregateAttr.Value = "count";
field.Attributes.Append(aggregateAttr);
XmlAttribute aliasAttr = indoc.CreateAttribute("alias");
aliasAttr.Value = "C";
field.Attributes.Append(aliasAttr);
indoc.SelectSingleNode("//fetch/entity").AppendChild(field);
//Xml of full result (without paging)
string fullResult = crmService.Fetch(indoc.OuterXml);
XmlDocument fullResultDocument = new XmlDocument();
fullResultDocument.LoadXml(fullResult);
//Total record count by fetch
int totalRecordCount = int.Parse(fullResultDocument.SelectSingleNode("//resultset/result/C").InnerText);
int totalPageCount = (totalRecordCount / pagecount) + ((totalRecordCount % pagecount) == 0 ? 0 : 1);
string result = string.Format("Total records = {0}, Total pages = {1}", totalRecordCount, totalPageCount);
//Result XML which is the result shown in Grid
XmlDocument outdoc = new XmlDocument();
outdoc.LoadXml((string)context.OutputParameters["FetchXmlResult"]);
//Creation of record which will show totals
XmlNode ResultNodeText = outdoc.CreateNode(XmlNodeType.Element, primaryFieldName, null);
ResultNodeText.InnerText = result;
XmlNode ResultNodeId = outdoc.CreateNode(XmlNodeType.Element, string.Format("{0}id", (entityName == EntityName.activitypointer.ToString() ? "activity" : entityName)), null);
ResultNodeId.InnerText = Guid.Empty.ToString();
XmlNode res = outdoc.CreateNode(XmlNodeType.Element, "result", null);
res.AppendChild(ResultNodeText);
res.AppendChild(ResultNodeId);
XmlNode ResultNodeType;
//Following code repair icon for record counter icon
if (entityName == EntityName.activitypointer.ToString())
{
ResultNodeType = outdoc.CreateNode(XmlNodeType.Element, "activitytypecode", null);
ResultNodeType.InnerText = "4212";
res.AppendChild(ResultNodeType);
}
//This code repair report view
if (entityName == EntityName.report.ToString())
{
ResultNodeType = outdoc.CreateNode(XmlNodeType.Element, "reporttypecode", null);
ResultNodeType.InnerText = "1";
res.AppendChild(ResultNodeType);
}
//Adding record with label of count of pages and records as a first record in recordset
outdoc.SelectSingleNode("//resultset").InsertBefore(res, outdoc.SelectSingleNode("//resultset").FirstChild);
context.OutputParameters["FetchXmlResult"] = outdoc.OuterXml;
}
}
#endregion
}
}
Registration of the step for this plugin:
And screenshots:
Advanced find:
Lookup:
Public and private views:
Quick find:
Form assistant:
Auto resolve lookup view:
Source code you can download here.
Hi A33ik,
ReplyDeletei tried your code, and it really works great in Accounts form and Account related advanced find and all...but it is showing an empty record instaed of total number of records and pages for other entities like Contact,Activity,Leads etc.Can you tell me where i am wrong
I see. Try to add primary field of to the views (for contact this is full name field, for activity - subject, e.t.c.) One of the features of this counter )
ReplyDeleteyes a33ik,
ReplyDeleteit worked now ..
thanks :-).
Hi a33ik,
ReplyDeleteThank you very much for this record counter it works perfectly!
I am learning a lot about plugins from this.
Please keep up the good work!
Record count is showing in first row of grid. But I want to show it right near 1 of 50 Seleted, below the grid
ReplyDeleteThis can be done only with unsupported customization. You can buy such counter here - http://geek.hubkey.com/2009/02/ms-dynamics-crm-40-record-counter-page.html
ReplyDeleteHi,
ReplyDeleteWhen i try to register the dll,a error message occured.
Unhandled Exception: System.Web.Services.Protocols.SoapException: Server was unable to process request.
PluginRegistrationTool.PluginRegistrationForm.btnRegister_Click(Object sender, EventArgs e)
This is strange. Have you build your own project or used mine?
ReplyDeleteHi A33IK,
ReplyDeleteits quiet strange taht when i tried your updated code i am getting an error message i all my crm pages.!! i used the same code given here and registered it the same way you have shown here! Can you please tell me what can be the reason...
Have you strongnamed your library?
ReplyDeleteyes :-)thanks its solved
ReplyDeleteAndriy, thank you for excellent sample!
ReplyDeleteBe my guest )
ReplyDeleteGreat code, can it be disabled from appearing in the Auto resolve lookup view?
ReplyDeleteI haven't found how to do it but you can try.
ReplyDeleteI registered the dll. See screen http://www.myimg.de/?img=bild157a08.jpg
ReplyDeleteBut I see no line with count or anything!
Any ideas?
You have to register step as shown here - http://1.bp.blogspot.com/_73OmG38HHME/Sp7F402FbBI/AAAAAAAAAQ4/ioZBhrwJjTI/s1600-h/StepRegistration.JPG
ReplyDeletewhich application is this?
ReplyDeletesorry, I 'm stupid :(
ReplyDeletethx
Andriy - First off, this is a fantastic plugin! It was simple to register and get working. Though I think I found an odd bug. After registering, my users all had issues using our mail merge templates. I am thinking that the mail merge is reading that record you are using to display the count.
ReplyDeleteAfter unregistering it, they were able to perform the MM so I'm think it is that. Do you have any advice as to how I could work around that?
Hi, Mayhem. I've checked mail merge and definitely my plugin breaks it... Add lines
ReplyDeleteif (indoc.DocumentElement.Attributes["count"] == null) return;
after XML load, rebuild plugin, reregister it. This will fix mail merge.
Andriy,
ReplyDeleteAwesome code - very neat functionality. We ran into an issue with certain srs reports after deploying this plugin. Anyone experience that?
Could you send me details of those errors at a33ik@bigmir.net?
ReplyDeleteThe hands of health. For large a gap to ensure Crm
ReplyDeleteCool plugin. I have a small problem though...
ReplyDeleteAs soon as I add the new step for this plugin, some queries to the CRM web service break - 'Server is unable to process request'.
It only seems to affect FetchXML queries - QueryExpression queries work fine.
Any ideas ?
Hi Andriy,
ReplyDeleteseems to be excatly what i need, but i cant get this plugin to work.
After registration i get the error: "FileNotFoundException. Could not load the File or assembly RecordCounter..."
Then, after copying the dll manually to the GAC, the error turns into: System.Net.WebException: The underlying connection was closed: could not establish trust relationship for the SSL/TLS secure channel.
Do you know how to solve this problem?
Thanks in advance,
Christian
>>PC
ReplyDeleteYou can redevelop this plugin a little bit to make Fetch XML queries to work. Add following lines at the start of plugin's code:
if (context.CallerOrigin == CallerOrigin.AsyncService ||
context.CallerOrigin == CallerOrigin.WebServiceApi)
return;
>>Christian
ReplyDeleteStrange error... I haven't seen such behavior in all of my developed plugins...
> You can redevelop this plugin a little bit to make Fetch XML queries to work....
ReplyDeleteExcellent. Thanks a lot.
This comment has been removed by the author.
ReplyDeleteHi Andriy,
ReplyDeletegreat plugin. i have a small problem...
i have registered this plug-in but now when i m trying to modify or edit the notes on any lead, it shows error.
Please help me out.
Thanks in advance.
For some reason, when I use this code, my grids return with no records at all. any ideas
ReplyDeleteCould you make screenshots of plugin and steps registration?
ReplyDeleteunfortunately no, because the system is on a closed and classified network. However, I double and triple checked the registration and it looks fine.
ReplyDeleteI'm afraid that I'm unable to help you without all required information...
ReplyDeleteI think I found the issue. I tried using the Linq to XML objects instead of the old xmlDocument and when I switched back to your version with the xmlDocument it worked.
ReplyDeleteSo it's either something I did wrong, or another issue related to language (i am using hebrew)
Thanks,
anyway
I figured my problem - When I used the linq object (XElement instead of xmlDocument), i needed to pass to the output parameter XElement.ToString(SaveOptions.DisableFormatting). It was the formatting that messed it up.
ReplyDeleteI found another problem with the plug-in. It will not work correctly if the primary attribute for the entity is not shown in the view.
ReplyDeleteI found a solution to this - get the list of attributes from the metadata service and then find a string attribute in the metadata that exists in the fetchXml and write the result to that attribute.
However, This causes a serious drop in performance as the retrieve action on the metadata is a lot heavier. Any ideas on how to solve this issue?
Thanks,
Gabby
I thought of another solution although this one requires some unsupported parts.
ReplyDeleteInstead of writing a "fake" row, I use the following code:
HttpContext httpContext = HttpContext.Current;
if (httpContext != null)
{
httpContext.Response.Write("var totalRowsAndPages = '" + result + "'") [in the Write method, the text is surrounded by html script tags, but I can't write them here].
What this code does is add a javascript variable that holds the result of the record counter.
I then add some javascript code when the grid page loads, that takes this value and adds it to the grids status bar, next to the 'selected X of Y' text.
This works great when the grid page first loads, but when I press the green refresh button, or delete a record (also causes the grid to refresh), the system throws an error, and the grid empties.
any thoughts on this option? if I can make it work, it would be great for performance and for grids that don't show the primary attribute of an entity.
Hi! I made some fix in BasePlugin.cs - you should add CampaignActivity as activity, because plugin fails for CampaignActivity
ReplyDeleteprotected List activities =
new List(new string[]
{ EntityName.activitypointer.ToString(),
EntityName.task.ToString(),
EntityName.email.ToString(),
EntityName.fax.ToString(),
EntityName.letter.ToString(),
__EntityName.campaignactivity.ToString()__,
EntityName.appointment.ToString(),
EntityName.serviceappointment.ToString(),
EntityName.bulkoperation.ToString()});