Sitecore Forms – Custom Value Provider for multi-value fields

Sitecore Forms offers the possiblity for a content editor to prefill certain fields within the Form by using Value Providers. Sitecore does not ship any default value providers out of the box.

Sitecore has a great example that uses a custom Value Provider to prefill form fields based on the values that are located within the user’s profile. Please see the link below for an example:
https://doc.sitecore.com/developers/91/sitecore-experience-manager/en/walkthrough–setting-up-a-value-provider-for-prefilling-forms.html

Unfortunately this implementation only works for simple text fields, like Single-line Text, Multiple-line text, Email, etc. The Value Provider will not work for fields that can hold multiple values, like the Dropdown list, Radio Button list, List Box and the Checkbox List. These fields use the ListViewModel as InputViewModel. When you try to use the example from the Sitecore Documentation page on these fields, you will run into the following error:

[ArgumentNullException: Value cannot be null. Parameter name: source]
   System.Linq.Enumerable.Contains(IEnumerable`1 source, TSource value, IEqualityComparer`1 comparer) +409
   Sitecore.ExperienceForms.Mvc.Models.Fields.<>c__DisplayClass31_0.<OnValueChanged>b__0(ListFieldItem i) +31
   System.Collections.Generic.List`1.ForEach(Action`1 action) +101
   Sitecore.ExperienceForms.Mvc.Models.Fields.InputViewModel`1.InitializeValue(FieldValueProviderContext context) +289
   Sitecore.ExperienceForms.Mvc.Pipelines.GetModel.CreateModel.Process(GetModelEventArgs args) +477
   (Object , Object ) +14
   Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args) +490
   Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists) +236
   Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain) +22
   Sitecore.Mvc.Pipelines.PipelineService.RunPipeline(String pipelineName, TArgs args) +195
   Sitecore.Mvc.Pipelines.PipelineService.RunPipeline(String pipelineName, TArgs args, Func`2 resultGetter) +161
   Sitecore.ExperienceForms.Mvc.Pipelines.RenderField.ResolveModel.Process(RenderFieldEventArgs args) +393
   (Object , Object ) +14
   Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args) +490
   Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists) +236
   Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain) +22
   Sitecore.Mvc.Pipelines.PipelineService.RunPipeline(String pipelineName, TArgs args) +195
   Sitecore.Mvc.Pipelines.PipelineService.RunPipeline(String pipelineName, TArgs args, Func`2 resultGetter) +161
   Sitecore.ExperienceForms.Mvc.Pipelines.RenderFields.RenderChildFields.Process(RenderFieldsEventArgs args) +588
   (Object , Object ) +14
   Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args) +490
   Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists) +236
   Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain) +22
   Sitecore.Mvc.Pipelines.PipelineService.RunPipeline(String pipelineName, TArgs args) +195
   Sitecore.Mvc.Pipelines.PipelineService.RunPipeline(String pipelineName, TArgs args, Func`2 resultGetter) +161
   Sitecore.ExperienceForms.Mvc.Html.HtmlExtensions.RenderFields(HtmlHelper htmlHelper, TModel model) +394

The error stated above occurs because Sitecore did not implement any conversion from a string to List<string>. Luckily we can retrieve the type of the field within the Value Provider, and return the proper type that the field requires. Please see the example below.

using System.Collections.Generic;
using System.Linq;
using Sitecore.Data;
using Sitecore.ExperienceForms.Models;
using Sitecore.ExperienceForms.Mvc.Models.Fields;
using Sitecore.ExperienceForms.ValueProviders;
using Sitecore.Reflection;

namespace Example.ValueProviders
{
    public class UserProfileValueProvider : IFieldValueProvider
    {
        public object GetValue(string parameters)
        {
            if (string.IsNullOrEmpty(parameters))
                return null;

            string value = null;
            if (Sitecore.Context.User?.IsAuthenticated ?? false)
            {
                value = Sitecore.Context.User.Profile[parameters.Trim()];
            }

            var fieldTypeId = ValueProviderContext.FieldItem?.Fields[Sitecore.ExperienceForms.Mvc.Constants.FieldNames.FieldType]?.Value;
            if (!string.IsNullOrEmpty(fieldTypeId))
            {
                var fieldTypeItem = ValueProviderContext.Database.GetItem(new ID(fieldTypeId));
                var modelType = fieldTypeItem.Fields[Sitecore.ExperienceForms.Mvc.Constants.FieldNames.ModelType]?.Value;
                if (modelType != null)
                {
                    var model = ReflectionUtil.CreateObject(modelType) as IViewModel;
                    if (model is InputViewModel<List<string>>)
                    {
                        return value?.Split('|').ToList() ?? new List<string>();
                    }
                }
            }
            
            return value;
        }

        public FieldValueProviderContext ValueProviderContext { get; set; }
    }
}

Please note that you can also check on the FieldTypeId directly, however I did not want to add all the different IDs, and this gives the flexibility to add other field types to Sitecore without having to change the value provider.

Leave a Reply

Your email address will not be published. Required fields are marked *