HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunitySubmit a ticketLog In
GitHubNuGetDev CommunitySubmit a ticket

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 easily create a marketing connector to get the benefits of 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, which must be the same for all instances of the connector but not reused across connector classes.
  • Guid InstanceId –The identifier for a connector instance that allows for multiple instances of a connector to 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 which contain 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 all 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 Id of the entry created.
  • string CreateEntity(SubmissionTarget submissionTarget, Dictionary<string, string> entityFields)– Submits the entityFields to the specified ConnectorDataSources SubmissionTarget and returning the Id of the entry created. The Marketing Connector Framework calls this when data is submitted to a form that is 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, that is, 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. By using this method, the Marketing Connector Framework will mark the Connector as Active and will query the Connector for data at the various CMS integration points (Forms, Visitor Group creation, etc).

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 credential fields being saved 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 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 shown 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

Sample Connector
    An implementation of the new Marketing Automation Integration framework's IMarketingConnector 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;
        }
    }
}
using System;
    using System.Collections.Generic;
    using System.Linq;
    using Optimizely.Marketing.Common.Exceptions;
    using Optimizely.Marketing.Connector.Framework;
    using Optimizely.Marketing.Connector.Framework.Data;
    using EPiServer.ServiceLocation;
    
    namespace Optimizely.Marketing.Connector.Sample
      {
        ///
        /// Sample connector that can be used to demonstrate how to write a connector 
        /// and verify MAI framework functionality.
        ///
        public class SampleConnector : IMarketingConnector
          {
            internal const string SampleConnectorName = "Sample";
    
            #region StaticData
            static internal List DataSources = new List()
              {
                new ConnectorDataSource()
                  {
                    Id = 1001, Name = "DataSource1",
                    SubmissionTargets = new List()
                      {
                        new SubmissionTarget()   { Id = 2001, Name = "List1"  },
                        new SubmissionTarget()   { Id = 2002 ,Name = "List2"  }
                      }
                  },
                new ConnectorDataSource()
                  {
                    Id = 1002 ,Name = "DataSource2",
                    SubmissionTargets = new List()
                      {
                        new SubmissionTarget()   { Id = 2001, Name = "List1"  },
                        new SubmissionTarget()   { Id = 2002 ,Name = "List2"  }
                      }
                  },
                new ConnectorDataSource()
                  {
                    Id = 1003, Name = "DataSource3",
                    SubmissionTargets = new List()
                      {
                        new SubmissionTarget()   { Id = 2001, Name = "List1"  },
                        new SubmissionTarget()   { Id = 2002 ,Name = "List2"  }
                      }
                  },
                new ConnectorDataSource()
                  {
                    Id = 1004, Name = "VeryLargeFieldList",
                    SubmissionTargets = new List()
                      {
                        new SubmissionTarget()   { Id = 2001, Name = "List1"  },
                        new SubmissionTarget()   { Id = 2002 ,Name = "List2"  }
                      }
                  },
                new SubmitOnlyDataSource()
                  {
                    Id = 1005, Name = "Submission Only 1",
                    SubmissionTargets = new List()
                      {
                        new SubmissionTarget()   { Id = 2001, Name = "List1"  },
                        new SubmissionTarget()   { Id = 2002 ,Name = "List2"  }
                      }
                  },
                new SubmitOnlyDataSource()
                  {
                    Id = 1006, Name = "Submission Only 2",
                    SubmissionTargets = new List()
                      {
                        new SubmissionTarget()   { Id = 2001, Name = "List1"  },
                        new SubmissionTarget()   { Id = 2002 ,Name = "List2"  }
                      }
                  }
              };
    
            static internal Dictionary<long, List> Fields = new Dictionary<long, List>()
              {
                  { 1001, new List()
                      {
                        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()
                      {
                        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()
                      {
                        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()
                  },
                  { 1005, new List()
                      {
                        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()
                      {
                        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 submissionTargets = new List()
              {
                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;
    
            ///
            /// Unique identifier that represents this Connector
            ///
            public Guid Id   { get   { return ConnectorGuid;    }    }
    
            ///
            /// Identifier for the connector instance. Used when there are multiple configurations
            /// for the same connector.
            ///
            public Guid InstanceId   { get; set;   }
    
            private string _Name = "SampleConnector";
            private ConnectorCredentials _connectorCredentials;
            private IServiceLocator _locator;
    
            ///
            /// Identifies the provider.
            /// Provider name. 
            ///
            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();
                        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;
              }
    
            ///
            /// Get a list of datasources available from this connector.
            /// For e.g. Leads, Prospects, Contacts.
            ///
            /// 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.
            ///
            public IEnumerable GetDataSources()
              {
                IsConfigured();
                return DataSources;
              }
    
            ///
            /// Gets field names of a specified datasource.
            /// For e.g. Firstname, Lastname, Email.
            ///
            /// The datasource identifier.
            /// 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.
            ///
            public IEnumerable GetDataSourceFields(long id)
              {
                IsConfigured();
                return Fields[id];
              }
    
            ///
            /// Returns the data the connector has for the specified entity
            ///
            /// The datasource to retrieve the entity from.
            /// identifier for the entity in the connector
            /// The entity and all of its associated Fields and values
            ///
            public EntityProfile GetEntity(ConnectorDataSource dataSource, string entityId)
              {
                IsConfigured();
                return EntityProfileStorage[entityId];
              }
    
            ///
            /// Saves an entity (Contact, Lead, Prospect etc) in the connector datasource.
            ///
            /// The target datasource for the entity.
            /// Dictionary of the entity's attributes.
            /// Unique identifier of the newly created/updated entity.
            ///
            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;
              }
    
            ///
            /// Saves an entity (Contact, Lead, Prospect etc) in the connector datasources 
            /// SubmissionTarget.
            /// SubmissionTargets are typically Contact Lists within a particular datasource.
            ///
            /// The SubmissionTarget for the entity.
            /// Dictionary of the entity's attributes.
            /// Unique identifier of the newly created/updated entity.
            ///
            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;
              }
    
            ///
            /// Updates an entity (Contact, Lead, Prospect etc) in the connector datasource.
            ///
            /// The unique identifier of the entity to update
            /// The target datasource for the entity.
            /// Dictionary of the entity's attributes.
            /// Unique identifier of the newly created/updated entity.
            ///
            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;
              }
    
            ///
            /// Updates an entity (Contact, Lead, Prospect etc) in the connector datasource.
            ///
            /// The unique identifier of the entity to update
            /// The SubmissionTarget for the entity.
            /// Dictionary of the entity's attributes.
            /// Unique identifier of the newly created/updated entity.
            ///
            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;
              }
          }
      }


## ConnectorSampleSettings.aspx.cs


    using Optimizely.Marketing.Connector.Framework;
    using Optimizely.Marketing.Connector.Framework.Data;
    using EPiServer.Core;
    using EPiServer.PlugIn;
    using EPiServer.Shell.WebForms;
    using System;
    using System.Collections.Generic;
    
    namespace Optimizely.Marketing.Connector.Sample
      {
        /// 
        /// Codebehind class for the settings page.
        /// 
        [GuiPlugIn(Area = PlugInArea.AdminConfigMenu, 
            UrlFromModuleFolder = "View/ConnectorSampleSettings.aspx",
            DisplayName = "ConnectorSampleSettings")]
        public partial class ConnectorSampleSettings : WebFormsBase
          {
            ConnectorCredentials currentCrendentials;
    
            /// 
            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);
              }
    
            /// 
            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;
                      }
                   }
              }
    
            /// 
            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 to update your local credential object
              }
          }
      }


## ConnectorSampleSettings.aspx


    <%@ Page Language="c#" 
        AutoEventWireup="False" 
        CodeBehind="ConnectorSampleSettings.aspx.cs"
        Inherits="Optimizely.Marketing.Connector.Sample.ConnectorSampleSettings"
        Title="EPiServer Marketing Connector Sample" %>
    
    <%@ Import Namespace="EPiServer" %>
    <%@ Import Namespace="Optimizely.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>