Search code examples
blazorblazor-server-sideblazor-webassembly

Blazor: Custom Validation based on another field


I am trying to build a custom validator in Blazor based on another field on the form. Requirement is to make Phone number mandatory when user checks Receive Text Messages checkbox. I googled a lot but was only able to find custom validator verifying empty or some hardcoded string. Is there a way I can validate a field based on another field's value in Blazor?

PhoneNumberValidator:

public class PhoneNumberValidaton : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null)
        {
            return new ValidationResult("Phone Number is mandatory if you want to receive text messages", new[] { validationContext.MemberName });
        }

        return null;
    }
}

Model:

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

    [Required]
    [Display(Name = "First Name")]
    public string FirstName { get; set; }

    [Required]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    [PhoneNumberValidaton]
    [Display(Name = "Phone Number")]
    public string Phone { get; set; }

    [Required]
    [Display(Name = "Receive Text Messages")]
    public bool CanReceiveText { get; set; }
}

Solution

  • Requirement is to make Phone number mandatory when user checks Receive Text Messages checkbox

    You can create a validation attribute to check for another property's value, if it matches the target value, then the property is required. For example if CanReceiveText value is true, then make the Phone property required.

    [RequiredIfAttribute("CanReceiveText", true)]
    [Display(Name = "Phone Number")]
    public string Phone { get; set; }
    
    [Required]
    [Display(Name = "Receive Text Messages")]
    public bool CanReceiveText { get; set; }
    
    public class RequiredIfAttribute : ValidationAttribute
    {
        readonly RequiredAttribute _innerAttribute = new RequiredAttribute();
        private string _dependentProperty { get; }
        private object _targetValue { get; }
    
        public RequiredIfAttribute(string dependentProperty, object targetValue)
        {
            _dependentProperty = dependentProperty;
            _targetValue = targetValue;
        }
    
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var field = validationContext.ObjectType.GetProperty(_dependentProperty);
            if (field != null)
            {
                var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
                if ((dependentValue == null && _targetValue == null) || dependentValue.Equals(_targetValue))
                {
                    if (!_innerAttribute.IsValid(value))
                    {
                        var name = validationContext.DisplayName;
                        var specificErrorMessage = ErrorMessage;
                        if (string.IsNullOrEmpty(specificErrorMessage))
                            specificErrorMessage = $"{name} is required.";
    
                        return new ValidationResult(specificErrorMessage, new[] { validationContext.MemberName });
                    }
                }
                return ValidationResult.Success;
            }
            return new ValidationResult(FormatErrorMessage(_dependentProperty));
        }
    }
    

    Online Demo:

    https://blazorfiddle.com/s/p5zfylbm

    Working Example:

    @page "/"
    @using System.ComponentModel.DataAnnotations
    
    <EditForm Model="@userModel" OnValidSubmit="@HandleValidSubmit">
        <DataAnnotationsValidator/>
        <ValidationSummary/>
    
        <p>
            <label for="FirstName">FirstName: </label>
            <InputText id="FirstName" @bind-Value="userModel.FirstName"/>
            <ValidationMessage For="() => userModel.FirstName"/>
        </p>
        <p>
            <label for="LastName">LastName: </label>
            <InputText id="LastName" @bind-Value="userModel.LastName"/>
            <ValidationMessage For="() => userModel.LastName"/>
        </p>
        <p>
            <label for="Phone">Phone Number: </label>
            <InputText id="Phone" @bind-Value="userModel.Phone"/>
            <ValidationMessage For="() => userModel.Phone"/>
        </p>
        <p>
            <label for="CanReceiveText">Receive Text Messages: </label>
            <InputCheckbox id="CanReceiveText" @bind-Value="userModel.CanReceiveText"/>
            <ValidationMessage For="() => userModel.CanReceiveText"/>
        </p>
    
        <button type="submit">Submit</button>
    </EditForm>
    
    @code {
    
        readonly UserModel userModel = new UserModel();
    
        private void HandleValidSubmit()
        {
        // Save the data
        }
    
        public class UserModel
        {
            public int Id { get; set; }
    
            [Required]
            [Display(Name = "First Name")]
            public string FirstName { get; set; }
    
            [Required]
            [Display(Name = "Last Name")]
            public string LastName { get; set; }
    
            [RequiredIfAttribute("CanReceiveText", true)]
            [Display(Name = "Phone Number")]
            public string Phone { get; set; }
    
            [Required]
            [Display(Name = "Receive Text Messages")]
            public bool CanReceiveText { get; set; }
        }
    
        public class RequiredIfAttribute : ValidationAttribute
        {
            readonly RequiredAttribute _innerAttribute = new RequiredAttribute();
            private string _dependentProperty { get; }
            private object _targetValue { get; }
    
            public RequiredIfAttribute(string dependentProperty, object targetValue)
            {
                _dependentProperty = dependentProperty;
                _targetValue = targetValue;
            }
    
            protected override ValidationResult IsValid(object value, ValidationContext validationContext)
            {
                var field = validationContext.ObjectType.GetProperty(_dependentProperty);
                if (field != null)
                {
                    var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
                    if ((dependentValue == null && _targetValue == null) || dependentValue.Equals(_targetValue))
                    {
                        if (!_innerAttribute.IsValid(value))
                        {
                            var name = validationContext.DisplayName;
                            var specificErrorMessage = ErrorMessage;
                            if (string.IsNullOrEmpty(specificErrorMessage))
                                specificErrorMessage = $"{name} is required.";
    
                            return new ValidationResult(specificErrorMessage, new[] { validationContext.MemberName });
                        }
                    }
                    return ValidationResult.Success;
                }
                return new ValidationResult(FormatErrorMessage(_dependentProperty));
            }
        }
    }
    

    Result: enter image description here