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

Create a data storage mechanism

Describes general guidelines about using Optimizely APIs to replace Dynamic Data Store (DDS) with another data storage system.

📘

Note

Optimizely Forms is only supported by MVC-based websites and HTML5-compliant browsers.

Optimizely Forms uses DDS as its default storage mechanism. This may cause performance-related issues in special cases. You can base an alternative data storage scheme on MSSQL or another data provider to try different solutions. This topic uses MongoDB as an alternative storage provider, but you can use any data storage system.

Comparison between DDS and MongoDB

1539

To implement a MongoDB data storage approach, first prepare your environment for Mongo. See https://docs.mongodb.com.

Required NuGet packages

  • MongoDB.Bson
  • MongoDB.Driver
  • MongoDB.Driver.Core
  • EPiServer.Forms.Core 4.6.1 (or higher)

PermanentStorage class

PermanentStorage is the base class for data storage. This class exposes the necessary APIs for developers to manipulate form data with their data storage.

  1. Create a class inheriting the PermanentStorage class. Put the ServiceConfiguration attribute on top, as shown in the following example.
[ServiceConfiguration(ServiceType = typeof(IPermanentStorage))]
public class YourDataStorage: PermanentStorage
  1. Within the newly-created class constructor, set up initial storage information, such as connection string, configuration, and so on.
  2. Override the five functions below.
    • SaveToStorage(FormIdentity formIden, Submission submission) – This function saves data at the first step of a form. This may also be the last step if a form only has one step. Using this method, developers can save data into a database based on these two parameters.
      • formIden – Contains form information, such as form id or language.
      • submission – Contains form submission data.
    • UpdateToStorage(Guid formSubmissionId, FormIdentity formIden, Submission submission) – This function saves data at the next steps or updates data at previous ones. Within this method, developers can update data in the database based on these two parameters.
      • submission – Contains form submission data.
      • formIden – Contains form information, such as form id or language.
    • LoadSubmissionFromStorage(FormIdentity formIden, DateTime beginDate, DateTime endDate, bool finalizedOnly = false) – This function loads form submission data from the database within a specific time period, and the status is finalized.
    • LoadSubmissionFromStorage(FormIdentity formIden, string[] submissionIds) – Loads form submission data only based on the ids of the submission.
    • Delete(FormIdentity formIden, string submissionId) – Deletes a form submission based on submission Id.

Example of using MongoDB as a data storage mechanism

Use the following example to replace DDS with MongoDB for data manipulation. The MongoDbPermanentStorage class implements the storage.

MongoDB uses the concept of "collection" as a table in Microsoft SQL. Each form has one collection, which stores form submission data. The collection also supports indexing, significantly improving data query performance. MongoDB's storage details are below.

  • MongoDbPermanentStorage() – Set the constructor of the storage. Within the constructor, you set up a connection string for the database connection, a database name for data storage, and form configurations.
  • GetFormCollection (FormIdentity formIden) – Creates a collection for a specific form based on form id. If a form was previously associated with a collection, it is returned, and no collection is created.
  • IsCollectionExisted(IMongoDatabase database, string collectionName) – Checks whether a collection exists in the database based on its name.
  • SaveToStorage(FormIdentity formIden, Submission submission) – Gets the collection associated with form submission data as a new item, then saves it to the MongoDB.
  • UpdateToStorage(Guid formSubmissionId, FormIdentity formIden, Submission submission) – Updates the collection associated with the forms and updates submission data previously stored.
  • LoadSubmissionFromStorage(FormIdentity formIden, DateTime beginDate, DateTime endDate, bool finalizedOnly = false) – Loads submission from the collection associated with the form during a time period.
  • LoadSubmissionFromStorage(FormIdentity formIden, string[] submissionIds) – Loads submission from the collection associated with the form based on the list of submission IDs.
  • Delete(FormIdentity formIden, string submissionId) – Deletes a submission previously stored in the collection.

Beta version of MongoDBPermanentStorage

Sample code

using EPiServer.Forms.Core.Data;
using EPiServer.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using EPiServer.Data.Dynamic;
using EPiServer.Forms.Core.Models;
using EPiServer.Forms.Core;
using EPiServer.Forms;
using EPiServer.ServiceLocation;
using EPiServer.Data;

namespace EPiServer.Forms.Demo.Data {
  /// <summary>
  /// To use MongDB storage you need to modify the Forms.config as below:
  /// 
  /// ...
  /// <storage defaultProvider="MongoDbPermanentStorage">
  ///  <providers>
  ///    <add name = "MongoDbPermanentStorage" type="EPiServer.Forms.Demo.Data.MongoDbPermanentStorage, EPiServer.Forms.Demo" connectionUri="mongodb://{username}:{password}@{host}:{port}" databaseName="{database}" />
  ///    <!--
  ///    Example:
  ///    <add name = "MongoDbPermanentStorage" type="EPiServer.Forms.Demo.Data.MongoDbPermanentStorage, EPiServer.Forms.Demo" connectionUri="mongodb://root:[email protected]:27017" databaseName="FormsDb" />
  ///    -->
  ///  </providers>
  /// </storage>
  /// ...
  ///
  /// </summary>
  [ServiceConfiguration(ServiceType = typeof (IPermanentStorage))]
  public class MongoDbPermanentStorage: PermanentStorage {
    private string _connectionUri = string.Empty;
    private string _databaseName = string.Empty;

    private MongoClient _client = null;
    private IMongoDatabase _database = null;

    private static object _lock = new object();
    private static readonly ILogger _logger = LogManager.GetLogger(typeof (MongoDbPermanentStorage));

    private Injected < IEPiServerFormsCoreConfig > _config;

    public MongoDbPermanentStorage() {
      var formsCoreConfig = _config.Service as EPiServerFormsCoreConfig;
      var providerSettings = formsCoreConfig.DefaultStorageProvider;

      _connectionUri = providerSettings.Parameters["connectionUri"];
      _databaseName = providerSettings.Parameters["databaseName"];

      lock(_lock) {
        _client = new MongoClient(_connectionUri);
        _database = _client.GetDatabase(_databaseName);
      }
    }

    /// <summary>
    /// Get collection associated with specified Form.
    /// </summary>
    /// <param name="formIden"></param>
    /// <returns></returns>
    protected virtual IMongoCollection < Submission > GetFormCollection(FormIdentity formIden) {
      var collectionName = string.Format("{0}{1}", Constants.StoreNamePrefix, formIden.Guid);
      IMongoCollection < Submission > collection = null;

      lock(_lock) {
        if (!IsCollectionExisted(_database, collectionName)) {
          _logger.Debug("Collection is not existed [{0}]. Create new one.", collectionName);
          _database.CreateCollection(collectionName);
          collection = _database.GetCollection < Submission > (collectionName);

          // create indexes for the form collection
          collection.Indexes.CreateOne(Builders < Submission > .IndexKeys.Ascending(s => s.Id));
          collection.Indexes.CreateOne(Builders < Submission > .IndexKeys.Ascending(s => s.Data[Constants.SYSTEMCOLUMN_SubmitTime]));
        } else {
          collection = _database.GetCollection < Submission > (collectionName);
        }
      }

      return collection;
    }

    /// <summary>
    /// Check if a collection existed in a database.
    /// </summary>
    /// <param name="database"></param>
    /// <param name="collectionName"></param>
    /// <returns></returns>
    protected virtual bool IsCollectionExisted(IMongoDatabase database, string collectionName) {
      var filter = new BsonDocument("name", collectionName);
      var collectionCursor = database.ListCollections(new ListCollectionsOptions {
        Filter = filter
      });
      return collectionCursor != null && collectionCursor.Any();
    }

    /// <summary>
    /// Save new submission into storage.
    /// </summary>
    public override Guid SaveToStorage(FormIdentity formIden, Submission submission) {
      var collection = GetFormCollection(formIden);
      var submissionId = Guid.NewGuid();
      submission.Id = submissionId.ToString();
      collection.InsertOne(submission);

      return submissionId;
    }

    /// <summary>
    /// Update a submission associated with a Guid.
    /// </summary>
    public override Guid UpdateToStorage(Guid formSubmissionId, FormIdentity formIden, Submission submission) {
      var collection = GetFormCollection(formIden);

      var updateFilter = Builders < Submission > .Filter.Eq(s => s.Id, formSubmissionId.ToString());
      var update = Builders < Submission > .Update.Set(s => s.Id, formSubmissionId.ToString());
      foreach(var item in submission.Data) {
        update = update.Set(s => s.Data[item.Key], item.Value);
      }
      collection.UpdateOne(updateFilter, update);

      return formSubmissionId;
    }

    /// <summary>
    /// Load submissions in a date time range.
    /// </summary>
    public override IEnumerable < PropertyBag > LoadSubmissionFromStorage(FormIdentity formIden, DateTime beginDate, DateTime endDate, bool finalizedOnly = false) {
      var collection = GetFormCollection(formIden);
      var filter = Builders < Submission > .Filter.Gte(s => s.Data[Constants.SYSTEMCOLUMN_SubmitTime], beginDate) &
        Builders < Submission > .Filter.Lte(s => s.Data[Constants.SYSTEMCOLUMN_SubmitTime], endDate);
      var submissions = collection.Find(filter);

      return submissions.ToEnumerable().Select(s => {
        var bag = new PropertyBag();
        bag.Id = Identity.NewIdentity(Guid.Parse(s.Id));
        bag.Add(s.Data);
        return bag;
      });
    }

    /// <summary>
    /// Load list of submissions.
    /// </summary>
    public override IEnumerable < PropertyBag > LoadSubmissionFromStorage(FormIdentity formIden, string[] submissionIds) {
      var collection = GetFormCollection(formIden);
      var externalIds = new List < string > ();
      foreach(var submissionId in submissionIds) {
        Identity parsedId;
        var externalId = submissionId.Contains(':') && Identity.TryParse(submissionId, out parsedId) ? // a full Identity string pattern = [long]:[Guid] ?
          parsedId.ExternalId.ToString() :
          submissionId; // or just has External Id
        externalIds.Add(externalId);
      }

      var filter = Builders < Submission > .Filter.In(s => s.Id, externalIds);
      var submissions = collection.Find(filter);

      return submissions.ToEnumerable().Select(s => {
        var bag = new PropertyBag();
        bag.Id = Identity.NewIdentity(Guid.Parse(s.Id));
        bag.Add(s.Data);
        return bag;
      });
    }

    /// <summary>
    /// Delete a submission from storage.
    /// </summary>
    public override void Delete(FormIdentity formIden, string submissionId) {
      var externalId = Identity.Parse(submissionId).ExternalId;
      var collection = GetFormCollection(formIden);
      var filter = Builders < Submission > .Filter.Eq(s => s.Id, externalId.ToString());
      collection.DeleteOne(filter);
    }
  }
}

Configure the database provider

The following example sets up the MongoDB configuration in the forms.config file:

  1. Use the <add> element to add a storage provider.
  2. Enter values for the properties below:
    • name – For example, MongoDBPermanentStorage.
    • type – The full namespace of your project's data storage and namespace. Use a comma to separate two values.
    • connectionUri – Database connection string.
    • databaseName – Your database name.
  3. Use new provider as the defaultProvider of the storage.

This is a sample forms.config file.