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

How Forms cookies work

Describes how Optimizely Forms deals with cookies to keep track of end-user identifiers as well as the status of form submissions

Description of cookies in Forms

Like other web applications, Optimizely Forms uses cookies to implement business rules and provide visitors with a better experience. The following cookies are used in Optimizely Forms.

  • .EPiForm_BID – This cookie is for distinguishing one browser from other browsers a visitor uses while surfing the internet. If a user visits an Optimizely site for the first time, Optimizely Forms automatically assigns a random GUID to the visitor's browser. The GUID is stored in a cookie and when it has expired, Optimizely Forms creates one the next time the user visits the website.

  • .EPiForm_VisitorIdentifier – This cookie is used to identify who is interacting with the application. It is created by combining the value of .EPiForm_BID with the current user name in case the user is already logged in. A colon is used as a separator. If the user is not logged in, the value of .EPiForm_VisitorIdentifier is the value of .EPiForm_BID concatenated with a colon. The sample value of this cookie is 0f8fad5b-d9cb-469f-a165-70867728950e:admin.

  • To keep track of which forms a user is interacting with and if they are completed or uncompleted, Optimizely Forms uses a cookie name progressive cookie. The format of the cookie key is EPiForm_{FormGUID}_{VisitorIdentifier}. FormGUID is the primary key of a form instance. Each form instance is given a GUID as a unique identifier right after it is created, and VisitorIdentifier is the value of the cookie .EPiForm_VisitorIdentifier mentioned above. A sample progressive cookie key is

    EPiForm_1g9kfad5z-d9bb-964f-a165-7086772895e_0f8fad5b-d9cb-469f-a165-70867728950e:admin

The value of the progressive cookie is a JSON object consisting of the following properties:

  • FormGUID – Already mentioned earlier.
  • SubmissionID – The ID of the stored submission data (both finalized and unfinalized) associated with the FormGUID.
  • IsFinalized – The status of the form submission. The value is true if the user finalizes the form by clicking the Finalize Submit button or finishing all steps of a form instance.  The Submit button form element has a property name Finalized. If, for example, a form has three steps and you want to let users finish the form at the second step, you can drag a submit button into the form and set the value of that property to true. Users can go through the three steps or finish the form at the second step.
600

Process for handling cookies

After submitting a form or navigating to another form step, Optimizely Forms checks if the progressive cookie related to the form instance (and current VisitorIdentifier) exists or not:

  1. Progressive cookie exists
    If the cookie does exist, the server retrieves the IsFinalized value from that cookie. If the value is true, Optimizely Forms gets the Form SubmissionID from the submitted data (through a hidden field); otherwise, the  SubmissionID is obtained from the progressive cookie. If the value of IsFinalized is true; it means that the form allows multiple submissions from the same IP address/cookie and that the user has completed submitting data at least once plus this time of submitting.
  2. Progressive cookie does not exist
    If the progressive cookie does not exist, Optimizely Forms gets the SubmissionID from the submitted data. Next, Optimizely Forms checks if the SubmissionID is null. The submission data is saved to the storage as a record if null. If it is not null, it means that the form instance has multi-steps and the user has already interacted with that form but has not completed the whole steps. The submitted data is updated to an existing record by the SubmissionID.

After saving or updating submission data, the output of the process is SubmissionID and progressive cookie is set (creating one if no one exists or updating existing cookie). The last process is to load the form elements of the next step along with the SubmissionID and display on the page. The SubmissionIDis used in a hidden field for the next navigation or submission.

Because an Optimizely Forms application may have multiple instances of a form (for example, Form_1 and Form_2), many progressive cookies may exist. For example, if a user visits an Optimizely site and finishes step 1 out of three steps on Form_1, and then directly jumps to Form_2 and starts entering information, then two progressive cookies are created. Those cookies tell the server that the user interacted with two different form instances, whether they were completed or not.

JavaScript enabled

If JavaScript is enabled, navigating between steps (previous and next step) is performed with an AJAX request, and the client uses the session storage to store submission data temporarily. Thus, if the form has multiple steps and a user fills in form data in the last step but wants to return to the previous step, the entered data is still retained. The user does not have to re-insert data, and the server does not need to hit the database to fetch data.

JavaScript disabled

If JavaScript is disabled, navigating between steps causes the browser to get redirected to a certain page to display form steps. Optimizely has to hit the database to retrieve already entered data, and fill in form elements.

Manage cookie expiration time

For consistency, the cookies mentioned above are set to the same expiration time.

The IVisitorIdentifyProvider interface is responsible for handling the form cookie expiration time of visitors. DefaultVisitorIdentifyProvider is the default implementation. There are a few points to note:

  • The function SetVisitorIdentifier is used to set the .EPiForm_VisitorIdentifier cookie for identifying visitors.

  • Currently, the expiration time unit is based on day, and the default expiration time is 90 days. You can modify this setting in the Forms.config file with the key visitorSubmitTimeout.

    861
  • The property Order: Optimizely Forms scans assemblies to find out which implementation of IVisitorIdentifyProvider interface is executed based on Ascending Order. If there are multiple implementations, the implementation with the lowest order is executed. The default order is 1000 in the DefaultVisitorIdentifyProvider class.

  • If you want to customize the expiration time of Optimizely Forms, you need to implement the IVisitorIdentifyProvider interface by inheriting the DefaultVisitorIdentifyProvider class and overriding desired functions or creating a class and implementing functions in IVisitorIdentifyProvider from scratch. The recommendation is to inherit the DefaultVisitorIdentifyProvider class for simplicity. Example code:

    using EPiServer.Forms.Core;
    using EPiServer.Forms.Core.Internal;
    using EPiServer.Forms.Core.Internal.VisitorIdentify;
    using EPiServer.Logging;
    using EPiServer.ServiceLocation;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using EPiServer.Forms.Core.Models.Internal;
    
    namespace OptimizelySiteLM.CustomForms {
      /// <summary>
      /// Handling form cookie expiration time.
      /// </summary>
      [ServiceConfiguration(typeof (IVisitorIdentifyProvider))]
      public class CustomVisitorIdentifyProvider: DefaultVisitorIdentifyProvider {
        private static readonly object _lock = new object();
        private static readonly ILogger _logger = LogManager.GetLogger(typeof (VisitorIdentifyService));
        private
        const int _expirationTimeInMinute = 20;
    
        /// <summary>
        /// Optimizely Container will scan in all assemblies to find out which Implementation will get executed.
        /// If there are multi-implementations, class with lowest Order will be executed. The Order of DefaultVisitorIdentifyProvider class is 1000.
        /// So we have to set this value to 1 (as long as lower than 1000)
        /// </summary>
        public override int Order {
          get {
            return 1;
          }
        }
    
        /// <summary>
        /// Set form cookie. We keep everything as default in base class except modifying the expiration time.
        /// </summary>
        /// <param name="visitorIdentifier"></param>
        public override void SetVisitorIdentifier(string visitorIdentifier) {
          // TECH NOTE: When access cookies from multi threads, sometime it throw exceptions even we use lock for synchronizing.
          // So that we need surround code with try/catch to make sure the exception does not break the request.
    
          HttpCookie cookieBrowserID;
          HttpCookie cookieVI;
    
          lock(_lock) {
            try {
              cookieBrowserID = Context.Request.Cookies[CookieName_Forms_BrowserID] ?? new HttpCookie(CookieName_Forms_BrowserID);
            } catch (Exception) {
              _logger.Warning($"Cannot get cookies: {CookieName_Forms_BrowserID}");
              cookieBrowserID = new HttpCookie(CookieName_Forms_BrowserID);
            }
    
            try {
              cookieVI = Context.Request.Cookies[CookieName_Forms_VisitorIdentifier] ?? new HttpCookie(CookieName_Forms_VisitorIdentifier);
            } catch (Exception) {
              _logger.Warning($"Cannot get cookies: {CookieName_Forms_VisitorIdentifier}");
              cookieVI = new HttpCookie(CookieName_Forms_VisitorIdentifier);
            }
          }
    
          string browserID = string.Empty, newVI = string.Empty;
    
          if (string.IsNullOrWhiteSpace(visitorIdentifier)) {
            browserID = GenerateBrowserId();
            newVI = BuildVisitorIdentifier(browserID, GetCurrentUserID());
          } else {
            var arrSplit = visitorIdentifier.Split(new string[] {
              SEPARATOR
            }, StringSplitOptions.RemoveEmptyEntries);
            browserID = arrSplit[0];
            newVI = visitorIdentifier;
          }
    
          cookieBrowserID.Value = browserID;
    
          // The default time unit is based on day. 
          //cookieBrowserID.Expires = DateTime.Now.AddDays(_formConfig.Service.VisitorSubmitTimeout);
    
          // If you want to use minute as unit then use this code
          cookieBrowserID.Expires = DateTime.Now.AddMinutes(_expirationTimeInMinute); // will expire in 20 minutes
    
          cookieBrowserID.Path = "/";
          cookieVI.Value = newVI;
          // The default time unit is based on day. 
          //cookieVI.Expires = DateTime.Now.AddDays(_formConfig.Service.VisitorSubmitTimeout);
    
          // If you want to use minute as unit then use this code
          cookieVI.Expires = DateTime.Now.AddMinutes(_expirationTimeInMinute); // will expire in 20 minutes
    
          cookieVI.Path = "/";
    
          lock(_lock) {
            try {
              if (Context.Response.Cookies.Keys.OfType < string > ().Contains(cookieBrowserID.Name)) {
                Context.Response.Cookies.Set(cookieBrowserID);
              } else {
                Context.Response.Cookies.Add(cookieBrowserID);
              }
            } catch (Exception) {
              _logger.Warning($"Cannot set cookies: {cookieBrowserID.Name}");
            }
    
            try {
              if (Context.Response.Cookies.Keys.OfType < string > ().Contains(cookieVI.Name)) {
                Context.Response.Cookies.Set(cookieVI);
              } else {
                Context.Response.Cookies.Add(cookieVI);
              }
            } catch (Exception) {
              _logger.Warning($"Cannot set cookies: {cookieVI.Name}");
            }
          }
        }
      }
    }