HomeGuidesAPI Reference
Submit Documentation FeedbackJoin Developer CommunityLog In

Creating a new data storage mechanism

This topic 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

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)

A guide to the 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 new 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, based on these two parameters, developers can save data into database::
      • formIden. Contains form information, such as form id or language.
      • submission. Contains form submission data.
    • UpdateToStorage(Guid formSubmissionId, FormIdentity formIden, Submission submission). This function is for saving data at the next steps or updating data at previous ones. Within this method, based on these two parameters, developers can update data in the database.
      • 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 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, which significantly improves the performance of data queries. 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, 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 new 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, and then saves it to the MongoDB.
  • UpdateToStorage(Guid formSubmissionId, FormIdentity formIden, Submission submission). Updates the collection associated with the forms update submission data previously stored into it.
  • 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 collectionassociated 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);
          }
      }
  }

Configuring the new 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.


What’s Next
Did this page help you?