HomeDev GuideRecipesAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Custom connector

Shows how to build a sample connector that implements the necessary components for integration with the marketing connector framework and explain the intended use of those methods.

With Marketing Automation Integration 5.0.0, you can create a marketing connector to benefit from form field mapping, form autofill integration, and visitor group integration with Optimizely Content Management System (CMS).

The IMarketingConnector interface is the main integration point to the marketing connector framework for this connector. The framework's MarketingConnectorManager reflects over the assemblies in the site to find the IMarketingConnector class implementations to use in the various CMS integrations. The IMarketingConnector interface is defined as follows:

  • Guid Id – The unique identifier for the connector class must be the same for instances of the connector but not reused across connector classes.
  • Guid InstanceId – The identifier for a connector instance that lets multiple connector instances be created with different credentials. This Id is set with the Guid from the ConnectorCredentials saved by the MarketingConnectorManager.
  • string Name – The connector instance name used to identify the connector in the CMS features the Marketing Connector. This name is set with the string from the ConnectorCredentials saved by the MarketingConnectorManager.
  • IEnumerable<ConnectorDataSource> GetDataSources() – Returns the ConnectorDataSources for the connector, which are used to determine where a CMS feature should submit or read data from for a connector. Typically, a ConnectorDataSource corresponds to a Database set up in a CRM product that contains different types of customer data (Accounts, Contacts, Cases, etc). ConnectorDataSources can also contain SubmissionTargets, which typically correspond to Contact Lists inside a ConnectorDataSource, which can be used to organize the submission to the DataSource.
  • IEnumerable<Field> GetDataSourceFields(long id) – Returns the fields for the DataSource Id. The fields can then be mapped to forms for submitting data from the form into the ConnectorDataSource.
  • string CreateEntity(ConnectorDataSource dataSource, Dictionary<string, string> entityFields) – Submits the entityFields to the specified ConnectorDataSource, returning the entry's Id created.
  • string CreateEntity(SubmissionTarget submissionTarget, Dictionary<string, string> entityFields)– Submits the entityFields to the specified ConnectorDataSources SubmissionTarget and returns the entry's Id created. The Marketing Connector Framework calls this when data is submitted to a form mapped to one of the Marketing Connector instances.
  • string UpdateEntity(string entityId, ConnectorDataSource dataSource, Dictionary<string, string> entityFields) – Updates the specified entity in the given ConnectorDataSources with the field values specified in the entityFields dictionary. The Marketing Connector Framework calls this when a submission to a mapped form occurs, and the MAI cookie has an entity ID value set for the ConnectorDataSource.
  • string UpdateEntity(string entityId, SubmissionTarget submissionTarget, Dictionary<string, string> entityFields) – Updates the specified entity in the given ConnectorDataSources's SubmissionTarget with the field values specified in the entityFields dictionary. The Marketing Connector Framework calls this when submitting form data from a known entity where a Submission Target is configured instead of the parent ConnectorDataSources; a Contact List is selected to submit to instead of a Connector Database.
  • EntityProfile GetEntity(ConnectorDataSource dataSource, string entityId)– Retrieves the data for a particular EntityId in the ConnectorDataSources. The Marketing Connector Framework uses this method for CMS integrations where a particular user's data needs to be looked at, such as VisitorGroup matching and Form Autofill scenarios.

Activate a connector

The Marketing Connector Framework relies on the connector to call it to save credentials. Saved ConnectorCredentials indicate when a Connector is active and can retrieve data from its ConnectorDataSources. To do this the MarketingConnectorManager class has a SaveCredentials method that takes a ConnectorCredentials object and stores it in the system. The Marketing Connector Framework will mark the Connector as Active using this method. It will query the Connector for data at the various CMS integration points (forms, audience creation, and so on).

The Marketing Connector Framework will not, however, test the credentials being saved. It is up to the mechanism for entering credentials to validate that the saved credential fields can be used to connect to the external system.

After credentials are saved, the Marketing Connector Framework keeps track of configured Connector credentials to determine how many actively configured Connectors are available. However, the Marketing Connector Framework does not log in to the configured account to the external system. It is up to the connector code to retrieve its configured credentials from the MarketingConnectorManager classes GetCredentials method and use them to log into the external system for retrieving or submitting data.

Download sample code

Download SampleDoc.zip, which contains a NuGet package and the files below. Install Episerver.Marketing.Connector.Sample.1.0.0.nupkg to your site through Visual Studio.

The code files in the NuGet package are shown in the following sections.

SampleConnectorIntro

The following is an implementation of the Marketing Automation Integration framework'sIMarketingConnector interface. The package demonstrates how custom connectors could be built to be used by the Marketing Automation framework.

ConnectorSampleSettings.aspx

<%@ Page Language="c#" AutoEventWireup="False" CodeBehind="ConnectorSampleSettings.aspx.cs" Inherits="Episerver.Marketing.Connector.Sample.ConnectorSampleSettings"
    Title="EPiServer Marketing Connector Sample" %>

<%@ Import Namespace="EPiServer" %>
<%@ Import Namespace="Episerver.Marketing.Connector.Sample" %>
<%@ Register Assembly="EPiServer.UI" Namespace="EPiServer.UI.WebControls" TagPrefix="EPiServerUI" %>

<asp:content contentplaceholderid="HeaderContentRegion" runat="server">
</asp:content>

<asp:content contentplaceholderid="FullRegion" runat="server">
 	<link href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/themes/smoothness/jquery-ui.min.css" rel="stylesheet">
	<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
	<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/jquery-ui.min.js"></script>

    <div class="epi-contentContainer epi-padding">
        <div class="epi-contentArea">
            <h1 class="EP-prefix">Sample Connector - Configuration</h1>
        </div>

        <asp:Panel runat="server" class="epi-formArea">
            <fieldset>
                <div class="epi-size15">
                    <asp:Label AssociatedControlID="ServerUrl" runat="server">Server URL</asp:Label>
                    <asp:TextBox ID="ServerUrl" runat="server" MaxLength="255" Text="" />
                </div>
                <div class="epi-size15">
                    <asp:Label AssociatedControlID="Username" runat="server">User Name</asp:Label>
                    <asp:TextBox ID="Username" runat="server" MaxLength="255" Text="" />
                </div>

                <div class="epi-size15">
                    <asp:Label AssociatedControlID="Password" runat="server">Password</asp:Label>
                    <asp:TextBox ID="Password" TextMode="Password" runat="server" MaxLength="255" />
                </div>

                <div class="epi-size15">
                    <label style="position:absolute; vertical-align:bottom;">Type</label>
                    <br />
                    <label></label>
                    <asp:RadioButton ID="RBType1" GroupName="RBGroup" runat="server" checked="true"/>
                    <label style="position:relative; vertical-align:middle;">RBType1</label>
                    <br />
                    <label></label>
                    <asp:RadioButton ID="RBType2" GroupName="RBGroup" runat="server" checked="false"/>
                    <label style="position:relative; vertical-align:middle;">RBType2</label>
                    <br />
                </div>
            </fieldset>
            <fieldset>
                <EPiServerUI:ToolButton runat="server" SkinID="Save" ID="Save" Enabled="true" OnClick="Save_Click"
                    Text="Save" validationgroup="inputValidationGroup" 
                    style="vertical-align:top; float: right"/>
            </fieldset>
        </asp:Panel>
    </div>
</asp:content>

ConnectorSampleSettings.aspx.cs

using Episerver.Marketing.Connector.Framework;
using Episerver.Marketing.Connector.Framework.Data;
using EPiServer.Core;
using EPiServer.PlugIn;
using EPiServer.Shell.WebForms;
using System;
using System.Collections.Generic;

namespace Episerver.Marketing.Connector.Sample {
  /// <summary>
  /// Codebehind class for the settings page.
  /// </summary>
  [GuiPlugIn(Area = PlugInArea.AdminConfigMenu, UrlFromModuleFolder = "View/ConnectorSampleSettings.aspx",
    DisplayName = "ConnectorSampleSettings")]
  public partial class ConnectorSampleSettings: WebFormsBase {
    ConnectorCredentials currentCrendentials;

    /// <inheritdoc />
    protected override void OnInit(EventArgs e) {
      // Security validation: user should have Edit access to view this page
      if (!EPiServer.Security.PrincipalInfo.HasAdminAccess) {
        throw new AccessDeniedException();
      }

      DataBind();

      base.OnInit(e);
    }

    /// <inheritdoc />
    protected override void OnLoad(EventArgs e) {
      base.OnLoad(e);

      if (!Page.IsPostBack) {
        SampleConnector connector = new SampleConnector();

        var manager = new MarketingConnectorManager();
        currentCrendentials = manager.GetConnectorCredentials(connector.Id.ToString(), connector.Id.ToString());
        if (currentCrendentials != null) {
          ServerUrl.Text = currentCrendentials.CredentialFields["ServerUrl"] as string;
          Username.Text = currentCrendentials.CredentialFields["Username"] as string;
          Password.Text = currentCrendentials.CredentialFields["Password"] as string;
          RBType1.Checked = Boolean.TrueString == currentCrendentials.CredentialFields["RBType1"] as string;
          RBType2.Checked = Boolean.TrueString == currentCrendentials.CredentialFields["RBType2"] as string;
        }
      }
    }

    /// <inheritdoc />
    public void Save_Click(object sender, EventArgs e) {
      var manager = new MarketingConnectorManager();
      if (currentCrendentials == null) {
        currentCrendentials = new ConnectorCredentials() {
          ConnectorName = SampleConnector.SampleConnectorName,
            ConnectorId = SampleConnector.ConnectorGuid,
            ConnectorInstanceId = SampleConnector.ConnectorGuid,
            CredentialFields = new Dictionary<string, object>()
        };
      }

      currentCrendentials.CredentialFields.Clear();
      currentCrendentials.CredentialFields.Add("ServerUrl", ServerUrl.Text);
      currentCrendentials.CredentialFields.Add("Username", Username.Text);
      currentCrendentials.CredentialFields.Add("Password", Password.Text);
      currentCrendentials.CredentialFields.Add("RBType1", RBType1.Checked.ToString());
      currentCrendentials.CredentialFields.Add("RBType2", RBType2.Checked.ToString());

      manager.SaveConnectorCredentials(currentCrendentials);
      currentCrendentials = manager.GetConnectorCredentials(SampleConnector.ConnectorGuid.ToString(), SampleConnector.ConnectorGuid.ToString()); // make sure we update our local credential object
    }
  }
}

Sample.Connector.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Episerver.Marketing.Common.Exceptions;
using Episerver.Marketing.Connector.Framework;
using Episerver.Marketing.Connector.Framework.Data;
using EPiServer.ServiceLocation;

namespace Episerver.Marketing.Connector.Sample
{
    /// <summary>
    /// Sample connector that can be used to demonstrate how to write a connector and verify MAI framework functionality.
    /// </summary>
    public class SampleConnector : IMarketingConnector
    {
        internal const string SampleConnectorName = "Sample";

        #region StaticData
        static internal List<ConnectorDataSource> DataSources = new List<ConnectorDataSource>()
        {
            new ConnectorDataSource()
            {
                Id = 1001, Name = "DataSource1",
                SubmissionTargets = new List<SubmissionTarget>()
                {
                    new SubmissionTarget() { Id = 2001, Name = "List1"},
                    new SubmissionTarget() { Id = 2002 ,Name = "List2"}
                }
            },
            new ConnectorDataSource()
            {
                Id = 1002 ,Name = "DataSource2",
                SubmissionTargets = new List<SubmissionTarget>()
                {
                    new SubmissionTarget() { Id = 2001, Name = "List1"},
                    new SubmissionTarget() { Id = 2002 ,Name = "List2"}
                }
            },
            new ConnectorDataSource()
            {
                Id = 1003, Name = "DataSource3",
                SubmissionTargets = new List<SubmissionTarget>()
                {
                    new SubmissionTarget() { Id = 2001, Name = "List1"},
                    new SubmissionTarget() { Id = 2002 ,Name = "List2"}
                }
            },
            new ConnectorDataSource()
            {
                Id = 1004, Name = "VeryLargeFieldList",
                SubmissionTargets = new List<SubmissionTarget>()
                {
                    new SubmissionTarget() { Id = 2001, Name = "List1"},
                    new SubmissionTarget() { Id = 2002 ,Name = "List2"}
                }
            },
            new SubmitOnlyDataSource()
            {
                Id = 1005, Name = "Submission Only 1",
                SubmissionTargets = new List<SubmissionTarget>()
                {
                    new SubmissionTarget() { Id = 2001, Name = "List1"},
                    new SubmissionTarget() { Id = 2002 ,Name = "List2"}
                }
            },
            new SubmitOnlyDataSource()
            {
                Id = 1006, Name = "Submission Only 2",
                SubmissionTargets = new List<SubmissionTarget>()
                {
                    new SubmissionTarget() { Id = 2001, Name = "List1"},
                    new SubmissionTarget() { Id = 2002 ,Name = "List2"}
                }
            }
        };

        static internal Dictionary<long, List<Field>> Fields = new Dictionary<long, List<Field>>()
        {
            { 1001, new List<Field>()
                {
                    new Field() {  Id = 1, Name = "DS1_Field_1"},
                    new Field() {  Id = 2, Name = "DS1_Field_2"},
                    new Field() {  Id = 3, Name = "DS1_Field_3"}
                }
            },
            { 1002, new List<Field>()
                {
                    new Field() {  Id = 32, Name = "DS2_Field_1"},
                    new Field() {  Id = 33, Name = "DS2_Field_2"},
                    new Field() {  Id = 34, Name = "DS2_Field_3"}
                }
            },
            { 1003, new List<Field>()
                {
                    new Field() {  Id = 32, Name = "DS3_Field_1"},
                    new Field() {  Id = 33, Name = "DS3_Field_2"},
                    new Field() {  Id = 34, Name = "DS3_Field_3"}
                }
            },
            { 1004, new List<Field>()
            },
            { 1005, new List<Field>()
                {
                    new Field() {  Id = 32, Name = "DS3_Field_1"},
                    new Field() {  Id = 33, Name = "DS3_Field_2"},
                    new Field() {  Id = 34, Name = "DS3_Field_3"}
                }
            },
            { 1006, new List<Field>()
                {
                    new Field() {  Id = 32, Name = "DS3_Field_1"},
                    new Field() {  Id = 33, Name = "DS3_Field_2"},
                    new Field() {  Id = 34, Name = "DS3_Field_3"}
                }
            },
        };

        static internal List<SubmissionTarget> submissionTargets = new List<SubmissionTarget>()
        {
            new SubmissionTarget()
            {
                Id = 2001, Name = "List1"
            },
            new SubmissionTarget()
            {
                Id = 2002 ,Name = "List2"
            }
        };

        static internal Dictionary<string, EntityProfile> EntityProfileStorage = new Dictionary<string, EntityProfile>();

        static SampleConnector()
        {
            // init a large number of fields
            for (int x = 0; x < 1000; x++)
            {
                Fields[1004].Add(new Field() { Id = x, Name = "VeryLargeFieldList_" + x });
            }
        }

        internal static Guid ConnectorGuid = new Guid("0D25D765-5BA8-49B8-AF6D-1AF2ECE30032");
        #endregion

        IMarketingConnectorManager _manager;

        /// <summary>
        /// Unique identifier that represents this Connector
        /// </summary>
        public Guid Id { get { return ConnectorGuid;  }  }

        /// <summary>
        /// Identifier for the connector instance. Used when there are multiple configurations for the same connector.
        /// </summary>
        public Guid InstanceId { get; set; }

        private string _Name = "SampleConnector";
        private ConnectorCredentials _connectorCredentials;
        private IServiceLocator _locator;

        /// <summary>
        /// Identifies the provider.
        /// <returns>Provider name.</returns> 
        /// </summary>
        public string Name { get => _Name; set => _Name = value; }

        public SampleConnector()
        {
            _locator = ServiceLocator.Current;
        }

        internal SampleConnector(IServiceLocator locator)
        {
            _locator = locator;
        }

        private bool IsConfigured()
        {
            if( InstanceId != Guid.Empty )
            {
                if (_manager == null && _connectorCredentials == null)
                {
                    _manager = _locator.GetInstance<IMarketingConnectorManager>();
                    var _connectorCredentials = _manager.GetConnectorCredentials(Id.ToString(), InstanceId.ToString());
                    if(_connectorCredentials == null )
                    {
                        throw new ConfigurationInvalidException($"credentials not found for {Name}");
                    }
                }
            }
            else
            {
                throw new ConfigurationInvalidException("InstanceId not set");
            }

            return true;
        }

        /// <summary>
        /// Get a list of datasources available from this connector.
        /// For e.g. Leads, Prospects, Contacts.
        /// </summary>
        /// <returns>Returns a list of datasources available from this connector. 
        /// This list is displayed in the dropdown under the mapping tab in the forms UI.
        /// A datasource is the target where an entity is created upon the submission of the form.</returns>
        public IEnumerable<ConnectorDataSource> GetDataSources()
        {
            IsConfigured();
            return DataSources;
        }

        /// <summary>
        /// Gets field names of a specified datasource.
        /// For e.g. Firstname, Lastname, Email.
        /// </summary>
        /// <param name="id">The datasource identifier.</param>
        /// <returns>Returns a list of fields available in this data source.
        /// This list is displayed in the drop down under the field mapping tab in the forms UI, 
        /// which allows for the mapping of a form field to a field in the datasource.
        /// </returns>
        public IEnumerable<Field> GetDataSourceFields(long id)
        {
            IsConfigured();
            return Fields[id];
        }

        /// <summary>
        /// Returns the data the connector has for the specified entity
        /// </summary>
        /// <param name="dataSource">The datasource to retrieve the entity from.</param>
        /// <param name="entityId">identifier for the entity in the connector</param>
        /// <returns>The entity and all of its associated Fields and values</returns>
        public EntityProfile GetEntity(ConnectorDataSource dataSource, string entityId)
        {
            IsConfigured();
            return EntityProfileStorage[entityId];
        }

        /// <summary>
        /// Saves an entity (Contact, Lead, Prospect etc) in the connector datasource.
        /// </summary>
        /// <param name="dataSource">The target datasource for the entity.</param>
        /// <param name="entityFields">Dictionary of the entity's attributes.</param>
        /// <returns>Unique identifier of the newly created/updated entity.</returns>
        public string CreateEntity(ConnectorDataSource dataSource, Dictionary<string, string> entityFields)
        {
            IsConfigured();
            var id = EntityProfileStorage.Count().ToString();

            EntityProfileStorage.Add(id, new EntityProfile() { Id = id, Fields = entityFields });

            return id;
        }

        /// <summary>
        /// Saves an entity (Contact, Lead, Prospect etc) in the connector datasources SubmissionTarget.
        /// SubmissionTargets are typically Contact Lists within a particular datasource
        /// </summary>
        /// <param name="submissionTarget">The SubmissionTarget for the entity.</param>
        /// <param name="entityFields">Dictionary of the entity's attributes.</param>
        /// <returns>Unique identifier of the newly created/updated entity.</returns>
        public string CreateEntity(SubmissionTarget submissionTarget, Dictionary<string, string> entityFields)
        {
            IsConfigured();
            var id = EntityProfileStorage.Count().ToString();

            EntityProfileStorage.Add(id, new EntityProfile() { Id = id, Fields = entityFields });

            return id;
        }

        /// <summary>
        /// Updates an entity (Contact, Lead, Prospect etc) in the connector datasource.
        /// </summary>
        /// <param name="entityId">The unique identifier of the entity to update</param>
        /// <param name="dataSource">The target datasource for the entity.</param>
        /// <param name="entityFields">Dictionary of the entity's attributes.</param>
        /// <returns>Unique identifier of the newly created/updated entity.</returns>
        public string UpdateEntity(string entityId, ConnectorDataSource dataSource, Dictionary<string, string> entityFields)
        {
            IsConfigured();
            EntityProfile entity;
            if (EntityProfileStorage.TryGetValue(entityId, out entity))
            {
                EntityProfileStorage.Remove(entityId);
                EntityProfileStorage.Add(entityId, new EntityProfile() { Id = entityId, Fields = entityFields });
            }
            return entityId;
        }

        /// <summary>
        /// Updates an entity (Contact, Lead, Prospect etc) in the connector datasource.
        /// </summary>
        /// <param name="entityId">The unique identifier of the entity to update</param>
        /// <param name="submissionTarget">The SubmissionTarget for the entity.</param>
        /// <param name="entityFields">Dictionary of the entity's attributes.</param>
        /// <returns>Unique identifier of the newly created/updated entity.</returns>
        public string UpdateEntity(string entityId, SubmissionTarget submissionTarget, Dictionary<string, string> entityFields)
        {
            IsConfigured();
            EntityProfile entity;
            if (EntityProfileStorage.TryGetValue(entityId, out entity))
            {
                EntityProfileStorage.Remove(entityId);
                EntityProfileStorage.Add(entityId, new EntityProfile() { Id = entityId, Fields = entityFields });
            }
            return entityId;
        }
    }
}