UI form component validation rules

Validation rules verify that the data users enter in a form meets the standards you specify. 

A validation rule contains an expression that evaluates the values of one or more form fields, and returns the validation result. A form cannot be submitted until user input passes all validation rules assigned to individual input fields.

Each validation rule also carries an error message that is displayed to users when their input fails to pass the given rule’s validation logic.

Default Xperience installations come with a set of validation rules ready to be used immediately. Additionally, the admin UI customization framework allows you to define custom rules suitable for your use cases and scenarios. 

Define validation rules

In Xperience, each validation rule consists of a main class that contains the rule’s validation logic – implemented in the rule’s Validate method – and two properties classes – one to enable client-side validation and the other to allow for the rule’s configuration (via the field editor or custom attributes).

Validation rules are strongly typed. A rule defined for decimalvalue types can only be applied to fields that use form components of the corresponding type. 

A validation rule consists of:

  1. Back end definition – used to define server-side validation logic and rule configuration. Located in the ASP.NET Core project.
  2. TypeScript component – used for client-side validation. Located in JavaScript modules that plug into the React admin app.

Validation rule architecture is summarized by the following diagram:

Validation rule architecture

Validation rule back end

Validation rules must inherit from the ValidationRule<TProperties, TClientProperties, TType> base class, where

  • TProperties – holds properties required for the functionality of the rule. The class must inherit from the ValidationRuleProperties base class. When creating the class, you must also implement the GetDescriptionText method. The method must return a string containing the description of the validation rule. This information is displayed when assigning validation rules to fields via the field editor.
    Validation rule description

    
    
     using Kentico.Xperience.Admin.Base.Forms;
    
     public class MyValidationRuleProperties : ValidationRuleProperties
     {
         // Properties used to configure the rule
    
         // Sets validation rule description
         public override string GetDescriptionText(ILocalizationService localizationService)
                 => $"Validates...";   
     }
    
     
  • TClientProperties – used to store and send validation rule metadata (validation status, error message) and properties to the client-side portion of the rule. Must inherit from the ValidationRuleClientProperties base class. Typically contains a subset of properties from TProperties suitable for use during client-side validation. If the validation rule doesn’t require any data on the client, this class can be omitted. Instead, make the validation rule main class inherit from ValidationRule<TProperties, TType>, which implicitly uses the ValidationRuleClientProperties base class.

    • This class serves purely as a data transfer object and should not contain additional logic.
    
    
     using Kentico.Xperience.Admin.Base.Forms;
    
     public class MyValidationRuleClientProperties : ValidationRuleClientProperties
     {
         // Properties sent to the client
     }
    
     
  • TType – the data type for which the rule is available (e.g., string, int, or custom types). For an overview of default data types registered in the system, see Data type management. For rules applicable to all types, use object.

    • Based on the specified type, the rule is offered when adding validation to fields of the corresponding data type via the field editor.
    • If you don’t need the rule available within the field editor, you can use any suitable data type as long as it matches the type of the annotated UI form component (identical to how form component registration functions).
    • Nullable types are not supported for validation rules.

When inheriting from the class, you need to implement the following abstract members:

  • ClientRuleName – property set to the name of the client TypeScript component used to provide client-side validation for the rule. You must provide a full name in the {orgName}/{projectName}/componentName format, as specified when you set up your custom admin JS module. Make sure the component is exported (visible to other modules). For example: @acme/web-admin-custom/ValidationRuleComponent
  • Validate – method that contains the rule’s validation logic. User input is stored in the method’s value parameter. Returns a ValidationResult object that contains the validation result and, optionally, a customized error message. The object’s constructor expects the following parameters:
    • isValid – a boolean value indicating the result of the validation.
    • errorMessage – optional error message that can be returned from the server. See Validation error messages for more information.
  • ConfigureClientProperties – method that configures the properties object sent to the client component.
    • Properties from the TProperties class are accessible via the Properties member. When ConfigureClientProperties is called, TProperties is populated with configured values.
Validation rule back end


using Kentico.Xperience.Admin.Base.Forms;

public class MyValidationRule : ValidationRule<MyValidationRuleProperties, MyValidationRuleClientProperties, TType>
{
    public override string ClientRuleName => "@kentico/xperience-admin-base/ValidationRuleComponent";

    protected override Task ConfigureClientProperties(TClientProperties clientProperties)
    {
        // Place to map properties sent to the client

        return base.ConfigureClientProperties(clientProperties);
    }

    public override Task<ValidationResult> Validate(TType value, IFormFieldValueProvider formFieldValueProvider)
    {
        // Validation logic
        bool result = ...
        // 'value' contains the input submitted by the user

        if (result)
        {
            return ValidationResult.SuccessResult();
        }

        string errorMessage = "Error message...";
        return ValidationResult.FailResult(errorMessage);
    }
}

Dependency injection support

Validation rules support constructor dependency injection by default. You can use dependency injection to resolve instances of external services on which your rule depends.

Using constructor dependency injection to resolve an instance of ILocalizationService


private readonly ILocalizationService localizationService;
...
public MyValidationRule(ILocalizationService localizationService)
{
    this.localizationService = localizationService;
}

During server-side validation, each validation rule assigned to a field is evaluated independently. Errors are collected and sent to the client, where they can be accessed under FormComponentProps.validationMessage.

Validation error messages

Validation rules provide several ways to specify error messages. When more than one is set, the system uses a priority system to determine which one is displayed. The priority order is as follows:  

  1. Error message specified when returning a ValidationResult from the Validate method. Prioritized above all others.

  2. Error message specified using the ErrorMessage property of the rule’s TProperties class (defined in the ValidationRuleProperties base class). Prioritized above the default error message.

  3. A default error message specified using the ValidationRule.DefaultErrorMessage property. Used when no other error message is set. The property can be overridden from the derived validation rule class and used to set a suitable default error message.

    Customizing the default error message
    
    
     protected override string DefaultErrorMessage => "My default error message";
    
     

Additionally, ValidationRuleProperties.ErrorMessage and ValidationRule.DefaultErrorMessage can use the ValidationRule.ErrorMessageFormatter property to format their output. The property stores a delegate that is used to format either ErrorMessage or DefaultErrorMessage before the string is sent to the client.



using Kentico.Xperience.Admin.Base.Forms;

public class MyValidationRule : ValidationRule<MyValidationRuleProperties, MyValidationRuleClientProperties, TType>
{ 
    protected override Func<string, string> ErrorMessageFormatter => (errorMessage) => String.Format(errorMessage, Properties.RuleConfiguration);   
}

This approach can be useful if you need to reflect rule configuration (dynamically changing values) in preset strings.

Error messages returned directly via the ValidationResult object are considered formatted and localized – the formatter is not used on them. 

Validation rule front end

The client part of a validation rule is a TypeScript implementation that ensures client-side validation.

Inherit from ValidationRule<TValidationRuleProps, TType>, where

  • TValidationRuleProps – a class that extends ValidationRuleProps. This class is the client-side mirror of the rule’s back end TClientProperties class and therefore needs to declare the same set of properties. If you aren’t sending additional properties to the client, the base ValidationRuleProps class can be used instead.
  • TType – the type of the input that is being validated (string, int, custom types).

and return an object with the following properties:

  • isValid: boolean – indicates the result of client-side validation.
  • errorMessage: string | null – the message to display in case validation failed.


import { ValidationRule, ValidationRuleProps } from '@kentico/xperience-admin-base';

export interface MyValidationRuleProps extends ValidationRuleProps {
    readonly prop1: string
    readonly prop2: number
} 

export const MyValidationRule: ValidationRule<MyValidationRuleProps, string> = (props, value) => {
    const validate = (prop1, prop2) => {
        let isValid = true;
        // Validation logic

        return isValid;
    };

    return { isValid: validate(props.prop1, props.prop2), errorMessage: props.errorMessage }
};

Register validation rules

To register a validation rule, use the RegisterFormValidationRule assembly attribute. This attribute ensures the validation rule is recognized by the system and available for use in the field editor. When registering a rule, specify the following parameters:

  • Identifier – a unique string identifier of the validation rule. We recommend using a unique prefix in your identifiers to prevent conflicts when deploying components to other projects. For example, use prefixes matching your company’s name.
  • ValidationRuleType – the System.Type of the validation rule class.
  • Name – used to set the name of the validation rule. Displayed when adding validation rules via the field editor.
  • (Optional) Description– sets the validation rule description. Displayed when adding validation rules via the field editor.

The following code snippet demonstrates validation rule registration:

Validation rule registration


[assembly: RegisterFormValidationRule("Acme.Administration.MyVisibilityCondition", typeof(MyVisibilityCondition), 
           "Rule name", "Rule description")]

The validation rule is now registered in the system. Users can include it when adding field validation via the field editor (e.g., when defining content types, forms, and other system objects).

Validation rules dependent on other inputs

Validation rules can also depend on other inputs in the form.

To construct such rules, the Validate method provides an instance of IFormFieldValueProvider, which enables access to the values of all preceding form field inputs. For example, if a form comprises inputs A, B, C, and validation is currently running on C, IFormFieldValueProvider can be used to access the values of A and B. Therefore, when configuring and assigning such rules, it’s important to keep in mind that they can only depend on inputs that precede the input to which they are assigned. Note that for editing components, the order of the inputs in the resulting forms can be adjusted via the Order property.

Values can be accessed via the following methods:

  • Get(string fieldName) – returns the value of the given field as object
  • Get<TType>(string fieldName) – returns the value of the given field as TType

where fieldName is the name of the dependent input, which must be provided when configuring the rule (via a configuration property).

Assign validation rules using attributes

In addition to field input validation, the system supports using validation rules for dynamically created input dialogs of Page Builder and Form Builder components and other admin UI components:

  • Page Builder
    • widget configuration and personalization
    • section configuration
    • page template configuration
  • Form Builder
    • form component configuration
    • form section configuration
  • Admin UI
    • form component configuration
    • validation rule configuration
    • visibility condition configuration

Within this approach, validation rules are assigned via attribute notation to the properties of model classes defining individual component configuration dialogs. For example:

Assigning the 'Required' validation rule to a property within a widget's configuration class


using Kentico.Xperience.Admin.Base.FormAnnotations;

using Kentico.PageBuilder.Web.Mvc;

public class CafeCardProperties : IWidgetProperties
{
    [PageSelectorComponent(Label = "Cafe", Order = 1)]
    // Binds the 'Required' validation rule to the property.
    // The rule ensures that the widget configuration dialog generated based 
    // on this class cannot be submitted until the field is populated.
    [RequiredValidationRule]
    public IEnumerable<PageRelatedItem> SelectedCafes { get; set; } = new List<PageRelatedItem>();
}

For this purpose, each validation rule can have a corresponding attribute that, when used to annotate a property of one of the supported components, binds the validation rule to that property’s input. The input dialog generated based on that model class cannot be submitted until user input passes all assigned validation rules.

A single property can have multiple validation attributes assigned. However, be mindful not to create contradictory rulesets that would make submission impossible.

Validation rule attribute classes must inherit from ValidationRuleAttribute.

Declare all mandatory and optional properties of the corresponding validation rule as properties of the attribute class. The properties must be named identically to their validation rule counterparts. The system uses the property names to pass and instantiate the values of the corresponding rule. Properties that are required for the rule to function can be forced via the attribute’s constructor.

Sample validation rule attribute


using Kentico.Xperience.Admin.Base.Forms;

public class ValueIsBetweenValidationRuleAttribute : ValidationRuleAttribute
{   
    // Property names must match the properties of the corresponding rule
    public int Min
    {
        get; 
    }

    public int Max
    {
        get; 
    }

    public ValueIsBetweenValidationRuleAttribute(int min, int max)
    {
        Min = min;
        Max = max;
    }
}

ValidationRuleAttribute also contains an ErrorMessage property that, when populated, overrides the error message provided by the corresponding validation rule.  

To indicate the relation between the attribute class and the corresponding validation rule, decorate the validation rule class with the ValidationRuleAttribute attribute.

Assigns ValueIsBetweenValidationRuleAttribute to ValueIsBetweenValidationRule


using Kentico.Xperience.Admin.Base.FormAnnotations;

[ValidationRuleAttribute(typeof(ValueIsBetweenValidationRuleAttribute))]
public class ValueIsBetweenValidationRule : ValidationRule<...>

For the example above, every property decorated with ValueIsBetweenValidationRuleAttribute is assigned a ValueIsBetweenValidationRule

Default validation rules

The following table gives an overview of validation rules provided with default Xperience installations. All validation rule attributes are located in the Kentico.Xperience.Admin.Base.FormAnnotations namespace.

Name

Applicable type

Attribute

Description

Field comparison

int
decimal
double
string

IntegerFieldComparisonValidationRule
DecimalFieldComparisonValidationRule
DoubleFieldComparisonValidationRule
StringFieldComparisonValidationRule

Compares the input’s value with another field of the same data type.

Email format

string

EmailValidationRule

Checks that the input is an email address. Can be configured to allow multi-email input separated by a semicolon.

Identifier

string

IdentifierValidationRule

Checks that the input contains a valid object identifier. Identifiers must begin with a letter or an underscore and can contain only underscores and alphanumeric characters.

Maximum length

string

MaxLengthValidationRule

Limits user input to the specified maximum length.

Minimum length

string

MinLengthValidationRule

Sets the minimum number of characters a field must contain before it can be submitted.

Maximum value

int
decimal
double

MaximumIntegerValueValidationRule
MaximumDecimalValueValidationRule
MaximumDoubleValueValidationRule

Prevents users from entering numbers larger than specified.

Minimum value

int
decimal
double

MinimumIntegerValueValidationRule
MinimumDecimalValueValidationRule
MinimumDoubleValueValidationRule

Prevents users from entering numbers smaller than specified.

Url format

string

UrlValidationRule

Checks if the input is a valid absolute URL. Additional properties of the attribute can be enabled to also allow the following types of URLs:

  • relative URLs starting with ~/
  • URL fragments starting with #
  • query string values starting with ?

Required value

object (all data types)

RequiredValidationRule

Prevents from submitting empty fields.

Example

The following example demonstrates a validation rule that checks if a number belongs to a specified interval. The example implements the rule for form components of the int type:



using Kentico.Xperience.Admin.Base.Forms;

[assembly: RegisterFormValidationRule(ValueIsBetweenValidationRule.IDENTIFIER,
                                  typeof(ValueIsBetweenValidationRule),
                                  "Entered number is between X and Y",
                                  "Checks that the entered number belongs to a specified closed interval.")]

public class ValueIsBetweenValidationRule : ValidationRule<ValueIsBetweenValidationRuleProperties, ValueIsBetweenValidationRuleClientProperties, int>
{
    internal const string IDENTIFIER = "Acme.Customizations.ValidationRules.ValueIsBetween";

    // Replace 'acme/web-admin-custom' with the name of your organization and project
    public override string ClientRuleName => "@acme/web-admin-custom/ValueIsBetweenValidationRule";

    public override Task<ValidationResult> Validate(int value, IFormFieldValueProvider formFieldValueProvider)
    {
        if (value <= Properties.Max && value >= Properties.Min)
        {
            return ValidationResult.SuccessResult();
        }

        return ValidationResult.FailResult($"Enter a value between {Properties.Min} and {Properties.Max}.");
    }

    // Sends properties to the client for use in client-side validation
    protected override Task ConfigureClientProperties(ValueIsBetweenValidationRuleClientProperties clientProperties)
    {
        clientProperties.Min = Properties.Min;
        clientProperties.Max = Properties.Max;

        return base.ConfigureClientProperties(clientProperties);     
    }
}

The rule’s properties class needs to hold two values – the lower and upper boundary of the range from which to accept user input:



using Kentico.Xperience.Admin.Base.Forms;
using Kentico.Xperience.Admin.Base.FormAnnotations;

public class ValueIsBetweenValidationRuleProperties : ValidationRuleProperties
{
    [NumberInputComponent(Label = "Minimum")]
    public int Min { get; set; }

    [NumberInputComponent(Label = "Maximum")]
    public int Max { get; set; }

    // Overriding the 'ErrorMessage' property and not assigning an editing component hides it from the configuration dialog.
    // This is desirable since the rule returns an error message via the 'Validate' method -- 
    // anything entered under 'ErrorMessage' has lower priority and is ignored by the system.
    public override string ErrorMessage { get => base.ErrorMessage; set => base.ErrorMessage = value; } 

    public override string GetDescriptionText(ILocalizationService localizationService) => $"Entered value is between Minimum and Maximum.";
}

For the rule’s client properties class, the client needs to know the same information as the back end to correctly evaluate the input. For this reason, the properties in this class need to mirror those of the back end configuration.

ValueIsBetween client properties class


public class ValueIsBetweenValidationRuleClientProperties : ValidationRuleClientProperties {
    public int Min { get; set; }

    public int Max { get; set; }
} 

And the TypeScript code to enable client-side validation:



import { ValidationRule, ValidationRuleProps } from '@kentico/xperience-admin-base';

interface ValueIsBetweenValidationRuleProps extends ValidationRuleProps {
    readonly max: number
    readonly min: number
} 

export const ValueIsBetweenValidationRule: ValidationRule<ValueIsBetweenValidationRuleProps, number> = (props, value) => {
    const validate = (max: number, min: number) => {     
        return min <= value && value <= max;
    };

    return { isValid: validate(props.max, props.min), errorMessage: props.errorMessage }
};

Finally, the corresponding attribute to enable usage in component model classes:



using Kentico.Xperience.Admin.Base.Forms;

public class ValueIsBetweenValidationRuleAttribute : ValidationRuleAttribute
{
    public int Max
    {
        get; 
    }

    public int Min
    {
        get; 
    } 

    public ValueIsBetweenValidationRuleAttribute(int max, int min)
    {
        Max = max;
        Min = min;
    }
}

With the attribute created, add ValidationRuleAttribute with the attribute type as the parameter to the validation rule’s main class to tie the attribute and validation rule together:



using Kentico.Xperience.Admin.Base.FormAnnotations;

[ValidationRuleAttribute(typeof(ValueIsBetweenValidationRuleAttribute))]
public class ValueIsBetweenValidationRule : ValidationRule<CustomValidationRuleProperties, CustomValidationRuleClientProperties, int>

The validation rule is now available when adding validation to fields and model class properties with the int data type.