Sheep

Extending Umbraco: Contour and Campaign Monitor Integration

I was recently tasked to move our company’s email campaign sends out of our own servers to the Campaign Monitor service. This meant we would need to customize our Umbraco Contour forms to integrate with the Campaign Monitor api. I must admit I was excited. I have admired Umbraco for its flexibility and its extensibility. However, beyond a few simple proof of concepts, I had never gotten a chance to dig in and extend its functionality for a real world application.

A quick search of the internet and specifically the very helpful Umbraco community forums at our.umbraco.org led me to the work of fellow Umbracian, Christian Palm which looked very close to what I was looking to do.

I saw this and said - YES! It can be done!

So It was Christian Palm’s screencast , a few our.umbraco.org  forum posts and the helpful examples the Contour.contrib project which gave me a head start and inspiration for my final solution.

The first thing that I needed was a prevalue list to enable the user to chose a list of email lists to which to subscribe (i.e. Newsletter, Product updates, etc). For this, we inherit from the FieldPreValueSourceType.

namespace Liquid.Contour.Extensions
{
    public class CampaignMonitorPrevalueSourceType: FieldPreValueSourceType
    {

        public CampaignMonitorPrevalueSourceType()
        {
            this.Id = new Guid("9fd33370-2fb6-4fbb-88e2-3d14d2e65772");
            this.Name = "Campaign Monitor Mailing Lists";
            this.Description = "List of Mailing Lists available within Campaign Monitor";

        }

 

The main method to override here is the “getPreValues” method which returns the values to show. In our case, which mailing lists to include on the form. But what if we only want to include a subset of all our lists? We need a way for the cms user to select which lists - of all possible lists - should be included in that subset.

To achieve that, we must first provide an interface for the user to make this selection: We do that by inheriting the FieldSettingType class and overriding its Rendercontrol method to return a webcontrol (in our case a checkboxlist):

namespace Liquid.Contour.Extensions
{
    public class CampaignMonitorListsFieldSettingsType: FieldSettingType
    {
        private CheckBoxList cbl = new CheckBoxList();
        private string _val = string.Empty;

       
        public override System.Web.UI.WebControls.WebControl RenderControl(Umbraco.Forms.Core.Attributes.Setting setting, Form form)
        {
            cbl.ID = setting.GetName();
            cbl.RepeatLayout = RepeatLayout.Flow;
            cbl.CssClass = "cml";

            string clientId = WebConfigurationManager.AppSettings["api_client_id"] as string;

            Client c = new Client(clientId);
            IEnumerable<BasicList> lists = c.Lists();

            System.Web.UI.WebControls.ListItem li;
            foreach (BasicList l in lists)
            {
                li = new System.Web.UI.WebControls.ListItem(l.Name, l.ListID);
                cbl.Items.Add(li);
                
            }          
      
      
           IEnumerable<string> vals = _val.Split(',');

           foreach (string v in vals)
           {
               System.Web.UI.WebControls.ListItem selLi = cbl.Items.FindByValue(v);

               if (selLi != null)
               {
                   selLi.Selected = true;
               }
           }

            return cbl;

        }
    }
}

 

Then we can assign this control to a property by decorating it with the Umbraco.Forms.Core.Attributes.Setting flag:

 [Umbraco.Forms.Core.Attributes.Setting(
            "ListsToInclude", 
            description = "The selected lists to which the user can subscribe",  
            control = "Liquid.Contour.Extensions.CampaignMonitorListsFieldSettingsType", 
            assembly="Liquid.Contour.Extensions"
            )]
        public string ListsToInclude
        {
            get;
            set;
        }

 

We can then use this user-set “List of lists” to loop through in our getPreValues method.

  public override List<PreValue> GetPreValues(Field field)
        {
            List<PreValue> pvs = new List<PreValue>();
            string clientId = WebConfigurationManager.AppSettings["api_client_id"] as string;
            Client c = new Client(clientId);
            IEnumerable<BasicList> lists = c.Lists();

            PreValue pv;
           

            IEnumerable<string> vals = ListsToInclude.Split(',');

            foreach (string v in vals)
            {
                BasicList l = lists.FirstOrDefault(s => s.ListID == v);
                pv = new PreValue();
                pv.Value = l.Name;
                pv.Id = l.ListID;

                pvs.Add(pv);
               

            }
           
            return pvs;
        }

 

Now we have the final subset of mailing lists available for use in checkbox lists within Contour forms:

Blog Image 2

That’s great....but how can we actually use these lists...and subscribe users to our mailing lists???

The answer is a custom workflow:

Inherit the WorkflowType class and override the Execute method. In this method, you do whatever it is you want done as part of that workflow before returning “complete”. In our case, we want to subscribe the user to a set of mailing lists. These lists can be either selected as part of a checkbox list (using our previously created prevalues) or selected as a setting within the workflow to “auto-enroll” users. We provide this “auto enroll” list the using the same method as our prevalue:

 [Umbraco.Forms.Core.Attributes.Setting(
            "Mailing Lists", 
            description = "Send to these Campaign Monitor Lists", 
            control = "Liquid.Contour.Extensions.CampaignMonitorListsFieldSettingsType", 
            assembly="Liquid.Contour.Extensions")]
        public string ListsToAddTo { get; set; }

We then take this combined list (of user selected and configued auto-selected lists) and enroll the user:

        public override Umbraco.Forms.Core.Enums.WorkflowExecutionStatus Execute(Record record, RecordEventArgs e)
        {
            string clientId = WebConfigurationManager.AppSettings["api_client_id"] as string;
            Client c = new Client(clientId);
            string sentToCMVal = "false";
            IEnumerable<string> listIds = ListsToAddTo.Split(',');
          
            //Check fields passed in for additional/optional lists on the form.  
            //for each checkbox, add to the listIds already "auto-enrolled"
                      
            foreach (string v in record.GetRecordField("#newsletterCaption").Values)
            {
                if (v.ToString().Length > 2)
                {
                    listIds = listIds.Concat(new[] { v });
                }

            }

            foreach (string listId in listIds)
            {
                if (listId.Length > 2)
                {
                   
                    Subscriber s = new Subscriber(listId);
                    string name = (record.GetRecordField("Name").Values.Count > 0) ? record.GetRecordField("Name").Values[0].ToString() : string.Empty;
                    string sid = s.Add(record.GetRecordField("Email").Values[0].ToString(), name, null, true);                     
                   
                }

            }
            
            return Umbraco.Forms.Core.Enums.WorkflowExecutionStatus.Completed;
        }

 

The custom workflow can now be added to the form:

Blog Image 6

Blog Image 5

 

But what if something goes wrong? What if Campaign Monitor has a hiccup? What if a user requests to be added after the fact and you want to manually add them to the list? Sure, you could log into Campaign Monitor to do this, but why not do it right from the Contour interface within Umbraco? You can!

 

Check out the RecordActionType and RecordSetActionType classes. 

 

 

RecordActionType class allows you to perform actions on individual form entries:

Blog Image 3

 

 

The RecordSetActionType class allows you to perform bulk operations on multiple records:

Blog Image 4

 

 

...and to pick which lists to send these records, simply decorate your lists property with the Umbraco.Forms.Core.Attributes.Setting as before to produce this:

Blog Image 1

 

So as you can see, extending Contour can be very powerful and the limits are really only your imagination.    Are there quirks?  Sure there are.  For instance, I was somehow unable to update records in a workflow when placing the workflow on the "submission" event.  However, the same workflow had no issues when placed on the "Approved" event.  It is not perfect, but it is generally well done and the provider model is very developer friendly.  I look forward to a chance to play with the new Contour 3.0 next!

Looking for help in Umbraco? We're an Umbraco Certified Partner. Contact us and let's talk about how Liquid can help you!