Agile Xrm

A blog about all things Dynamics CRM and Agile

Using Custom Actions as Thick adapters with Dynamics CRM

Over the years I have been involved with many CRM projects where we have had to integrate with other systems.

The majority of the integration is where CRM would pull data from another system or update the other system when actions take place in CRM. The design pattern is fairly simple here, a CRM plugin retrieves/updates the data, normally through an integration layer.

On occasion, another system needs to pull data from Dynamics CRM, this is another task entirely.  If the integration layer is on Biztalk, it becomes fairly simple but when your integration layer is something else, like a JAVA layer, you run into a bit of trouble when integrating with CRM. The CRM web services, in their current form (the new Rest interfaces should assist with this) are very difficult to navigate with other, non-.Net programming languages. Not to mention the CRM data structures are very complex and extremely confusing for someone that does not have any CRM knowledge.

The design pattern that normally helps here is to put a thick adapter written in .Net between the integration layer and CRM that can handle all the complex logic and translations from CRM into simple objects that can then be exposed in the integration layer. http://www.soainstitute.org/resources/articles/soa-watch-soa-and-adapters
What I do not like about this approach is it complicates the CRM deployment, everything in CRM is stored in solutions, but now having this external thick adapter adds extra complexity to the CRM deployments. If you need to add a field on an entity and that field is required by the other system you need to build and deploy the thick adapter.

Solution: Enter custom actions….

The solution we came up with here was to write the thick adapter logic into a custom action. The integration layer could then simply call the Execute operation on the CRM web services for the custom action and get/set the data accordingly. This keeps our CRM deployment nice and neat and also allows the integration layer to have fairly simple interfaces.

Below is an example of a retrieveMultiple request that returns the results a JSON string back to the calling system/integration layer. 

The custom action accepts parameters to lookup on and then we do a QueryExpression to retrieve the data from CRM. We then translate the data into a simple object and pass that back in the output parameter of the custom action.


 

Below is the setup of our simple appointment object.

class AppointmentDetail
    {
        public string interactiontype;
        public string subject;
        public Guid activityid;
        public string scheduledstart;
        public string scheduledend;
        public String prioritycode;
        public String statuscode;
        public String location;
        public System.Collections.Generic.List<AppointmentAttendee> requiredattendees;
        public System.Collections.Generic.List<AppointmentAttendee> optionalattendees;
        public RegardingClient regardingclient;
    }


    class AppointmentAttendee
    {
        public String Name;
    }

    class RegardingClient
    {
        public String Name;
        public String Mobile;
        public String BusinessPhone;
        public String Type;
    }

 

Below is the code in the plugin

var DomainName = context.InputParameters["UserName"].ToString();
                var StartDate = context.InputParameters["StartDate"].ToString();
                var EndDate = context.InputParameters["EndDate"].ToString();

                QueryExpression appointmentQuery = new QueryExpression(Appointment.EntityLogicalName);
                appointmentQuery.ColumnSet = new ColumnSet("activitytypecode", "subject", "statecode", "activityid", "scheduledstart", "scheduledend", "prioritycode", "statuscode", "location", "requiredattendees", "optionalattendees", "regardingobjectid", "fnb_interactiontype");
                appointmentQuery.Criteria.AddCondition(new ConditionExpression("scheduledstart", ConditionOperator.GreaterEqual, StartDate));
                appointmentQuery.Criteria.AddCondition(new ConditionExpression("scheduledstart", ConditionOperator.LessEqual, EndDate));
                appointmentQuery.Criteria.AddCondition(new ConditionExpression("regardingobjectid", ConditionOperator.NotNull));

                LinkEntity contacts = new LinkEntity("activitypointer", "contact", "regardingobjectid", "contactid", JoinOperator.LeftOuter);
                contacts.Columns = new ColumnSet("fullname", "mobilephone", "telephone1");
                contacts.EntityAlias = "contact";
                appointmentQuery.LinkEntities.Add(contacts);

                LinkEntity accounts = new LinkEntity("activitypointer", "account", "regardingobjectid", "accountid", JoinOperator.LeftOuter);
                accounts.Columns = new ColumnSet("name", "telephone1", "address1_telephone2");
                accounts.EntityAlias = "accounts";
                appointmentQuery.LinkEntities.Add(accounts);

                LinkEntity owner = new LinkEntity("activitypointer", "systemuser", "owninguser", "systemuserid", JoinOperator.Inner);
                owner.LinkCriteria.AddCondition(new ConditionExpression("domainname", ConditionOperator.Like, "%" + DomainName + "%"));
                appointmentQuery.LinkEntities.Add(owner);

                appointmentQuery.AddOrder("scheduledstart", OrderType.Ascending);

                var appointmentCollection = orgService.RetrieveMultiple(appointmentQuery);

                System.Collections.Generic.List<AppointmentRecord> appList = new System.Collections.Generic.List<AppointmentRecord>();

                foreach (var appointment in appointmentCollection.Entities)
                {
                    Appointment app = appointment.ToEntity<Appointment>();
                    AppointmentRecord appItem = new AppointmentRecord();
                    appItem.appointment = new AppointmentDetail();

                    appItem.appointment.activityid = app.ActivityId.Value;
                    if (app.Subject != null) appItem.appointment.subject = app.Subject;
                    if (app.Location != null) appItem.appointment.location = app.Location;
                    appItem.appointment.scheduledstart = app.ScheduledStart.Value.ToLocalTime().ToString() ;
                    appItem.appointment.scheduledend = app.ScheduledEnd.Value.ToLocalTime().ToString();                   
//we would normally do a retrieve for the metadata here but for performance reasons it has been done this way...
                    if (app.PriorityCode != null)
                    {
                        switch (app.PriorityCode.Value)
                        {
                            case (int)ActivityPointerPriorityCode.High:
                                appItem.appointment.prioritycode = "High";
                                break;
                            case (int)ActivityPointerPriorityCode.Normal:
                                appItem.appointment.prioritycode = "Normal";
                                break;
                            case (int)ActivityPointerPriorityCode.Low:
                                appItem.appointment.prioritycode = "Low";
                                break;
                            default:
                                appItem.appointment.prioritycode = "none";
                                break;
                        }
                    }

                    if (app.fnb_InteractionType != null)
                    {
                        switch (app.fnb_InteractionType.Value)
                        {
                            case (int)fnb_interactiontype.ClientReview:
                                appItem.appointment.interactiontype = "ClientReview";
                                break;
                            case (int)fnb_interactiontype.CourtesyCall:
                                appItem.appointment.interactiontype = "CourtesyCall";
                                break;
                            case (int)fnb_interactiontype.Leadership:
                                appItem.appointment.interactiontype = "Leadership";
                                break;
                            case (int)fnb_interactiontype.NewClientTakeon:
                                appItem.appointment.interactiontype = "NewClientTakeon";
                                break;
                            case (int)fnb_interactiontype.Sales:
                                appItem.appointment.interactiontype = "Sales";
                                break;
                            case (int)fnb_interactiontype.Service:
                                appItem.appointment.interactiontype = "Service";
                                break;
                            default:
                                appItem.appointment.interactiontype = "none";
                                break;
                        }
                    }

                    if (app.StatusCode != null)
                    {
                        switch (app.StatusCode.Value)
                        {
                            case (int)appointment_statuscode.Busy:
                                appItem.appointment.statuscode = "Busy";
                                break;
                            case (int)appointment_statuscode.Canceled:
                                appItem.appointment.statuscode = "Canceled";
                                break;
                            case (int)appointment_statuscode.Completed:
                                appItem.appointment.statuscode = "Completed";
                                break;
                            case (int)appointment_statuscode.Free:
                                appItem.appointment.statuscode = "Free";
                                break;
                            case (int)appointment_statuscode.OutofOffice:
                                appItem.appointment.statuscode = "OutofOffice";
                                break;
                            case (int)appointment_statuscode.Tentative:
                                appItem.appointment.statuscode = "Tentative";
                                break;
                            default:
                                appItem.appointment.statuscode = "none";
                                break;
                        }
                    }

                    if (app.RegardingObjectId != null)
                    {
                        switch (app.RegardingObjectId.LogicalName)
                        {
                            case "contact":
                                appItem.appointment.regardingclient = new RegardingClient();
                                appItem.appointment.regardingclient.Name = ((AliasedValue)app.Attributes["contact.fullname"]).Value.ToString();
                                appItem.appointment.regardingclient.Mobile = ((AliasedValue)app.Attributes["contact.mobilephone"]).Value.ToString();
                                appItem.appointment.regardingclient.BusinessPhone = ((AliasedValue)app.Attributes["contact.telephone1"]).Value.ToString();
                                appItem.appointment.regardingclient.Type = "individual";
                                break;
                            case "account":
                                appItem.appointment.regardingclient = new RegardingClient();
                                appItem.appointment.regardingclient.Name = ((AliasedValue)app.Attributes["accounts.name"]).Value.ToString();
                                appItem.appointment.regardingclient.Mobile = ((AliasedValue)app.Attributes["accounts.telephone1"]).Value.ToString();
                                appItem.appointment.regardingclient.BusinessPhone = ((AliasedValue)app.Attributes["accounts.address1_telephone2"]).Value.ToString();
                                appItem.appointment.regardingclient.Type = "juristic";
                                break;
                            default:
                                break;     
                        }
                    }

                    appItem.appointment.requiredattendees = new System.Collections.Generic.List<AppointmentAttendee>();
                    foreach (var attendee in app.RequiredAttendees)
                    {
                        AppointmentAttendee appAttendee = new AppointmentAttendee();
                        appAttendee.Name = attendee.PartyId.Name.ToString();
                        appItem.appointment.requiredattendees.Add(appAttendee);
                    }        
                    appItem.appointment.optionalattendees = new System.Collections.Generic.List<AppointmentAttendee>();
                    foreach (var attendee in app.OptionalAttendees)
                    {
                        AppointmentAttendee appAttendee = new AppointmentAttendee();
                        appAttendee.Name = attendee.PartyId.Name.ToString();
                        appItem.appointment.optionalattendees.Add(appAttendee);
                    }    
                    appList.Add(appItem);      
                }       

                var javaScriptSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
                var jsonString = javaScriptSerializer.Serialize(appList);

                context.OutputParameters["JSONResult"] = jsonString;

 

 

Loading