Create custom Sitefinity basic settings section

When you integrate 3-rd party API in Sitefinity, you will often need to store sensitive data in your project. Such sensitive data can be private API keys, additional configurations, etc. The best way to store it is in the Basic settings section in the Sitefinity back end.

Pros:

  • No need to recompile the project to change the data
  • Only authenticated users with back end access can access the data
  • Single place of editing the data
  • Content editors without development knowledge can modify the data

For example if you need to integrate Google maps on your Sitefinity project, you will need to get a Maps Console key which allows you to monitor your application's Maps API usage. See official Google Maps documentation for how to retrieve the Console API key.

Follow the steps below to store the Google API key into Sitefinity basic settings:

1. Create configuration class

Usually all configuration properties in Sitefinity are stored in configuration files. Create new class with name GoogleMapsConfig and paste the following code inside:

[ObjectInfo(Title = "Google maps config title", Description = "Google maps config description")]
public class GoogleMapsConfig: ConfigSection
{
    [ConfigurationProperty("googleMapsAPIKey", DefaultValue = "your-API-key", IsRequired = true)]
    [ObjectInfo(Title = "Private key for the Google Maps API.", Description = "Private key for the Google Maps API.")]
    public string GoogleMapsAPIKey
    {
        get
        {
            return (string)this["googleMapsAPIKey"];
        }

        set
        {
            this["googleMapsAPIKey"] = value;
        }
    }
}

Notice that the config class inherits from the ConfigSection base class. It has a single property of type string that will be used to get/set the Google Maps API Key.

2.Create basic settings view template (.ascx file)

This template will define the view that the content editors will see after navigating to the basic settings section. Create new .ascx file with name GoogleMapsBasicSettingsView in your SitefinityWebApp and mark it as Embedded resource. Put the following code inside:

 <%@ Control Language="C#" %>
 <%@ Register Assembly="Telerik.Sitefinity" Namespace="Telerik.Sitefinity.Web.UI.Fields" TagPrefix="sfFields" %>
 <%@ Register Assembly="Telerik.Sitefinity" Namespace="Telerik.Sitefinity.Web.UI" TagPrefix="sf" %>
 <%@ Register Assembly="Telerik.Sitefinity" Namespace="Telerik.Sitefinity.Web.UI.FieldControls" TagPrefix="sfFields" %>
 <%@ Register Assembly="Telerik.Sitefinity" Namespace="Telerik.Sitefinity.Web.UI.Fields" TagPrefix="sfFields" %> 
<sf:ClientLabelManager id="clientLabelManager" runat="server">
 <labels>
     <sf:ClientLabel ClassId="Labels" Key="SectionSaved" runat="server" />
 </labels>
</sf:ClientLabelManager>
<sfFields:FormManager ID="formManager" runat="server" />

<div class="sfTwitterSettingsWrp">
 <sf:Message
     runat="server"
     ID="message"
     ElementTag="div"
     CssClass="sfMessage sfDialogMessage"
     RemoveAfter="5000"
     FadeDuration="10" />

 <div id="loadingView" runat="server" style="display: none;" class="sfLoadingFormBtns sfButtonArea">
     <sf:SfImage ID="loadingImage1" runat="server" AlternateText="<%$Resources:Labels, SavingImgAlt %>" />
 </div>

 <div class="sfSettingsSection">
     <h2 id="sfAddTwitterAppTitle">
         <asp:Literal ID="Literal1" runat="server" Text="<%$Resources: CustomResources, GoogleMaps %>" /></h2>
 </div>
<sfFields:FieldControlsBinder ID="fieldsBinder" runat="server" TargetId="fieldsBinder"
     DataKeyNames="Id" ServiceUrl="~/Sitefinity/Services/Configuration/ConfigSectionItems.svc/localization">
 </sfFields:FieldControlsBinder>
 <div class="sfSettingsSection">
     <sfFields:TextField ID="googleMapsAPIKeyField" runat="server" DataFieldName="GoogleMapsAPIKey" Title="<%$ Resources:CustomResources, GoogleMapsAPIKey %>" 
         DisplayMode="Write" WrapperTag="div" CssClass="sfFieldWrp" ></sfFields:TextField>
    <sf:SitefinityLabel ID="SitefinityLabel1" runat="server" Text="<%$ Resources: CustomResources, GoogleMapsAPIKeyDescription %>" WrapperTagName="p" />
 </div>
 <div class="sfButtonArea" id="buttonsArea" runat="server">
     <asp:HyperLink ID="btnSave" runat="server" class="sfLinkBtn sfSave">
         <strong class="sfLinkBtnIn">
             <asp:Literal ID="Literal4" runat="server" Text='<%$ Resources:Labels, SaveChangesLabel %>' />
         </strong>
     </asp:HyperLink>
 </div>
  </div>

The code above defines series of custom labels defined in CustomResources class. For more information on creating resource files, see official Sitefinity documentation.

3.Create code behind for the basic settings view

You must create new class (.cs file) that will handle the server-side logic needed for the Basic settings view. The class must inherit Sitefinity's BasicSettingsView base class and similar to other SimpleView classes must define the path for the .ascx template via LayoutTemplatePath property and register custom scripts references via the GetScriptDescriptors class:

public class GoogleMapsBasicSettingsView : BasicSettingsView
{
    /// <inheritdoc />
    protected override string LayoutTemplateName
    {
        get
        {
            return null;
        }
    }

    /// <inheritdoc />
    public override string LayoutTemplatePath
    {
        get
        {
            return this.layoutTemplatePath;
        }
        set
        {
            this.layoutTemplatePath = value;
        }
    }

    protected virtual TextField GoogleMapsAPIKeyField
    {
        get
        {
            return this.Container.GetControl<TextField>("googleMapsAPIKeyField", true);
        }
    }

    /// <inheritdoc />
    protected override void InitializeControls(System.Web.UI.Control viewContainer)
    {
        base.InitializeControls(viewContainer);
    }

    /// <inheritdoc />
    public override IEnumerable<System.Web.UI.ScriptReference> GetScriptReferences()
    {
        var scripts = new List<ScriptReference>(base.GetScriptReferences());
        scripts.Add(new ScriptReference(this.scriptReference));
        return scripts;
    }

    /// <inheritdoc />
    public override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
    {
        var baseDescriptors = new List<ScriptDescriptor>(base.GetScriptDescriptors());
        var scriptDescriptor = new ScriptControlDescriptor(typeof(GoogleMapsBasicSettingsView).FullName, this.ClientID);

        scriptDescriptor.AddComponentProperty("googleMapsAPIKeyField", this.GoogleMapsAPIKeyField.ClientID);
        scriptDescriptor.AddComponentProperty("messageControl", this.Message.ClientID);
        scriptDescriptor.AddElementProperty("saveButton", this.SaveButton.ClientID);

        baseDescriptors.Add(scriptDescriptor);
        return baseDescriptors;
    }

    #region Private fields and constants

    private string layoutTemplatePath = "~/GoogleMapsBasicSettingsView.ascx";
    private readonly string scriptReference = "~/GoogleMapsBasicSettingsView.js";

    #endregion
}
}

4.Define client component for the basic settings view

The component represents an Embedded JavaScript file with the following code:

GoogleMapsBasicSettingsView = function (element)    {
GoogleMapsBasicSettingsView.initializeBase(this,  [element]);
this._ajaxResponseDelegate = null;
this._ajaxResponseErrorDelegate = null;
this._googleMapsAPIKeyField = null;
this._messageControl = null;
this._saveButton = null;
this._saveButtonClickDelegate = null;
this._saveSettingsResponseDelegate = null;
this._saveSettingsErrorDelegate = null;
}

GoogleMapsBasicSettingsView.prototype = {
initialize: function () {
 GoogleMapsBasicSettingsView .callBaseMethod(this, 'initialize');

    this._ajaxResponseDelegate = Function.createDelegate(this, this._handleServiceResponse);
    this._ajaxResponseErrorDelegate = Function.createDelegate(this, this._handleServiceError);
    this._saveButtonClickDelegate = Function.createDelegate(this, this._handleSaveButtonClick);
    this._saveSettingsResponseDelegate = Function.createDelegate(this, this._handleSaveSettingsResponse);
    this._saveSettingsErrorDelegate = Function.createDelegate(this, this._handleSaveSettingsError);

    jQuery(this.get_saveButton()).on('click', this._saveButtonClickDelegate);

    var serviceUrl = "http://" + location.host + "/CustomServices/GoogleMapsService/GetSettings";
    jQuery.ajax({
        url: serviceUrl,
        type: "GET",
        contentType: "application/json",
        dataType: "json",
        success: this._ajaxResponseDelegate,
        error: this._ajaxResponseErrorDelegate
    });
},
dispose: function () {
    GoogleMapsBasicSettingsView .callBaseMethod(this, 'dispose');

    if (this._ajaxResponseDelegate)
        delete this._ajaxResponseDelegate;
    if (this._ajaxResponseErrorDelegate)
        delete this._ajaxResponseErrorDelegate;
    if (this._saveButtonClickDelegate)
        delete this._saveButtonClickDelegate;
    if (this._saveSettingsResponseDelegate)
        delete this._saveSettingsResponseDelegate;
    if (this._saveSettingsErrorDelegate)
        delete this._saveSettingsErrorDelegate;
},

_handleSaveButtonClick: function (sender, args) {
    var settings = {
        "GoogleMapsAPIKey": this.get_googleMapsAPIKeyField().get_value()
    };

    var serviceUrl = "http://" + location.host + "/CustomServices/GoogleMapsService/SaveSettings";
    jQuery.ajax({
        url: serviceUrl,
        type: "POST",
        contentType: "application/json",
        dataType: "json",
        data: JSON.stringify(settings),
        success: this._saveSettingsResponseDelegate,
        error: this._saveSettingsErrorDelegate
    });
},

_handleServiceResponse: function (data, jqXHR, textStatus) {
    this.get_googleMapsAPIKeyField().set_value(data.GoogleMapsAPIKey);
},

_handleServiceError: function (jqXHR, textStatus, error) {
    this.get_messageControl().showNegativeMessage("A problem occurred while loading settings: " + error);
},

_handleSaveSettingsResponse: function (data, jqXHR, textStatus) {
    this.get_messageControl().showPositiveMessage("Settings saved successfully.");
},

_handleSaveSettingsError: function (jqXHR, textStatus, error) {
    this.get_messageControl().showNegativeMessage("A problem occurred while saving settings: " + error);
},

get_googleMapsAPIKeyField: function () {
    return this._googleMapsAPIKeyField;
},

set_googleMapsAPIKeyField: function (value) {
    if (value) {
        this._googleMapsAPIKeyField = value;
    }
},

get_messageControl: function () {
    return this._messageControl;
},

set_messageControl: function (value) {
    if (value) {
        this._messageControl = value;
    }
},

get_saveButton: function () {
    return this._saveButton;
},

set_saveButton: function (value) {
    this._saveButton = value;
}
}

The code above uses AJAX calls to a custom service to store the Google Maps API Key to the configuration file created in step 1.

5.Create custom config service contract

The contract represents an interface for the WCF service that will contain the URI templates and the signature of the methods needed to retrieve and save the settings in the Google Maps configuration file.

/// <summary>
/// Contract for the custom configurations WCF service.
/// </summary>
[ServiceContract]
public interface IGoogleMapsService
{
    [OperationContract]
    [WebInvoke(Method = "GET", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    ConfigViewModel GetSettings();

    [OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    void SaveSettings(ConfigViewModel viewModel);
}

6.Create custom view model class

The methods in the WCF contract return and use as a parameter a special custom class named ConfigViewModel. This class is used as a bridge between the config and the service.

public class ConfigViewModel
{
    public string GoogleMapsAPIKey { get; set; }
}

7.Create the WCF service class

You need to create a class that implements the interface in step 5 and add definitions to the methods for getting and saving the Google maps configurations.

[ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class GoogleMapsService : IGoogleMapsService
{
    /// <summary>
    /// Gets the settings from the GoogleMapsConfig.
    /// </summary>
    /// <returns></returns>
    public ConfigViewModel GetSettings()
    {
        ServiceUtility.RequestAuthentication();
        var config = Config.Get<GoogleMapsConfig>();

        return new ConfigViewModel()
        {
            GoogleMapsAPIKey = config.GoogleMapsAPIKey
        };
    }

    /// <summary>
    /// Saves the settings in the GoogleMapsConfig.
    /// </summary>
    /// <param name="viewModel">The view model.</param>
    public void SaveSettings(ConfigViewModel viewModel)
    {
        ServiceUtility.RequestAuthentication();
        var configManager = ConfigManager.GetManager();
        var section = configManager.GetSection<GoogleMapsConfig>();

        section.GoogleMapsAPIKey = viewModel.GoogleMapsAPIKey;

        configManager.SaveSection(section);
    }
}

}

TIP: Always secure your custom WCF services from unauthenticated users. Otherwise, you will introduce a security hole for your application. To secure the services, you need to call the RequestAuthentication method, part of the ServiceUtility Sitefinity class in all your methods.

8.Register the configuration class, basic settings view and custom WCF service:

All custom assets in Sitefinity can be registered in the SystemManager.Application_Start event handler.

a) Register custom WCF service

In Sitefinity, you can register WCF services dynamically without the need to create a real .svc files for them. This can be achieved via the RegisterWebService method, part of the SystemManager class:

//Custom WCF service installation          SystemManager.RegisterWebService(typeof(GoogleMapsService), "CustomServices/GoogleMapsService");

b) Register custom basic settings view

SystemManager has another valuable static method that allows registration of custom basic settings views: SystemManager.RegisterBasicSettings("GoogleMaps", "GoogleMaps", "CustomResources", false);

Note that the RegisterBasicSettings method requires 4 parameters: settingsName, settingsTitle, settingsResourceClass, allowSettingsPerSite. In this sample GoogleMaps param is a property of the CustomResources custom resource class. As the sample is not run in Multisite environment, the last parameter is set to false.

c) Register custom configuration class

Config.RegisterSection<GoogleMapsConfig>();

9.Test the basic settings section

As a last step, you need to recompile and recycle the application so that the custom resources are registered. In order to test the new basic settings view, authenticate in Sitefinity's backend and navigate to Administration -> Settings. Your custom section must appear in the bottom on the left sidebar.

Veronica Milcheva

About Veronica Milcheva

I am a passionate Sitefinity blogger, developer and consultant. In my spare time I enjoy running and listening to music. My personal quote: There's no tough problem, just not enough coffee :)

View Comments

comments powered by Disqus