Validating the maximum length of a text field in Episerver Forms

This blog post will show you how to allow editors to specify a maximum length for text boxes (and text areas) in Episerver Forms and then utilize the maxlength attribute to restrict what users can enter. From an editor perspective this is very convenient and can be enabled like any other validator:

First things first, we need to create the validator. This should implement IPreviewableTextBox (in the EPiServer.Forms.UI NuGet package) which allows editors to enter a value for the validator (and is typically used for the existing Regular expression validator).

We also need to convert the value we get from that to an integer (in this example rawStringOfSettings), since we're getting the user entered value as a string.

When we're building our validation model (in the appropriately named BuildValidationModel method) we want to ensure we add our maxlength attribute to the model.

Let's also add some basic validation logic and we end up with something that looks like this:

public class MaxLengthValidator : ElementValidatorBase, IPreviewableTextBox
{
    public override int ValidationOrder => 400;

    private int _maxLength;

    public override void Initialize(string rawStringOfSettings)
    {
        base.Initialize(rawStringOfSettings);

        if (!int.TryParse(rawStringOfSettings, out _maxLength))
        {
            _maxLength = 0;
        }
    }

    public override bool? Validate(IElementValidatable targetElement)
    {
        var model = base.BuildValidationModel(targetElement);

        var submittedValue = targetElement.GetSubmittedValue() as string;

        if (string.IsNullOrEmpty(submittedValue))
        {
            return true;
        }

        return submittedValue.Length <= _maxLength;
    }

    /// <inheritdoc />
    public override IValidationModel BuildValidationModel(IElementValidatable targetElement)
    {
        var model = base.BuildValidationModel(targetElement);

        try
        {
            model.Message = string.Format(model.Message, _maxLength.ToString());
        }
        catch (Exception) {}

        if (model.AdditionalAttributes == null)
        {
            model.AdditionalAttributes = new Dictionary<string, string>();
        }

        model.AdditionalAttributes.AddOrSetValue("maxlength", _maxLength.ToString());

        return model;
    }
}

To keep things looking nice in edit mode let's add the follow to the translations XML:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<languages>
  <language name="English" id="en">
    <episerver>
      <forms>
        <validators>
          <your.namespace.maxlengthvalidator>
            <displayname>Max Length</displayname>
            <message>This field has a max length of {0} characters.</message>
          </your.namespace.maxlengthvalidator>
        </validators>
      </forms>
    </episerver>
  </language>
</languages>

Opposed to going to the hassle of creating a custom Episerver Forms element where we enable this validator ideally, we'd be able to attach it to all existing text and text area elements. Well, we're in luck: Allan Thraen has a blog post on this called “Episerver Forms: Adding custom validators to existing elements”. Using that example, we can add our validator with just a line of code per element:

var validationService = ServiceLocator.Current.GetInstance<IValidationService>() as ValidationServiceWithCustomValidators;
validationService.AddAdditionalValidator(typeof(TextboxElementBlock), typeof(MaxLengthValidator));
validationService.AddAdditionalValidator(typeof(TextareaElementBlock), typeof(MaxLengthValidator));

If you run the code at this point everything will pretty much work, except (and it's a big one) if you add an element and use the custom validator you'll get a JavaScript error when completing the form. This is because we haven't added any client-side validation yet, so the validation is failing.

This can be solved the (comparatively) hard way, or the easy way, so take your pick:

The (Comparatively) Hard Way

We can make this easier by using the existing support for regular expressions.

First, let's add some regex patterns to our MaxLengthValidator constructor:

public override void Initialize(string rawStringOfSettings)
{
    base.Initialize(rawStringOfSettings);

    if (!int.TryParse(rawStringOfSettings, out _maxLength))
    {
        _maxLength = 0;
    }

    _model = new RegularExpressionValidationModel {
        JsPattern = $"^.{{0,{_maxLength}}}$",
        DotNetPattern = $"^.{{0,{_maxLength}}}$"
    };
}

Now, we just need to add our validator to the list of client-side validators, conveniently there is a blog post for that: “Custom validators with Episerver Forms. Alternatively, if you want to make things harder still you can write your own JavaScript validator, which is also covered in the linked blog post.

The Easy Way

Since we're already extending the validation service, we can override the GetFormValidationInfo and remove our validators from the client-side. Technically, this means we're not validating in the front-end but, we are setting a maxlength on the input and validating in the back-end, so the result is basically the same.

public class ValidationServiceWithCustomValidators : ValidationService
{
    // Other overridden methods here!

    public override IEnumerable<ValidationDescriptor> GetFormValidationInfo(FormContainerBlock formContainer)
    {
        var infos = base.GetFormValidationInfo(formContainer).ToList();

        foreach (var info in infos)
        {
            info.Validators = info.Validators.Where(x => !x.Type.Equals(typeof(MaxLengthValidator).FullName, StringComparison.Ordinal));
        }

        return infos;
    }
}

On a slight sidenote, if you update the MaxLengthValidator to use the RegularExpressionValidationModel (see the hard way above) it's also possible to change the validator type here to use the RegularExpressionValidator and that also works and enables validation (at the expensive of feeling hacky):

public override IEnumerable<ValidationDescriptor> GetFormValidationInfo(FormContainerBlock formContainer)
{
    var infos = base.GetFormValidationInfo(formContainer).ToList();

    foreach (var info in infos)
    {
        foreach (var validator in info.Validators)
        {
            if (validator.Type.Equals(typeof(MaxLengthValidator).FullName, StringComparison.Ordinal))
            {
                validator.Type = typeof(RegularExpressionValidator).FullName;
            }
        }
    }

    return infos;
}

Regardless of which route you went; you should now have a working maximum length validator you can use on any (or all) text box or text area in Episerver Forms. Nice.

Comments

There are zero comments 😢