Saturday, June 11, 2011

Rollup activities for custom entities in Microsoft Dynamics CRM 4.0

Everybody who works with Microsoft Dynamics CRM knows that it is possible to see activities for special entities like contact, account or opportunity but not for custom entities. I had task to develop such kind of functionality. I proposed easier solution - report but customer wanted to have all activities in standard grid.



I made some investigations and understood that it is possible to do with handling of retrievemultiple message. I developed this plugin for one custom entity and after I decided to make it more flexible with possibility to configure without additional development. Here is the code of plugin:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk;
using System.Xml;
using Microsoft.Crm.SdkTypeProxy;
using System.Web;
using Microsoft.Crm.Sdk.Query;
using System.Web.Services.Protocols;

namespace RollupActivitites
{
public class RetrieveMultipleHandler : IPlugin
{
#region Privates

private XmlDocument _settings = null;

#endregion Privates

#region CTOR

public RetrieveMultipleHandler(string config, string secureconfig)
{
_settings = new XmlDocument();
_settings.LoadXml(config);
}

#endregion CTOR

#region IPlugin Members

public void Execute(IPluginExecutionContext context)
{
if (context.MessageName != MessageName.RetrieveMultiple || context.PrimaryEntityName != EntityName.activitypointer.ToString())
return;

if (!(context.CallerOrigin is ApplicationOrigin))
return;

HttpContext webcontext = HttpContext.Current;
if (webcontext == null)
return;

string callerentitytype = null;
string callerentityidstring = null;

if (webcontext.Request.Params.AllKeys.Contains("oType"))
{
callerentitytype = webcontext.Request.Params["oType"];
callerentityidstring = webcontext.Request.Params["oId"];
}
else
{
string requeststring = webcontext.Request.UrlReferrer.Query;
requeststring = requeststring.Substring(1);

string[] parts = requeststring.Split(new string[] { "=", "&" }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < parts.Length - 1; i++)
if (parts[i].ToLower() == "otype")
callerentitytype = parts[i + 1];
else if (parts[i].ToLower() == "oid")
callerentityidstring = parts[i + 1];

}

if (string.IsNullOrEmpty(callerentitytype))
return;

Guid callerentityid = new Guid(callerentityidstring.Replace("%7b", "").Replace("%7d", ""));

XmlNodeList childentities = _settings.SelectNodes(string.Format("//Settings/Setting[ParentEntityType={0}]", callerentitytype));
if (childentities.Count == 0)
return;

ICrmService crmservice = context.CreateCrmService(false);
List<object> childids = new List<object>();

foreach (XmlNode child in childentities)
{
string childentityname = child.SelectSingleNode("./ChildEntityName").InnerText;
string lookupfield = child.SelectSingleNode("./Lookup").InnerText;

QueryByAttribute query = new QueryByAttribute();
query.Attributes = new string[] { lookupfield };
query.ColumnSet = new ColumnSet(new string[] { string.Format("{0}id", childentityname) });
query.EntityName = childentityname;
query.Values = new object[] { callerentityid };

RetrieveMultipleRequest request = new RetrieveMultipleRequest();
request.Query = query;
request.ReturnDynamicEntities = true;

RetrieveMultipleResponse response = null;

try
{
response = crmservice.Execute(request) as RetrieveMultipleResponse;
}
catch (SoapException e)
{
throw new Exception(e.Detail.InnerText);
}

foreach (DynamicEntity childentity in response.BusinessEntityCollection.BusinessEntities)
childids.Add((childentity[string.Format("{0}id", childentityname)] as Key).Value);
}

if (childids.Count == 0)
return;

childids.Add(callerentityid);

QueryExpression resultquery = context.InputParameters[ParameterName.Query] as QueryExpression;
if (resultquery == null)
return;

if (resultquery.LinkEntities.Count == 0)
return;

LinkEntity link = resultquery.LinkEntities[0] as LinkEntity;
ConditionExpression condition = link.LinkCriteria.Conditions[0] as ConditionExpression;
condition.Operator = ConditionOperator.In;
condition.Values = childids.ToArray();
}

#endregion
}
}


To make it work you also have to configure it in the proper way - you should insert configure xml to unsecure configuration of step registration dialogue:



Here are registration parameters:

<?xml version="1.0" standalone="yes"?>
<Settings>
<Setting>
<ParentEntityType>10004</ParentEntityType>
<ChildEntityName>custom_child</ChildEntityName>
<Lookup>custom_parentid</Lookup>
</Setting>
</Settings>


ParentEntityType is code of entity from which you want see activities of child entities.
ChildEntityName is scheme name of child entity.
Lookup is the relational field between parent and child entities.

Result of work of this plugin:


Activities of child entity.


Activities of parent entity.

This plugin work for History too.

You can download source code and compiled assembly here: