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 is0f8fad5b-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, andVisitorIdentifier
is the value of the cookie.EPiForm_VisitorIdentifier
mentioned above. A sample progressive cookie key isEPiForm_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 theFormGUID
.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.
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:
- Progressive cookie exists
If the cookie does exist, the server retrieves theIsFinalized
value from that cookie. If the value is true, Optimizely Forms gets the FormSubmissionID
from the submitted data (through a hidden field); otherwise, theÂSubmissionID
is obtained from the progressive cookie. If the value ofIsFinalized
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. - Progressive cookie does not exist
If the progressive cookie does not exist, Optimizely Forms gets theSubmissionID
from the submitted data. Next, Optimizely Forms checks if theSubmissionID
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 theSubmissionID
.
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 SubmissionID
is 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 keyvisitorSubmitTimeout
. -
The property
Order
: Optimizely Forms scans assemblies to find out which implementation ofIVisitorIdentifyProvider
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 theDefaultVisitorIdentifyProvider
class. -
If you want to customize the expiration time of Optimizely Forms, you need to implement the
IVisitorIdentifyProvider
interface by inheriting theDefaultVisitorIdentifyProvider
class and overriding desired functions or creating a class and implementing functions inIVisitorIdentifyProvider
from scratch. The recommendation is to inherit theDefaultVisitorIdentifyProvider
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}"); } } } } }
Updated 9 months ago