Tuesday 11 November 2014

Umbraco Contour - Campaign Monitor Integration

There are a number of blog posts, forum posts and documents floating around about how to integrate with Campaign Monitor from Umbraco Contour. I used all of these to reach my eventual solution.

Rather than say what was wrong with each of these, I’d say that they were all instrumental in achieving the final result, but none of them on their own was enough and I would like to repay the help by making this the definitive post on how to achieve this.

Please note that this solution was developed in Umbraco 6.2.2 and I have not yet tried within an Umbraco 7 installation, though my assumption is that it should work there too.

Also to note that while this post will get you a working solution, I was up against a deadline and I have not had as much time to spend on this as I would like to fully understand the code, therefore at the end of the post I have included a ‘what’s next’ section with some thoughts and when time permits I intend to follow up on this to refactor the code once I fully understand what’s happening.

I’d like to acknowledge the following:
·         Tim Geyssens - https://twitter.com/timgeyssens
·         Greg Fyans - https://twitter.com/gfyans
·         Ravi Motha - https://twitter.com/ravimotha

Requirements

One thing that some of the other resources were lacking was to tell you which dll’s you need. You will need:
·         Createsend_dotnet – a .NET wrapper for the campaign monitor API. You can get this from GitHub: https://github.com/campaignmonitor/createsend-dotnet
·         System.Web.UI.WebControls
·         Umraco.Forms.Core

API Key

You will need the campaign monitor API key. This page will show you how to find it: http://help.campaignmonitor.com/topic.aspx?t=206
I saved mine as an appSettings entry in web.config in case it changes later.

Client ID

You will also need a client ID. Within campaign monitor you may have several clients and each of those may have several lists, therefore, we need to ensure we’re getting the lists of the correct client. This page will show you how to find this: https://www.campaignmonitor.com/api/getting-started/#clientid
Again I saved this in web.config.

Render Control

The first thing we need is a checkbox list containing each of the lists within campaign monitor to display in Contour.

To do this we need a class which inherits from FieldSettingType class.

public class CampaignMonitorListsFieldSettingsType : FieldSettingType
{

We also need to instantiate a new CheckBoxList object whose class is contained within System.Web.UI.WebControls namespace.

private CheckBoxList cbl = new CheckBoxList();
private string _val = string.Empty;

We need to assign the returned CheckBoxList to a string which is decorated with a special attribute which Contour recognises and creates a space in memory whenever contour is loaded, or so the documentation tells me.

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

Notice that the control is the name of our class fully namespaced and the assembly is, well, the assembly J

Now we can go about overriding the methods:

public override string Value
{
get
       {
              return cbl.Items.ToString();
       }
       set
       {
              if (!string.IsNullOrEmpty(value))
                _val = value;
       }
 }
This basically gets the value of the checked lists in Contour and saves them. This will be clearer in a minute.

public override WebControl RenderControl(Umbraco.Forms.Core.Attributes.Setting setting, Form form)
        {
            cbl.ID = setting.GetName();
            cbl.RepeatLayout = RepeatLayout.Flow;
            cbl.CssClass = "cml";
            string apiKey = ConfigurationManager.AppSettings["CampaignMonitorAPIKey"] as string;
            string clientId = ConfigurationManager.AppSettings["CampaignMonitorClientID"] as string;
            AuthenticationDetails authDetails = new ApiKeyAuthenticationDetails(apiKey);
            Client client = new Client(authDetails, clientId);
            IEnumerable<BasicList> lists = client.Lists();
            ListItem li;
            foreach (BasicList list in lists)
            {
                li = new ListItem(list.Name, list.ListID);
                cbl.Items.Add(li);
            }
            IEnumerable<string> vals = _val.Split(',');
            foreach (string v in vals)
            {
                ListItem selLi = cbl.Items.FindByValue(v);
                if (selLi != null)
                {
                    selLi.Selected = true;
                }
            }
            return cbl;
        }
What we are doing in this method is going to Campaign Monitor API and getting all the lists that belong to the client ID and returning them as a CheckBoxList.

Pre Value Source

The next class we need, displays the lists as a CheckBoxList so that the user can select which lists they would like to include.

public class CampaignMonitorExtension : FieldPreValueSourceType
    {
        [Umbraco.Forms.Core.Attributes.Setting("ListsToInclude",
       description = "The selected lists to which the user can subscribe",
       control = "MyApp.WebApp.Utils.CampaignMonitorListsFieldSettingsType",
       assembly = "MyApp.WebApp")]
        public string ListsToInclude { get; set; }

        public CampaignMonitorExtension()
        {
            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";
        }

        public override List<PreValue> GetPreValues(Field field)
        {
            List<PreValue> pvs = new List<PreValue>();
            string apiKey = ConfigurationManager.AppSettings["CampaignMonitorAPIKey"] as string;
            string clientId = ConfigurationManager.AppSettings["CampaignMonitorClientID"] as string;
            AuthenticationDetails authDetails = new ApiKeyAuthenticationDetails(apiKey);
            Client client = new Client(authDetails, clientId);
            IEnumerable<BasicList> lists = client.Lists();
            PreValue preValue;
            IEnumerable<string> vals = ListsToInclude.Split(',');
            foreach (string v in vals)
            {
                BasicList l = lists.FirstOrDefault(s => s.ListID == v);
                preValue = new PreValue();
                preValue.Value = l.Name;
                preValue.Id = l.ListID;
                pvs.Add(preValue);
            }
            return pvs;
        }

        public override List<Exception> ValidateSettings()
        {
            List<Exception> exs = new List<Exception>();
            if (string.IsNullOrEmpty(ListsToInclude))
            {
                exs.Add(new Exception("Lists To Include is Empty"));
            }
            return exs;
        }
    }

Workflow

The final piece of the jigsaw is a workflow type.

Please pay special attention to the code which signs the user up – very important!

This is what happens when a user clicks the checkbox(es) and submitting the form:

public class CampaignMonitorSend : WorkflowType
    {
        [Umbraco.Forms.Core.Attributes.Setting("Lists To Include",
        description = "The selected lists to which the user can subscribe",
        control = "MyApp.WebApp.Utils.CampaignMonitorListsFieldSettingsType",
        assembly = "MyApp.WebApp")]
        public String ListsToAddTo { get; set; }

        public CampaignMonitorSend()
        {
            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";
        }

        public override WorkflowExecutionStatus Execute(Record record, RecordEventArgs e)
        {
            string apiKey = ConfigurationManager.AppSettings["CampaignMonitorAPIKey"] as string;
            string clientId = ConfigurationManager.AppSettings["CampaignMonitorClientID"] as string;
            AuthenticationDetails authDetails = new ApiKeyAuthenticationDetails(apiKey);
            Client client = new Client(authDetails, clientId);
            IEnumerable<string> listIds = ListsToAddTo.Split(',');

            foreach (string listId in listIds)
            {
                //Only add to campaign monitor if the signup checkbox is checked
                if (listId.Length > 2 && record.GetRecordField(new Guid("16922c9c-ffb6-4986-9c1e-324a9fa87e6a")).Values[0].ToString().ToLower() == "true")
                {
                    Subscriber s = new Subscriber(authDetails, 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;
        }

        public override List<Exception> ValidateSettings()
        {
            List<Exception> exs = new List<Exception>();

            if (string.IsNullOrEmpty(ListsToAddTo))
                exs.Add(new Exception("Lists to add to are empty"));

            return exs;
        }
    }

By the time you have this code in place, you will have a working solution similar to what this blog post tried to achieve: http://www.liquidint.com/blog/extending-umbraco-contour-campaign-monitor-integration/

What’s next

As I said at the beginning of this post, I did not have time to refactor and fully understand this code, but my plan is to do that and then possibly develop a package which a number of people have been requesting in the forums.

A few things to note:

  • When you have added your custom pre value source in contour and you select lists in the Checkbox list and save, the values ARE updated in Umbraco but the checkbox state is not remembered, so the next time you visit this you will see a list of unchecked boxes.  I plan to fix this ASAP.
  • The code is supposed to allow you to choose this in the workflow UI, so that you can display whichever lists you want to sign users up to in the front end. This does not currently work. My personal feeling on this is that you may not wish to hand that control over to front end users, but in some cases you might so this is also planned for fix.
  • Subscribing users
//Only add to campaign monitor if the signup checkbox is checked
if (listId.Length > 2 && record.GetRecordField(new Guid("16922c9c-ffb6-4986-9c1e-324a9fa87e6a")).Values[0].ToString().ToLower() == "true")
{
Subscriber s = new Subscriber(authDetails, 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);
}

This is the section of code which has changed from the blog post I followed. In order to check if the checkbox was checked when the form was submitted, we have to look at the value of it but this is done by GUID. I had to debug the app and take a note of the GUID (you could also get this from the database) and hard code it. This is not ideal because if the checkbox was deleted and recreated in the form designer, the GUID would change and the code inside the if statement would never be reached. Either that or it would fall over when it couldn’t find the GUID. It is my top priority to find a better solution to this.
  • In both CampaignMonitorListsFieldSettingsType and CampaignMonitorExtension classes we are going to the CampaignMonitor API to do the same thing – get lists of lists. I am not convinced this is necessary, it certainly doesn’t make sense to hit the API twice but I would like to find out if this is actually required for the integration to work.

 So, that's it. I hope this is of use and please feel free to get in touch if there are any questions or suggestions, or even any mistakes - Bear in mind I intend to fix all of the above and then hopefully make this into a package. Soon!

Friday 22 February 2013

GLUUG 21st February 2013


GLUUG 21/02/2013


Umbraco MVC – Chris Koiak:


Chris Koiak delivered a fantastic talk on Umbraco MVC using his standard website MVC package, which can be downloaded from http://our.umbraco.org/projects/starter-kits/standard-website-mvc

He quickly built some additional functionality on top of the package to talk us through Models, Views and Controllers in Umbraco.
It was very nice to see that MVC is now a real thing in Umbraco as there were some doubts and concerns around the time of V5
Chris’ slides will be made available ASAP.

uSiteBuilder / Ways of working


I’m a big fan of uSiteBuilder as everybody knows but I recently moved from an agency where it was very back-end development driven to an agency where there are more “front end developers” than us “back-end developers” and in this environment it has shown that uSiteBuilder isn't the best tool for us.
When I started, my first job was to rebuild the company website in Umbraco and I naturally, not knowing how we work, defaulted to my uSiteBuilder/User Controls way of working but towards the end of the project it became apparent that the front end guys were changing properties in document types or template names and when uSiteBuilder synchronised, it was overwriting their changes.(Even with Stephen Rogers’ uSiteBuilder Admin package, this would still be a hassle) In the case of changing template names this gave the yellow screen of death because the document type was no longer able to locate the template associated with it as it was renamed.

Eventually I exported all the document types and rewrote the site without uSiteBuilder and replaced all user controls with razor scripts.

Last night it was interesting to see how other agencies approach this:

We all seem to agree that having a standard approach to all Umbraco projects in the team is the correct thing to do, but we had varying opinions on what is probably an age old question about “front end” and “back end”.

Rob (@restlesslake) feels that development in general has become a lot more complex than it was 5 or 10 years ago, and with things changing so much and so quickly, it’s more important than ever for agencies to have defined ways of working in place.

Jon(@billywizz) agrees with this, but his agency have a much larger back-end development team and less front-end developers therefore a tool like uSiteBuilder is excellent for them as they can work in Visual Studio, have source control and this is tightly controlled by the back-end team.

James(@james_m_south) feels very strongly about the importance of distinction between front and back end. He feels that there should be no grey area and instead clearly defined roles and areas of responsibility

David(@dconlisk) agrees with Rob and said particularly with the ongoing changes to Umbraco core, it’s next to impossible to maintain a level of expertise in all technologies front and back-end. David feels that in order to deliver the highest quality to the customer/client, he prefers to outsource front-end work so that he can concentrate on back-end

Caroline(@cazkirhope) Feels that her time would better utilised if the capable front end team were to take ownership of document types and razor scripts, leaving her to concentrate on other back end work, or even custom Umbraco work. She feels that a lot of her time would be unnecessarily spent adding properties to a document type when a front end developer could manage this change themselves.

I’m sure the uSiteBuilder / Ways of working debate will continue for a while but feel free to add your own comments and experience