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:

11 comments:

  1. Hi Andriy,

    I am trying to implement this solution in CRM 2011. I have registered plugin on RetrieveMultiple message on activitypointer, Plugin triggering only when i am viewing activities from Activities link on Home page. when i am checking activities on Account entity its not firing the debugger to check.

    Any help from you.. RetrieveMultiple will trigger from any entity right while viewing activities.

    ReplyDelete
  2. Hello Guru Prasad,
    For some entities like Account and Contact Rollup message is triggered. Sample you can check here - http://a33ik.blogspot.com/2010/01/handling-rollup-message-with-plugins-in.html

    ReplyDelete
  3. Hi Andriy,

    Thanks for your response.. The solution which you have provided is an unsupported one. Is there any supported solution to implement activities Roll-up functionality for custom entities?

    BTW,I am using CRM 2011 online version, we can't perform unsupported customization's on it..

    ReplyDelete
  4. Any thoughts on CRM Online? also we have multiple child entities.

    ReplyDelete
    Replies
    1. Hello David,

      Workaround provided in article works with webcontext that is unavailable in SandBox plugins. So I believe it could be complicated to do that. Possible workaround is development of custom webresource using html+js or SilverLight.

      Delete
  5. Hi Andrii,

    Great plugin and works nicely with CRM 2011!

    Big thanks

    ReplyDelete
    Replies
    1. Hi danisti,

      I have same requirement to view child entity activities in parent entity in ms crm 2011. I referred andrii's ms crm 4.0 plugin but stuck with converting it ms crm 2011.So can you please provide the ms crm 2011 plugin to fix this issue.This is my id -> kpshetty85@gmail.com

      Thanks in Advance.

      Delete
  6. Hello Andrii

    Great work !! - I have modified and developed your plugin for CRM 2011.

    I have a question about the prefiltering view in the account entity.

    The new_activity lookup Activity Data is now only shown in the Related View and not in the linked associated view.

    It is possible to show the activity in the linked associated view and not in the Related View.
    --> This means in the same view as Opportunity Activities - or Contact Activities


    Regards,



    Andreas

    ReplyDelete
    Replies
    1. Hello Andreas!

      Not sure what do you mean under Related and Linked Associated view...

      Kind regards,
      Andrii.

      Delete
    2. Sorry I mean the
      filter:
      RollupRelatedByParty (all activities from i.e. the account and the child entities like Contact)
      and
      RetrieveByParty (only Activitities which are directly linked (related) to the account.

      With the modified Plugin the activities of my custom child entity with own activities are only shon in the RetrieveByParty view.

      I have seen that you posted are unsupported SQL Statement for enabling SDK Message Rollup.

      Is this perhaps a possible way to show the activitities of the custom entity in the RollupRelatedByParty View?
      Do you have some experiences with this "dirty" change?
      -->http://a33ik.blogspot.com/2010/01/handling-rollup-message-with-plugins-in.html

      Regards,

      Andreas


      Delete