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
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.