Dynamics 365 Plugin for Capturing Marketing List Member Events with C#


As you may know accessing the hidden entity listmember (Marketing List Members) is not possible via the SDK when writing a Dynamics 365 plugin.  After digging around from site to site, searching Google till my eyes popped out, I uncovered a couple unique message names that one can attach to the parent entity list (Marketing List) that are triggered when adding or removing contacts to a static marketing list.  The reason that I needed to capture when a contact was either added or removed from a marketing list was that I needed to modify some contact field information depending upon the type of event that occurred.

What I’ll try to show in this article is how one would interact with a static marketing list when adding or removing contacts and describe those events that follow.  After that I’ll dive into an example plugin written in C# that will show how a plugin can interact with a static marketing list when those users are added or removed.  I won’t write a full plugin since there are many ways to write one and plenty of examples on the web.  What I’ll do is show the important pieces when dealing with the C# code.

Managing Marketing List Members

When adding contacts to a marketing list it’s advised that you use either the “Add using Lookup” option or “Add using Advanced Find” option to properly trigger the required events.  There is one caveat to this process, if selecting the “Add using Advanced Find” method, only select the top option “Add only selected members to the marketing list” even if you plan on adding everyone that was returned from the advanced find query.  The second option “Add all the members returned by the search to the marketing list” fires an event that the SDK does not currently have access to nor supports at this time.  I haven’t heard of any future plans from Microsoft on supporting this event via the SDK when dealing with plugins either. 

Event Messages

There are three event messages that one can listen for.  Those messages include AddMember, AddListMembers, and RemoveMember.  Each are triggered at different times in the interface depending upon the actions of the user.  I’ll also walk-through a scenario where one such action does not trigger anything because the message is not exposed to the SDK as described in the previous section, thus when that scenario is in-acted there are no workarounds at the moment.  You’ll have to make note of that issue so that users can avoid venturing down that road.

Message: AddMember

  • Dynamic Marketing Lists
    • Plugin does not trigger for dynamic lists.
  • Static Marketing Lists
    • *** Plugin was triggered via “Add Using Lookup” ***
    • Add Members via “Add Using Advanced Find”, plugin DID NOT trigger with “Add only selected members to the marketing list”
    • Add Members via “Add Using Advanced Find”, plugin DID NOT trigger with “Add all the members returned by the search to the marketing list” – Currently there is no way to capture this event.

Message: AddListMembers

  • Dynamic Marketing Lists
    • Plugin does not trigger for dynamic lists.
  • Static Marketing Lists
    • Plugin DID NOT trigger via “Add Using Lookup”
    • *** Add Members via “Add Using Advanced Find”, plugin triggered with “Add only selected members to the marketing list” ***
    • Add Members via “Add Using Advanced Find”, plugin DID NOT trigger with “Add all the members returned by the search to the marketing list” – Currently there is no way to capture this event.

Message: RemoveMember

  • Dynamic Marketing Lists
    • Plugin does not trigger for dynamic lists.
  • Static Marketing Lists
    • From Contact Association Screen, select the contacts and click “Remove from Marketing List” 

C# Example Code

In this example I am first checking to see if the plugin fired on the list (Marketing List) entity by checking to see if the context contains the input parameter ListId.  If so, grab the value that is of type GUID and then use that to return the list name that the contact was either added to or removed from.

        if (context.InputParameters.Contains("ListId")&&context.InputParameters["ListId"] is Guid) {
          localContext.Trace(String.Format(CultureInfo.InvariantCulture,"{0}: Retrieving List Variables...", this.ChildClassName));
         
          var listId=(Guid) context.InputParameters["ListId"];
          localContext.Trace(String.Format(CultureInfo.InvariantCulture,"{0}: Found ListId: {1}", this.ChildClassName, listId));

          var lookupList = service.Retrieve(CrmEarlyBound.List.EntityLogicalName, listId, new ColumnSet(CrmEarlyBound.List.Fields.ListName));
          var listName = lookupList.GetAttributeValue<string>(CrmEarlyBound.List.Fields.ListName) ?? String.Empty;

          localContext.Trace(String.Format(CultureInfo.InvariantCulture,"{0}: List Information -- ListId: {1}; ListName: {2}", this.ChildClassName, listId, listName));

Once I’ve established a positive id on the list and have verified that the list name meets my requirements, I’ll need to grab one more important input parameter provided by the context called MessageName.  Below is an example code block for the AddMember message.  The RemoveMember message usage is the same as AddMember and both process one contact at a time.

if (context.MessageName==Common.AddMember) {
  var entityId=(Guid) context.InputParameters["EntityId"];

  localContext.Trace(String.Format(CultureInfo.InvariantCulture,"{0}: Found EntityId: {1}", this.ChildClassName, entityId));

  var lookupContact=service.Retrieve(CrmEarlyBound.Contact.EntityLogicalName,entityId,new ColumnSet( CrmEarlyBound.Contact.Fields.FullName, CrmEarlyBound.Contact.Fields.new_someField2));
  var contactFullName=lookupContact.GetAttributeValue<string>(CrmEarlyBound.Contact.Fields.FullName);
  var someField1=lookupContact.GetAttributeValue<Boolean>(CrmEarlyBound.Contact.Fields.new_someField1);
  var someField2=lookupContact.GetAttributeValue<Boolean>(CrmEarlyBound.Contact.Fields.new_someField2);

  localContext.Trace(String.Format(CultureInfo.InvariantCulture,"{0}: Entity Information -- EntityId: {1}; FullName: {2}; Field 1: {3}; Field 2: {4}", this.ChildClassName, entityId, contactFullName, someField1, someField2));

  if (someField1.Equals(false)||someField2.Equals(false)) {
    lookupContact[CrmEarlyBound.Contact.Fields.new_someField1] = true;
    lookupContact[CrmEarlyBound.Contact.Fields.new_someField2] = true;

    localContext.Trace(String.Format("{0}: ***Ready To Update", this.ChildClassName));
    service.Update(lookupContact);
    localContext.Trace(String.Format("{0}: ***Updated", this.ChildClassName));
  }
}

The final message AddListMembers is similar in nature but instead of parameters for an individual contact the parameter returns an array of member ids.  Casting the input parameter to type GUID[ ] is the way to handle this input parameter.  Once you have created your array you can then cycle through each value using a foreach loop.

if (context.MessageName==Common.AddListMembers) {
  var memberIds=(Guid[]) context.InputParameters["MemberIds"];

  foreach (var memberId in memberIds) {
    localContext.Trace(String.Format(CultureInfo.InvariantCulture,"{0}: Found MemberId: {1}", this.ChildClassName, memberId));

    var lookupContact = service.Retrieve(CrmEarlyBound.Contact.EntityLogicalName, memberId, new ColumnSet( CrmEarlyBound.Contact.Fields.FullName, CrmEarlyBound.Contact.Fields.new_someField2)); 
    var contactFullName = lookupContact.GetAttributeValue<string>(CrmEarlyBound.Contact.Fields.FullName); 
    var someField1=lookupContact.GetAttributeValue<Boolean>(CrmEarlyBound.Contact.Fields.new_someField1); 
    var someField2=lookupContact.GetAttributeValue<Boolean>(CrmEarlyBound.Contact.Fields.new_someField2);

    localContext.Trace(String.Format(CultureInfo.InvariantCulture,"{0}: Entity Information -- MemberId: {1}; FullName: {2}; Field 1: {3}; Field 2: {4}", this.ChildClassName, memberId, contactFullName, someField1, someField2));
    
    if (new_someField1.Equals(false)||new_someField2.Equals(false)) {
      lookupContact[CrmEarlyBound.Contact.Fields.new_someField1] = true;
      lookupContact[CrmEarlyBound.Contact.Fields.new_someField2] = true;

      localContext.Trace(String.Format("{0}: ***Ready To Update", this.ChildClassName));
      service.Update(lookupContact);
      localContext.Trace(String.Format("{0}: ***Updated", this.ChildClassName));
    }
  }
}

Food For Thought:

ExecuteMultipleRequest Object

Keep in mind that I’m taking the easy way out and simply looping though and updating a single contact at a time.  A more efficient way is to use the ExecuteMultipleRequest object and build the statement out where you can execute in bulk.

I’ve provided an example of how one would use the ExecuteMultipleRequest object in conjunction with the AddMemberListRequest object.  This object is used when adding multiple contact members to multiple static marketing lists.  This same code can be used when you want to remove multiple contacts from multiple static marketing lists by replacing the above with the RemoveMemberListRequest object.

var multipleRequest = new ExecuteMultipleRequest() {
  Settings = new ExecuteMultipleSettings() {
    ContinueOnError = false
   ,ReturnResponses = true
  }
 ,Requests=new OrganizationRequestCollection()
};

foreach (var entity in myCollection.Entities) {
  /* Do some stuff here */

  var addRequest = new AddMemberListRequest {
    EntityId=theEntityId
   ,ListId=theListId
  }
}

if (multipleRequest.Requests.Count>0) {
  var multipleResponses = (ExecuteMultipleResponse) service.Execute(multipleRequest);
  var responses = (ExecuteMultipleResponseItemCollection) multipleResponses.Results["Responses"];

  foreach (var responseItem in responses) {
    localContext.Trace(responseItem.Fault != null ? String.Format("{0}: Multiple Request Response Item Returned a fault that has occurred", this.ChildClassName) : String.Format("{0}: Item(s) purged successfully", this.ChildClassName));
  }
}

Keep in mind that when using this method the ExecuteMultipleRequest will return a ExecuteMultipleResponse object.  You’ll use this object to verify each record was processed successfully and if not will provide you with some information on the error.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s