Try our conversational search powered by Generative AI!

Jacob Pretorius
May 10, 2019
  4772
(6 votes)

Autofill Episerver Forms fields from Profile Store

As a user, it can be frustrating to repeatedly fill in the same information, especially if the system already has that data recorded. So why not use all the info from Profile Store to autofill forms and save some time?

The Episerver Profile Store can be a great option for tracking visitor behaviour and getting personalisation going quickly. Once you combine the Profile Store with Insight, Forms and David Knipe's Insight Form Field Mapper, you can easily extract a great deal of value from the service.

At Zone, we've recently been investigating how we can extend the Episerver platform to provide the best user experience possible. One of the stories we have is the ability to autofill Episerver Forms elements.

With this idea in mind I started digging around to see how we could achieve exactly that. Fortunately, I found the developer guides for the Forms Autofill API and even though it’s still in beta, it doesn't look like there has been much new development going on around it. I decided to give it a go.

For this demo we show a user providing their details in a standard custom form during checkout. That info is then stored to the Profile Store and retrieved to autofill the Forms elements on the homepage. Sharing information between different forms is also supported as long as they are mapped to push and pull data from the Profile Store in the CMS.

All we really need to do to get the Forms elements hooked up to our data source (Profile Store) is implement IExternalSystem and IAutofillProvider.

Implementing the interfaces

Both interfaces can be implemented in the same class as there is some overlap between them. I've called my implementing class ProfileStoreAutofillFields. The IExternalSystem interface is used to register a custom data source and drives the CMS drop-down list.

public virtual string Id
{
    get { return "ProfileStoreAutofillFields"; }
}

public virtual IEnumerable<IDatasource> Datasources
{
    get
    {
        // Register the Profile Store as a data source
        var profileStoreDataSource = new Datasource()
        {
            Id = "ProfileStoreDataSource",
            Name = "Profile Store Data Source",
            OwnerSystem = this,
            Columns = new Dictionary<string, string> {
                // "Name of mapped field", "friendly name in CMS"
                { "profilestoreemail", "Email" },
                { "profilestorename", "Name" },
                { "profilestorecity", "City" },
                { "profilestoremobile", "Mobile" },
                { "profilestorephone", "Phone" }
            }
        };

        return new[] { profileStoreDataSource };
    }
}

Up next, the IAutofillProvider is where the magic happens and we use GetSuggestedValues to provide our "suggested" values from the Profile Store based on the device GUID as set in the Profile Store Cookie. The drop-down list in the CMS needs to match here for a value to be suggested.

/// <summary>
/// Returns a list of suggested values by field mapping key. This will be called automatically by the GetAutofillValues() function in DataElementBlockBase for each field
/// </summary>
/// <returns>Collection of suggested values</returns>
public virtual IEnumerable<string> GetSuggestedValues(IDatasource selectedDatasource, IEnumerable<RemoteFieldInfo> remoteFieldInfos,
ElementBlockBase content, IFormContainerBlock formContainerBlock, HttpContextBase context)
{
    if (selectedDatasource == null || remoteFieldInfos == null)
        return Enumerable.Empty<string>();

    // Make sure the Data sources are for this system
    if (!this.Datasources.Any(ds => ds.Id == selectedDatasource.Id)
        || !remoteFieldInfos.Any(mi => mi.DatasourceId == selectedDatasource.Id))
    {
        return Enumerable.Empty<string>();
    }

    // We also need to make sure that we have some tracking info to auto fill
    // _madid is the default Episerver Profile Store tracking cookie, see https://world.episerver.com/documentation/developer-guides/tracking/episerver-cookies/
    var userDeviceId = context.Request.Cookies["_madid"]?.Value;

    // Because this gets called with EVERY FIELD it is suggested to cache the response elsewhere
    var userProfile = ProfileStoreApiService.GetProfileByDeviceId(userDeviceId);
    if (userProfile == null)
    {
        return Enumerable.Empty<string>();
    }
    
    // Unpack the info object
    var info = userProfile["Info"];

    // Get the field details
    var activeRemoteFieldInfo = remoteFieldInfos.FirstOrDefault(mi => mi.DatasourceId == selectedDatasource.Id);
    switch (activeRemoteFieldInfo.ColumnId)
    {
        // Suggest the data from the Profile Store user profile
        case "profilestoreemail":
            return new List<string> {
                (string)(info["Email"] ?? info["Email"]?.ToString())
            };

        case "profilestorename":
            return new List<string> {
                (string)(userProfile["Name"] ?? userProfile["Name"]?.ToString())
            };

        case "profilestorecity":
            return new List<string>{
                (string)(info["City"] ?? info["City"]?.ToString())
            };

        case "profilestorephone":
            return new List<string>{
                (string)(info["Phone"] ?? info["Phone"]?.ToString())
            };

        case "profilestoremobile":
            return new List<string>{
                (string)(info["Mobile"] ?? info["Mobile"]?.ToString())
            };

        default:
            return Enumerable.Empty<string>();
    } 
}

I have only added a handful of fields to demonstrate how it works. To add more, simply update the implementation of both interfaces with the fields available in the Profile Store API - all of which you can see here.

Getting the user profile from the Profile Store

We get all the user info by querying the Profile Store API. There are some ongoing improvements to provide a wrapper instead of manually having to implement the API calls, such as the Episerver Profile Store .NET client; however, to keep things flexible I've provided a (mostly unchanged) version of the one call we need here from the client by David Knipe.

using Newtonsoft.Json.Linq;
using RestSharp;

public static class ProfileStoreApiService
{
    // This should come from app settings really
    private static readonly string apiRootUrl = "https://somesecret.profilestore.episerver.net"; // Get from Insight / Profile store developer portal
    private static readonly string subscriptionKey = "somesecret"; // Get from Insight / Profile store developer portal

    public static JToken GetProfileByDeviceId(string deviceId)
    {
        // Set up the request
        var client = new RestClient(apiRootUrl);
        var request = new RestRequest("api/v1.0/Profiles", Method.GET);
        request.AddHeader("Ocp-Apim-Subscription-Key", subscriptionKey);

        // Filter the profiles based on the current device id
        request.AddParameter("$filter", "DeviceIds eq " + deviceId);

        // Execute the request to get the profile
        var getProfileResponse = client.Execute(request);
        var getProfileContent = getProfileResponse.Content;

        // Get the results as a JArray object
        if (!string.IsNullOrWhiteSpace(getProfileContent))
        {
            var profileResponseObject = JObject.Parse(getProfileContent);
            var profileArray = (JArray)profileResponseObject["items"];

            // Expecting an array of profiles with one item in it
            return profileArray.First;
        }

        return null;
    }
}

It should be noted that in production you would want to build out this ProfileStoreApiService to include some form of response caching. The GetSuggestedValues function we have calls GetProfileByDeviceId (aka the Profile Store API) for every Forms element we have mapped. Personally, I would parse the response JToken object and cache a strongly typed user profile object, but there are various ways to go about it.

With all that done it's time to spin up the site and go update some Forms.

Forms configuration

First, you need to update the Form Container to map our new data source.


Next, we need to edit the Form Elements to link the mappings.


Make sure it's mapped to push responses to the correct Profile Store/Insight fields.

And finally, map to the Profile Store data source to retrieve the stored fields.

That's all there is to it! You should now have Forms elements that fill themselves out as the user information gets updated.

Future work

This should be seen as more of a POC to get things going. You need to decide if and when you want to "trust" that a user profile is actually who we think they are (such as only for logged-in users) to make sure you don't expose personal user data.

Useful resources

Check out the Forms demo project and the Episerver Insight Form Field Mapper to see examples of what else is currently possible. The sample code for this can be found on Github.

May 10, 2019

Comments

David Knipe
David Knipe May 11, 2019 09:47 AM

Nice write up - thanks for sharing :)!

Please login to comment.
Latest blogs
Azure AI Language – Extractive Summarisation in Optimizely CMS

In this article, I demonstrate how extractive summarisation, provided by the Azure AI Language platform, can be leveraged to produce a set of summa...

Anil Patel | Apr 26, 2024 | Syndicated blog

Optimizely Unit Testing Using CmsContentScaffolding Package

Introduction Unit tests shouldn't be created just for business logic, but also for the content and rules defined for content creation (available...

MilosR | Apr 26, 2024

Solving the mystery of high memory usage

Sometimes, my work is easy, the problem could be resolved with one look (when I’m lucky enough to look at where it needs to be looked, just like th...

Quan Mai | Apr 22, 2024 | Syndicated blog

Search & Navigation reporting improvements

From version 16.1.0 there are some updates on the statistics pages: Add pagination to search phrase list Allows choosing a custom date range to get...

Phong | Apr 22, 2024

Optimizely and the never-ending story of the missing globe!

I've worked with Optimizely CMS for 14 years, and there are two things I'm obsessed with: Link validation and the globe that keeps disappearing on...

Tomas Hensrud Gulla | Apr 18, 2024 | Syndicated blog

Visitor Groups Usage Report For Optimizely CMS 12

This add-on offers detailed information on how visitor groups are used and how effective they are within Optimizely CMS. Editors can monitor and...

Adnan Zameer | Apr 18, 2024 | Syndicated blog