People using and pointing at laptop screen
Technology
Sitecore Development

Sitecore Forms Conditional Validation Part 2

Oct 01, 2020

In the previous post, we implemented a client-side comparison conditional validation for two numeric fields using Unobtrusive jQuery Validation. It works well for one-page forms, or when both fields are on the same page. But in multi-page forms, there could be circumstances where a field in a page has to be validated conditionally with the value from a field on another page. In this post, we are going to implement this case using server-side validation.

What do you need?

For this example we are going to use Sitecore 9.1.1 with the same requirements as the previous post.

How to implement?

We are going to use the same two-page form example we used in the previous Sitecore Forms post, including the “Other Number” field we created in the previous post:

Empty Sitecore Form


Sitecore form second page


Our new validation will be “Another Number must be less than Other Number”. To do this, we have to somehow read the value entered in the first page for the Other Number field. Fortunately, when you submit one page of the form, Sitecore Forms stores the values you entered in session. That means that we are able to read those values on the next page, but in server code. Let’s first create an extension method that will return the value stored in session of a field. We are going to use the Sitecore.ExperienceForms.Mvc.IFormRenderingContext service to access the stored fields.

FormExtensions.cs

namespace Oshyn.Forms.Samples.Extensions
{
    public static class FormExtensions
    {
        public static string GetPostedFieldValue(this IFormRenderingContext formContext, string itemId)
        {
            if (!ID.TryParse(itemId, out ID id))
            {
                return null;
            }
 
            var postedField = formContext.GetPostedField(id);
 
            if (postedField == null)
            {
                return null;
            }
 
            //If reading other types of fields, include code here to cast field types and extract values.
 
            return postedField.GetType().GetProperty(“Value”)?.GetValue(postedField, null)?.ToString();
        }
    }
}

Now let’s create our validation class:

OtherPageConditionalValidation.cs

namespace Oshyn.Forms.Samples.Validations
{
    public class OtherPageConditionalValidation : ValidationElement<FieldComparisonParameters>
    {
        public override IEnumerable<ModelClientValidationRule> ClientValidationRules
        {
            get
            {
                yield return new ModelClientValidationRule
                {
                    ErrorMessage = FormatMessage(Title, GetOtherFieldTitle()),
                    ValidationType = “otherpagecond”
                };
            }
        }
 
        protected virtual string Title { get; set; }
 
        protected virtual string OtherFieldId { get; set; }
 
        protected virtual string Operator { get; set; }
 
        public OtherPageConditionalValidation(ValidationDataModel validationItem) : base(validationItem) { }
 
        public override void Initialize(object validationModel)
        {
            base.Initialize(validationModel);
 
            Assert.IsNotNullOrEmpty(Parameters?.OtherFieldId, “Other Field ID must be set.”);
            Assert.IsNotNullOrEmpty(Parameters?.Operator, “Operator must be set to >, >=, <, <= or =“);
 
            var field = validationModel as TitleFieldViewModel;
 
            Title = field.Title;
            OtherFieldId = Parameters?.OtherFieldId;
            Operator = Parameters?.Operator;
        }
 
        public override ValidationResult Validate(object value)
        {
            var numericValue = value as double?;
 
            if (numericValue == null || !numericValue.HasValue)
            {
                return ValidationResult.Success;
            }
 
            var formContext = ServiceLocator.ServiceProvider.GetService<IFormRenderingContext>();
            var otherFieldValue = formContext.GetPostedFieldValue(OtherFieldId);
 
            if (string.IsNullOrEmpty(otherFieldValue) || !double.TryParse(otherFieldValue, out double otherFieldNumericValue))
            {
                return ValidationResult.Success;
            }
 
            switch (Operator)
            {
                case “>” when numericValue.Value > otherFieldNumericValue:
                case “>=“ when numericValue.Value >= otherFieldNumericValue:
                case “<“ when numericValue.Value < otherFieldNumericValue:
                case “<=“ when numericValue.Value <= otherFieldNumericValue:
                case “=“ when numericValue.Value == otherFieldNumericValue:
                    return ValidationResult.Success;
            }
 
            return new ValidationResult(FormatMessage(Title, GetOtherFieldTitle()));
        }
 
        private string GetOtherFieldTitle()
        {
            var otherFieldItem = Sitecore.Context.Database.GetItem(ID.Parse(OtherFieldId));
 
            if (otherFieldItem == null)
            {
                return “Other Field”;
            }
 
            return !string.IsNullOrWhiteSpace(otherFieldItem[“Title”]) ? otherFieldItem[“Title”] : otherFieldItem.Name;
        }
    }
}

Let’s analyze this class implementation:

  • You’ll notice the declaration of the class is very similar to the one we implemented in the previous post. It reuses FieldComparisonParameters as the parameters class (see the previous post for details on this class).
  • The ClientValidationRules property’s get is now very simple. It only returns a simple ModelClientValidationRule with the formatted error message, and a type (make sure this type is not used at all on the client side)
  • The Validate() method now contains the logic of the validation:
    • It converts the field’s value to double.
    • It gets the IFormRenderingContext service, and uses the extension method we created previously to extract the value from the other field.
    • It converts the other field’s value to double.
    • The switch block reads the Operator property, and applies the conditional logic with both fields depending on the provided Operator.
    • If the condition is not fulfilled, return a ValidationResult containing the formatted message that should be defined in the validation’s Sitecore item.

Build and deploy your code to your Sitecore installation. Enter Sitecore Content Editor, and navigate to /sitecore/System/Settings/Forms/Validations. Create a new Validation item using template /sitecore/templates/System/Forms/Validation and fill out the fields:

  • Type: “Oshyn.Forms.Samples.Validations.OtherPageConditionalValidation,Oshyn.Forms.Samples” (this is the fully qualified name of the validation class we created, change it to your specific namespaces and assembly name)
  • Message: “{0} must be less than {1}” (this follows C# string.Format() conventions, the first (0) parameter is the title of the field that we will attach this validation, and the second (1) parameter is the title of the other field that we will use to compare)
  • Parameters:{“otherFieldId”:”{other_field_id}”, “operator”:”<“}”. This is the same JSON we used in the previous blog post for FieldComparisonParameters, but obviously we need to copy and paste the ID of the “Other Number” field for the “otherFieldId” parameter:

    OtherNumber parameters in Sitecore

    For the “operator” value, we are going to use the less-than operator “<“.

At the end, your Validation item should look something like this:

Validation item definition in Sitecore


We must allow this validation to be attached to any “Number” field, so we can select it when we attach it to our field in the form. Navigate to /sitecore/System/Settings/Field Types/Basic/Number, and in the “Allowed Validations” field, add our new Less Than Validator to the list:

Screenshot of adding 'Less Than Validator' to Validation section


Now it’s time to attach the validation to the “Another Number” field. Open the Forms Editor, and select the “Another Number” field in the second page. On the right panel, open the “Validation” section, and select “Less Than Validator”. Click on Apply and save the form:

Screenshot of right panel of Forms Editor - 'Less Than Validator'


Publish all created/modified items (or do a full site Smart Publish). Open the form and enter any values on the first page:

Enter values on the first page of the form


Click on Next Page, and purposefully enter in “Another Number” a number greater than the one you entered in “Other Number”. When you try to Submit the form, you will get a validation error message just below the field:

Second page of the form


If you enter a value less than what you entered for Other Number, when you click Submit the form will be submitted and you will be shown the Thank You page.

WARNING: This solution only works when fields are located in different pages. It will not work in one-page forms, or when the conditional field is on the same page. This is because server side validations happen BEFORE the field values are saved into session, and this causes inconsistencies. If you need fully server-side validation for fields on the same page, consider including them in a custom Submit Action, with the disadvantage of displaying the validation errors not under the field, but in the form’s validation summary list that appears on top of the form.

Conclusion

Conditional validation is totally possible in Sitecore Forms. Although these examples shown in this series use simple number comparison, you can implement your own conditionals with other types of fields and any rules you require. The disadvantage is that you need to create separate Validation items for each case and set of fields. This also means that you need to train your CMS admins and editors on how to correctly use these validations, since it needs more setup than what is already included out of the box.