New to Telerik UI for Blazor? Download free 30-day trial

Use Custom DataAnnotations Validator

Environment

Product UI for Blazor,
Grid for Blazor,
Form for Blazor,
ValidationMessage for Blazor,
ValidationSummary for Blazor,
ValidationTooltip for Blazor

Description

This KB answers the following questions:

  • How to use conditional required validation with Telerik UI for Blazor components?
  • How to make a Form field required, depending on the value of another field?
  • How to implement a conditional DataAnnotations validator and integrate it with the Telerik Blazor Form or Grid?
  • How to display inline validation messages or validation tooltips when using a custom validator?

Solution

  1. Implement a class that inherits from ValidationAttribute.
  2. Override the IsValid() method overload, which accepts a ValidationContext and returns a ValidationResult.
  3. Return a ValidationResult that includes the failing field name(s) as a second argument of type IEnumerable<string>. This step is crucial in order to apply invalid state to the respective input component and display an inline validation message next to it.
  4. (optional) Override the FormatErrorMessage method to provide a custom validation message.

Creating a custom DataAnnotations validator does not involve Telerik APIs and is outside the Telerik support scope. The following implementation is just an example that shows that Telerik Blazor components can work with a custom validator. The exact validator implementation depends on the specific requirements and can vary.

Use custom conditional required DataAnnotations validator with Telerik components for Blazor

@using System.ComponentModel.DataAnnotations

@using System.Reflection

<h1>Conditional Validation</h1>

<p><code>ShippingAddress</code> is required when <code>UseBillingAddressForShipping</code> is <code>false</code>.</p>

<h2>Form</h2>

<TelerikForm Model="@FormModel">
    <FormValidation>
        <DataAnnotationsValidator></DataAnnotationsValidator>
        <TelerikValidationSummary />
    </FormValidation>
    <FormItems>
        <FormItem Field="@nameof(OrderDelivery.BillingAddress)" LabelText="Billing Address"></FormItem>
        <FormItem Field="@nameof(OrderDelivery.UseBillingAddressForShipping)" LabelText="Use Billing Address for Shipping"></FormItem>
        <FormItem Field="@nameof(OrderDelivery.ShippingAddress)" LabelText="Shipping Address"></FormItem>
    </FormItems>
</TelerikForm>

<h2>Grid</h2>

<TelerikGrid Data="@GridData"
             EditMode="@GridEditMode.Inline"
             OnUpdate="@OnGridUpdate"
             OnCreate="@OnGridCreate">
    <GridToolBarTemplate>
        <GridCommandButton Command="Add">Add Item</GridCommandButton>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field="@nameof(OrderDelivery.BillingAddress)" Title="Billing Address" />
        <GridColumn Field="@nameof(OrderDelivery.UseBillingAddressForShipping)" Title="Use Billing Address for Shipping" />
        <GridColumn Field="@nameof(OrderDelivery.ShippingAddress)" Title="Shipping Address" />
        <GridCommandColumn>
            <GridCommandButton Command="Edit">Edit</GridCommandButton>
            <GridCommandButton Command="Save" ShowInEdit="true">Save</GridCommandButton>
            <GridCommandButton Command="Cancel" ShowInEdit="true">Cancel</GridCommandButton>
        </GridCommandColumn>
    </GridColumns>
</TelerikGrid>

<style>
    h1 {
        font-size: 1.5rem;
    }

    h2 {
        font-size: 1.2rem;
    }
</style>

@code {
    private OrderDelivery FormModel { get; set; } = new() { Id = 1 };

    #region Grid

    private List<OrderDelivery> GridData { get; set; } = new();

    private int LastId { get; set; }

    private void OnGridCreate(GridCommandEventArgs args)
    {
        var createdItem = (OrderDelivery)args.Item;

        createdItem.Id = ++LastId;

        GridData.Insert(0, createdItem);
    }

    private void OnGridUpdate(GridCommandEventArgs args)
    {
        var updatedItem = (OrderDelivery)args.Item;
        var originalItemIndex = GridData.FindIndex(i => i.Id == updatedItem.Id);

        if (originalItemIndex != -1)
        {
            GridData[originalItemIndex] = updatedItem;
        }
    }

    #endregion Grid

    #region Model

    public class OrderDelivery
    {
        public int Id { get; set; }

        [Required]
        [Display(Name = "Billing Address")]
        public string BillingAddress { get; set; } = string.Empty;

        [Display(Name = "Use Billing Address For Shipping")]
        public bool UseBillingAddressForShipping { get; set; }

        [ConditionalRequired(nameof(OrderDelivery.UseBillingAddressForShipping), false)]
        [Display(Name = "Shipping Address")]
        public string ShippingAddress { get; set; } = string.Empty;
    }

    #endregion Model

    #region Custom Validator

    public class ConditionalRequired : ValidationAttribute
    {
        private string DependentPropertyName { get; set; }
        private string DependentPropertyDisplayName { get; set; } = string.Empty;
        private object? DependentPropertyExpectedValue { get; set; }
        private object? DependentPropertyValue { get; set; }

        public override bool RequiresValidationContext
        {
            get { return true; }
        }

        public ConditionalRequired(string dependentPropertyName, object dependentPropertyExpectedValue)
        : base("The {0} field is required when {1} is equal to {2}.")
        {
            DependentPropertyName = dependentPropertyName;
            DependentPropertyExpectedValue = dependentPropertyExpectedValue;
        }

        public override string FormatErrorMessage(string requiredPropertyName)
        {
            return string.Format(
                System.Globalization.CultureInfo.CurrentCulture,
                base.ErrorMessageString,
                requiredPropertyName,
                DependentPropertyDisplayName,
                DependentPropertyValue);
        }

        protected override ValidationResult IsValid(object? validatedValue, ValidationContext validationContext)
        {
            if (validationContext == null)
            {
                throw new ArgumentNullException("validationContext");
            }

            PropertyInfo? dependentProperty = validationContext.ObjectType.GetProperty(DependentPropertyName);
            DependentPropertyValue = dependentProperty?.GetValue(validationContext.ObjectInstance);
            DependentPropertyDisplayName = dependentProperty?.GetCustomAttribute<DisplayAttribute>()?.Name ?? DependentPropertyName;

            if ((DependentPropertyValue == null && DependentPropertyExpectedValue == null)
                || (DependentPropertyValue != null && DependentPropertyValue.Equals(DependentPropertyExpectedValue))
                && string.IsNullOrEmpty(validatedValue?.ToString()))
            {
                return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new List<string> { validationContext.DisplayName });
            }

            return ValidationResult.Success!;
        }
    }

    #endregion Custom Validator
}

The Telerik components for Blazor do not perform the actual validation of the model. Validation is managed by the EditContext. The role of the Telerik components is to call EditContext methods, subscribe to EditContext events, retrieve validation messages, and display them. If a validation scenario does not work as expected, check the behavior in a standard Blazor <EditForm> to verify if the issue is related to the Telerik components.

See Also

In this article