Changing the Visitor Submit Timeout in Optimizely Forms for CMS 12
When working with Optimizely Forms, you'll find there are multiple ways to configure the functionality of a form. One of those options an editor can configure is "Allow multiple submissions from the same IP/cookie", which is commonly used to prevent/deny a visitor from resubmitting the form, and it works by creating a browser cookie with the form's information.
While this can be a useful option to have to prevent a visitor from continually resubmitting a form, it's not foolproof. A visitor could easily resubmit the form in incognito mode (private browsing), or simply clear their browser cookies to resubmit the form, or just wait until the cookie expires.
But what if you want to allow the user to resubmit the form after a certain amount of time? Let's look at how we can change the timeout for a form resubmission.
Default Configuration
By default, the timeout for a form resubmission is set to 90 days. This is a good default value, but it may not be the best value for your specific use case. You may want to change this value to something shorter, such as 30 days, 7 days, or even 1 day.
The easiest way to change this value is to update the appsettings.json
file, providing the new value in the FormsConfig
section. Here's an example of how you can set the value to 1 day:
{
"EPiServer": {
"Forms": {
"FormsConfig": {
"VisitorSubmitTimeout": 1
}
}
}
}
An alternative way to set the value is to set the value in the ConfigureServices
function in Startup.cs
. Here's an example of how you can set the value to 1 day:
services.Configure<FormsConfigOptions>(options => options.VisitorSubmitTimeout = 1);
There are many more settings available within the FormsConfigOptions
class. You can find the full list of options in the Optimizely Forms documentation.
But what if you want to change this value to be shorter than 1 day? You can't do that with the FormsConfig
section, because the default functionality only considers that value to be in terms of days, and the VisitorSubmitTimeout
property must be an integer
type. If you want to set the value to be shorter than 1 day, you'll need to create a custom implementation.
Switching from Days to Seconds
To change the value from days to seconds, you'll need to extend the functionality found in the ProgressiveSubmitInfoService
class, which is in the EPiServer.Forms.Core.Internal
namespace.
I do need to mention that this class is in an internal namespace, which means it's not part of the supported public API.
Such classes can still be used, but they are not covered by the semantic versioning promise and can therefore change between minor releases without prior warning. Any problems that arise from using such classes are also not covered by our usual support.
In other words, this means that the class could change in future versions of Optimizely Forms, and your implementation could break. However, if you're willing to take that risk, here's how you can do it.
First, we'll need to extend the ProgressiveSubmitInfoService
class and override the SetProgressiveSubmitInfo
function. In this extended class, we'll just change the cookie's Expires
option to add seconds to the current DateTime
instead of days.
public class ExtendedProgressiveSubmitInfoService : ProgressiveSubmitInfoService
{
public override void SetProgressiveSubmitInfo(Guid formContentGuid, HttpContext httpContext, ProgressiveSubmitInfo progressiveSubmitInfo, string formLanguage)
{
var visitorIdentifier = _visitorIdentifyService.Service.GetVisitorIdentifyProvider(httpContext).GetVisitorIdentifier();
if (string.IsNullOrWhiteSpace(visitorIdentifier))
{
return;
}
string progressiveSubmitCookieKey = GetProgressiveSubmitCookieKey(formContentGuid, visitorIdentifier, formLanguage);
httpContext.Response.Cookies.Append(progressiveSubmitCookieKey, progressiveSubmitInfo.ToJson(), new CookieOptions()
{
HttpOnly = true,
Path = "/",
// Default functionality uses days
//Expires = new DateTimeOffset?((DateTimeOffset)DateTime.Now.AddDays(_formConfig.Service.VisitorSubmitTimeout)),
// New functionality uses seconds
Expires = new DateTimeOffset?((DateTimeOffset)DateTime.Now.AddSeconds(_formConfig.Service.VisitorSubmitTimeout)),
Secure = httpContext.Request.IsHttps
});
}
}
Using this Extended Class
To use this extended class, we unfortunately can't simply register it in the ConfigureServices
function in Startup.cs
. We'll need to create an IConfigurableModule
, ensuring it has a dependency on the EPiServer.Forms.InitializationModule
initialization. This will allow us to override the default implementation and use our ExtendedProgressiveSubmitInfoService
class.
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Forms.InitializationModule))]
public class FormsInitialization : IConfigurableModule
{
public void ConfigureContainer(ServiceConfigurationContext context)
{
context.ConfigurationComplete += (o, e) =>
{
context.Services.AddSingleton<ProgressiveSubmitInfoService, ExtendedProgressiveSubmitInfoService>();
};
}
public void Initialize(InitializationEngine context) { }
public void Uninitialize(InitializationEngine context) { }
}
Making This More Editor Configurable
The one issue with this approach is that all forms use this same VisitorSubmitTimeout
value. There may be times when you want to set a different value for different forms. If you want to make this more editor-configurable, there's a bit more work that needs to happen.
First, you'll need to have custom FormContainerBlock
. There is documentation available that shows how this can be done.
To that custom FormContainerBlock
, you'll need to add a new property that allows the editor to set a VisitorSubmitTimeout
value:
public class ExtendedFormContainerBlock : FormContainerBlock
{
[Display(Name = "Visitor Submit Timeout (In seconds)", GroupName = SiteConstants.GroupNames.Settings, Order = 1,
Description = "Used when 'Allow multiple submissions from the same IP/cookie' is not checked. Default value is 90 days.")]
[Range(0, int.MaxValue, ErrorMessage = "Only positive number allowed.")]
public virtual double VisitorSubmitTimeout { get; set; }
}
Finally, we'll change the ExtendedProgressiveSubmitInfoService
class to use this new value. We'll also change back the default functionality to use days as well:
public class ExtendedProgressiveSubmitInfoService : ProgressiveSubmitInfoService
{
protected Injected<IContentLoader> _contentLoader;
public override void SetProgressiveSubmitInfo(Guid formContentGuid, HttpContext httpContext, ProgressiveSubmitInfo progressiveSubmitInfo, string formLanguage)
{
var visitorIdentifier = _visitorIdentifyService.Service.GetVisitorIdentifyProvider(httpContext).GetVisitorIdentifier();
if (string.IsNullOrWhiteSpace(visitorIdentifier))
{
return;
}
string progressiveSubmitCookieKey = GetProgressiveSubmitCookieKey(formContentGuid, visitorIdentifier, formLanguage);
httpContext.Response.Cookies.Append(progressiveSubmitCookieKey, progressiveSubmitInfo.ToJson(), new CookieOptions()
{
HttpOnly = true,
Path = "/",
Expires = GetExpires(formContentGuid), // Compared to default functionality, this line is the only change
Secure = httpContext.Request.IsHttps
});
}
private DateTimeOffset? GetExpires(Guid formContentGuid)
{
// Default functionality sets expires to X days from now, where X is the value of the form configuration's VisitorSubmitTimeout property
var expires = new DateTimeOffset?((DateTimeOffset)DateTime.Now.AddDays(_formConfig.Service.VisitorSubmitTimeout));
// Extended functionality sets expires to X seconds from now, where X is the value of the form instance's VisitorSubmitTimeout property
var currentForm = _contentLoader.Service.Get<IContent>(formContentGuid) as ExtendedFormContainerBlock.ExtendedFormContainerBlock;
if (currentForm != null && currentForm.VisitorSubmitTimeout > 0)
{
expires = new DateTimeOffset?((DateTimeOffset)DateTime.Now.AddSeconds(currentForm.VisitorSubmitTimeout));
}
return expires;
}
}
With this, you can now set a different VisitorSubmitTimeout
value for each form, allowing visitors to resubmit the form but after a much shorter time.