|
| 1 | +--- |
| 2 | +title: Use Custom DataAnnotations Validator |
| 3 | +description: Learn how to implement and integrate custom DataAnnotations validation with Telerik Blazor components such as Form, Grid, ValidationMessage, ValidationTooltip, and others. |
| 4 | +type: how-to |
| 5 | +page_title: How to Use Custom DataAnnotations Validator with Telerik UI for Blazor |
| 6 | +slug: validation-kb-custom-dataannotations-validator |
| 7 | +tags: telerik, blazor, validation, form, grid |
| 8 | +ticketid: 1666005, 1665269, 1658101, 1560189, 1558247, 1543336 |
| 9 | +res_type: kb |
| 10 | +--- |
| 11 | + |
| 12 | +## Environment |
| 13 | + |
| 14 | +<table> |
| 15 | + <tbody> |
| 16 | + <tr> |
| 17 | + <td>Product</td> |
| 18 | + <td> |
| 19 | + UI for Blazor, <br /> |
| 20 | + Grid for Blazor, <br /> |
| 21 | + Form for Blazor, <br /> |
| 22 | + ValidationMessage for Blazor, <br /> |
| 23 | + ValidationSummary for Blazor, <br /> |
| 24 | + ValidationTooltip for Blazor |
| 25 | + </td> |
| 26 | + </tr> |
| 27 | + </tbody> |
| 28 | +</table> |
| 29 | + |
| 30 | +## Description |
| 31 | + |
| 32 | +This KB answers the following questions: |
| 33 | + |
| 34 | +* How to use conditional required validation with Telerik UI for Blazor components? |
| 35 | +* How to make a Form field required, depending on the value of another field? |
| 36 | +* How to implement a conditional `DataAnnotations` validator and integrate it with the Telerik Blazor Form or Grid? |
| 37 | +* How to display inline validation messages or validation tooltips when using a custom validator? |
| 38 | + |
| 39 | +## Solution |
| 40 | + |
| 41 | +1. Implement a class that inherits from [`ValidationAttribute`](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validationattribute). |
| 42 | +1. Override the [`IsValid()` method overload](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validationattribute.isvalid), which accepts a [`ValidationContext`](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validationcontext) and returns a [`ValidationResult`](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validationresult). |
| 43 | +1. 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. |
| 44 | +1. (optional) Override the `FormatErrorMessage` method to provide a custom validation message. |
| 45 | + |
| 46 | +> 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 <a href="https://stackoverflow.com/questions/26354853/conditionally-required-property-using-data-annotations" target="_blank">depends on the specific requirements and can vary</a>. |
| 47 | +
|
| 48 | +>caption Use custom conditional required DataAnnotations validator with Telerik components for Blazor |
| 49 | +
|
| 50 | +````CSHTML |
| 51 | +@using System.ComponentModel.DataAnnotations |
| 52 | +
|
| 53 | +@using System.Reflection |
| 54 | +
|
| 55 | +<h1>Conditional Validation</h1> |
| 56 | +
|
| 57 | +<p><code>ShippingAddress</code> is required when <code>UseBillingAddressForShipping</code> is <code>false</code>.</p> |
| 58 | +
|
| 59 | +<h2>Form</h2> |
| 60 | +
|
| 61 | +<TelerikForm Model="@FormModel"> |
| 62 | + <FormValidation> |
| 63 | + <DataAnnotationsValidator></DataAnnotationsValidator> |
| 64 | + <TelerikValidationSummary /> |
| 65 | + </FormValidation> |
| 66 | + <FormItems> |
| 67 | + <FormItem Field="@nameof(OrderDelivery.BillingAddress)" LabelText="Billing Address"></FormItem> |
| 68 | + <FormItem Field="@nameof(OrderDelivery.UseBillingAddressForShipping)" LabelText="Use Billing Address for Shipping"></FormItem> |
| 69 | + <FormItem Field="@nameof(OrderDelivery.ShippingAddress)" LabelText="Shipping Address"></FormItem> |
| 70 | + </FormItems> |
| 71 | +</TelerikForm> |
| 72 | +
|
| 73 | +<h2>Grid</h2> |
| 74 | +
|
| 75 | +<TelerikGrid Data="@GridData" |
| 76 | + EditMode="@GridEditMode.Inline" |
| 77 | + OnUpdate="@OnGridUpdate" |
| 78 | + OnCreate="@OnGridCreate"> |
| 79 | + <GridToolBarTemplate> |
| 80 | + <GridCommandButton Command="Add">Add Item</GridCommandButton> |
| 81 | + </GridToolBarTemplate> |
| 82 | + <GridColumns> |
| 83 | + <GridColumn Field="@nameof(OrderDelivery.BillingAddress)" Title="Billing Address" /> |
| 84 | + <GridColumn Field="@nameof(OrderDelivery.UseBillingAddressForShipping)" Title="Use Billing Address for Shipping" /> |
| 85 | + <GridColumn Field="@nameof(OrderDelivery.ShippingAddress)" Title="Shipping Address" /> |
| 86 | + <GridCommandColumn> |
| 87 | + <GridCommandButton Command="Edit">Edit</GridCommandButton> |
| 88 | + <GridCommandButton Command="Save" ShowInEdit="true">Save</GridCommandButton> |
| 89 | + <GridCommandButton Command="Cancel" ShowInEdit="true">Cancel</GridCommandButton> |
| 90 | + </GridCommandColumn> |
| 91 | + </GridColumns> |
| 92 | +</TelerikGrid> |
| 93 | +
|
| 94 | +<style> |
| 95 | + h1 { |
| 96 | + font-size: 1.5rem; |
| 97 | + } |
| 98 | +
|
| 99 | + h2 { |
| 100 | + font-size: 1.2rem; |
| 101 | + } |
| 102 | +</style> |
| 103 | +
|
| 104 | +@code { |
| 105 | + private OrderDelivery FormModel { get; set; } = new() { Id = 1 }; |
| 106 | +
|
| 107 | + #region Grid |
| 108 | +
|
| 109 | + private List<OrderDelivery> GridData { get; set; } = new(); |
| 110 | +
|
| 111 | + private int LastId { get; set; } |
| 112 | +
|
| 113 | + private void OnGridCreate(GridCommandEventArgs args) |
| 114 | + { |
| 115 | + var createdItem = (OrderDelivery)args.Item; |
| 116 | +
|
| 117 | + createdItem.Id = ++LastId; |
| 118 | +
|
| 119 | + GridData.Insert(0, createdItem); |
| 120 | + } |
| 121 | +
|
| 122 | + private void OnGridUpdate(GridCommandEventArgs args) |
| 123 | + { |
| 124 | + var updatedItem = (OrderDelivery)args.Item; |
| 125 | + var originalItemIndex = GridData.FindIndex(i => i.Id == updatedItem.Id); |
| 126 | +
|
| 127 | + if (originalItemIndex != -1) |
| 128 | + { |
| 129 | + GridData[originalItemIndex] = updatedItem; |
| 130 | + } |
| 131 | + } |
| 132 | +
|
| 133 | + #endregion Grid |
| 134 | +
|
| 135 | + #region Model |
| 136 | +
|
| 137 | + public class OrderDelivery |
| 138 | + { |
| 139 | + public int Id { get; set; } |
| 140 | +
|
| 141 | + [Required] |
| 142 | + [Display(Name = "Billing Address")] |
| 143 | + public string BillingAddress { get; set; } = string.Empty; |
| 144 | +
|
| 145 | + [Display(Name = "Use Billing Address For Shipping")] |
| 146 | + public bool UseBillingAddressForShipping { get; set; } |
| 147 | +
|
| 148 | + [ConditionalRequired(nameof(OrderDelivery.UseBillingAddressForShipping), false)] |
| 149 | + [Display(Name = "Shipping Address")] |
| 150 | + public string ShippingAddress { get; set; } = string.Empty; |
| 151 | + } |
| 152 | +
|
| 153 | + #endregion Model |
| 154 | +
|
| 155 | + #region Custom Validator |
| 156 | +
|
| 157 | + public class ConditionalRequired : ValidationAttribute |
| 158 | + { |
| 159 | + private string DependentPropertyName { get; set; } |
| 160 | + private string DependentPropertyDisplayName { get; set; } = string.Empty; |
| 161 | + private object? DependentPropertyExpectedValue { get; set; } |
| 162 | + private object? DependentPropertyValue { get; set; } |
| 163 | +
|
| 164 | + public override bool RequiresValidationContext |
| 165 | + { |
| 166 | + get { return true; } |
| 167 | + } |
| 168 | +
|
| 169 | + public ConditionalRequired(string dependentPropertyName, object dependentPropertyExpectedValue) |
| 170 | + : base("The {0} field is required when {1} is equal to {2}.") |
| 171 | + { |
| 172 | + DependentPropertyName = dependentPropertyName; |
| 173 | + DependentPropertyExpectedValue = dependentPropertyExpectedValue; |
| 174 | + } |
| 175 | +
|
| 176 | + public override string FormatErrorMessage(string requiredPropertyName) |
| 177 | + { |
| 178 | + return string.Format( |
| 179 | + System.Globalization.CultureInfo.CurrentCulture, |
| 180 | + base.ErrorMessageString, |
| 181 | + requiredPropertyName, |
| 182 | + DependentPropertyDisplayName, |
| 183 | + DependentPropertyValue); |
| 184 | + } |
| 185 | +
|
| 186 | + protected override ValidationResult IsValid(object? validatedValue, ValidationContext validationContext) |
| 187 | + { |
| 188 | + if (validationContext == null) |
| 189 | + { |
| 190 | + throw new ArgumentNullException("validationContext"); |
| 191 | + } |
| 192 | +
|
| 193 | + PropertyInfo? dependentProperty = validationContext.ObjectType.GetProperty(DependentPropertyName); |
| 194 | + DependentPropertyValue = dependentProperty?.GetValue(validationContext.ObjectInstance); |
| 195 | + DependentPropertyDisplayName = dependentProperty?.GetCustomAttribute<DisplayAttribute>()?.Name ?? DependentPropertyName; |
| 196 | +
|
| 197 | + if ((DependentPropertyValue == null && DependentPropertyExpectedValue == null) |
| 198 | + || (DependentPropertyValue != null && DependentPropertyValue.Equals(DependentPropertyExpectedValue)) |
| 199 | + && string.IsNullOrEmpty(validatedValue?.ToString())) |
| 200 | + { |
| 201 | + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new List<string> { validationContext.DisplayName }); |
| 202 | + } |
| 203 | +
|
| 204 | + return ValidationResult.Success!; |
| 205 | + } |
| 206 | + } |
| 207 | +
|
| 208 | + #endregion Custom Validator |
| 209 | +} |
| 210 | +```` |
| 211 | + |
| 212 | +@[template](/_contentTemplates/common/form-validation.md#note-telerik-role-in-validation) |
| 213 | + |
| 214 | +## See Also |
| 215 | + |
| 216 | +* [Conditional Form Validation Options]({%slug form-kb-conditional-validation%}) |
| 217 | +* [Form Validation]({%slug form-validation%}) |
| 218 | +* [Grid Validation]({%slug grid-editing-validation%}) |
| 219 | +* [Validation Tools Overview]({%slug validation-tools-overview%}) |
0 commit comments