Thursday, October 7, 2021

Creating Gated Content with Optimizely Forms

I recently worked on an Optimizely project where the customer wanted to serve gated content to visitors. If you're not familiar with gated content, it's where content is only available to a user that fills out a form - the form being the "gate". Gated content can be a powerful marketing tool to help capture valuable information for lead generation, or for tracking visitor statistics. 

In our case, the customer wanted to provide whitepaper downloads to visitors that provided a defined set of information. Our goal was to create this interaction using Optimizely Forms.

This approach involved several setup steps

A Visitor Group was created in Optimizely that a site visitor would be added to when they submitted a specific form. A ContentArea was created with the specified gated content form, and a block to provide a description and download link for the whitepaper. Using Optimizely Personalization on the ContentArea, the whitepaper block was set to display only for the Visitor Group, and the form for Everyone Else.

This initial setup worked, but only if the page was refreshed after submitting the form. Because the form submission processes asynchronously in JS mode, the page didn't refresh on it's own. Forms allow you to specify a redirect page on submission, but that would make each use of the form redirect to the same page, and the form needed to be used on multiple pages and refresh only the page they were on.

A custom form container block was the first piece to accomplish this

The custom form container block addressed several things:
  1. A property was added to indicate that the form should refresh the current page after submission.
  2. The RedirectToPage value had to be modified after submission since Optimizely uses this property for the out-of-box redirect functionality. However, this property is marked virtual, so it's readonly by default. To work around that, it needed to be overridden and ignored. (read more about why it's readonly)
  3. To allow existing redirect functionality for non-gated forms a new property had to be added to store a redirect URL.
  4. Because this is the new "Standard Form" and we didn't want to use the OOB form, the OOB Optimizely Form had to be hidden from the editors.

The new custom form container code looks like this:

[pre class="brush:csharp;class-name:collapse-box"]
using EpiSandbox.Interfaces;
using EPiServer;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using EPiServer.Forms.Core;
using EPiServer.Forms.Core.Events;
using EPiServer.Forms.Implementation.Elements;
using EPiServer.ServiceLocation;
using System.ComponentModel.DataAnnotations;
using System.Web;

namespace EpiSandbox.Models
    //  it's called Form Container so it looks like the OOB one. :) Sneaky
    [ContentType(DisplayName = "Form Container", GUID = "d6b5bbce-c897-4e4f-bb54-d14cd6b8a3a7", Description = "Custom form container to replace default Optimizely Form.",
        GroupName = EPiServer.Forms.Constants.FormElementGroup_Container)]

    [ImageUrl("~/resources/icons/custom-form-block.jpg")] // just for demo purposes to show it's not OOB form
    public class StandardFormContainerBlock : FormContainerBlock, IFormSubmitted
        [Display(Name = "Refresh page after submitting form.", GroupName = "Information", Order = -6499)]
        public virtual bool RefreshAfterSubmission { get; set; }

        // ignore this so it can be modified later, otherwise it's readonly
        public override Url RedirectToPage { get; set; }

        // use this to specify redirect url instead
        [Display(Name = "Display page after submission", Description = "Select a page to show after form submission",
            GroupName = "Information", Order = -6500)]
        public virtual Url RedirectPageAfterSubmission { get; set; }

        public void OnSubmissionFinalized(object sender, FormsEventArgs e)
            var submittedEventArgs = e as FormsSubmittedEventArgs;
            var formBlock = submittedEventArgs.FormsContent as StandardFormContainerBlock;
            if (submittedEventArgs != null && formBlock != null)
                if (formBlock.RefreshAfterSubmission)
                    formBlock.RedirectToPage = new Url(HttpContext.Current.Request.UrlReferrer.AbsolutePath);
                else if (formBlock.RedirectPageAfterSubmission != null && !formBlock.RedirectPageAfterSubmission.IsEmpty())
                    formBlock.RedirectToPage = formBlock.RedirectPageAfterSubmission;


To render this new form container using the standard view, a new controller inheriting from the default one is required.

[pre class="brush:csharp;class-name:collapse-box;"]
using EpiSandbox.Models;
using EPiServer.Forms.Controllers;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Framework.Web;

namespace EpiSandbox.Controllers
    [TemplateDescriptor(AvailableWithoutTag = true,
        Default = true,
        ModelType = typeof(StandardFormContainerBlock),
        TemplateTypeCategory = TemplateTypeCategories.MvcPartialController
    public class StandardFormContainerBlockController: FormContainerBlockController
        // the base Index action takes over


The approach from my article Form Events Interface Pattern is used for handling the form submission event, OnSubmissionFinalized, in the code above.

To hide the default Optimizely FormContainerBlock, follow this approach.

At this point the page will refresh when you submit the form, but Optimizely will be adding extra form information in the URL. Follow my article, Hide Optimizely Form Info when Redirecting, to stop that behaviour. For this use case replace the condition to check for HideFormInfoOnRedirect with RefreshAfterSubmission to prevent the info from being added when refreshing.

[pre class="brush:csharp"]
public override IDictionary<string, object> GetExtraInfo(FormIdentity formIden, Submission submission)
    if (formIden == null) { return new Dictionary<string, object>(); }

    var formBlock = _contentRepository.Get<FormContainerBlock>(formIden.Guid) as StandardFormContainerBlock;
    if (formBlock.RefreshAfterSubmission)
        return new Dictionary<string, object>();
    return base.GetExtraInfo(formIden, submission);  // this is the default behaviour

With everything wired up with the Visitor Groups...

No comments:

Post a Comment

Share your thoughts or ask questions...