Skip to content

Commit 6ec129a

Browse files
committed
kb(validation): Add Custom Validator KB, enhance validation-related info (#2466)
* kb(validation): Add KB for custom conditional required validator * Explain built-in validation, polish KBs and links * Add note * Update knowledge-base/validation-custom-dataannotations-validator.md
1 parent d9a4386 commit 6ec129a

File tree

6 files changed

+241
-2
lines changed

6 files changed

+241
-2
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
#note-validation
22
>note The Telerik Blazor validation tools provide a way to display different types of validation messages. The main benefit is consistent styling with all other Telerik Blazor components. The validation tools do not expose API or settings for specific validation logic. You should configure the desired standard or custom validation separately, and then use our UI components to display messages to the user.
33
#end
4+
5+
#note-telerik-role-in-validation
6+
> The Telerik components for Blazor do not perform the actual validation of the model. Validation is managed by the [`EditContext`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.forms.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.
7+
#end

components/form/validation.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ When you provide an `EditContext` to the form, you can use its [`EnableDataAnnot
6262

6363
@[template](/_contentTemplates/common/form-validation.md#note-validation)
6464

65+
@[template](/_contentTemplates/common/form-validation.md#note-telerik-role-in-validation)
66+
6567
## Validation Message Type
6668

6769
With the `ValidationMessageType` parameter of the Telerik Form for Blazor you can customize the way the validation messages are presented to the user. This setting accepts a member of the `FormValidationMessageType` enum:
@@ -110,7 +112,7 @@ This section provides the following examples:
110112
* [Validate a Complex Model](#validate-a-complex-model)
111113
* [Fluent Validation](#fluent-validation)
112114

113-
It is also possible to [trigger Form validation programmatically]({%slug form-overview%}#form-reference-and-methods).
115+
It is also possible to [trigger Form validation programmatically]({%slug form-overview%}#form-reference-and-methods) or [use custom DataAnnotations validation]({%slug validation-kb-custom-dataannotations-validator%}).
114116

115117
### Validate a Model
116118

@@ -269,3 +271,8 @@ You can use third-party validation libraries that integrate with the standard `E
269271
}
270272
}
271273
````
274+
275+
## See Also
276+
277+
* [Custom Form `DataAnnotations` Validation]({%slug validation-kb-custom-dataannotations-validator%})
278+
* [Conditional Form Validation Options]({%slug form-kb-conditional-validation%})

components/grid/editing/validation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@ You can validate the Grid with any validator that uses the `EditContext`. To cha
315315
}
316316
`````
317317
318+
@[template](/_contentTemplates/common/form-validation.md#note-telerik-role-in-validation)
319+
318320
## See Also
319321
320322
* [Grid Editing]({%slug components/grid/editing/overview%})
323+
* [Custom Grid `DataAnnotations` Validation]({%slug validation-kb-custom-dataannotations-validator%})

components/validation/overview.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ You can seamlessly integrate the validation tools with the [Form Component]({%sl
118118
}
119119
````
120120

121+
@[template](/_contentTemplates/common/form-validation.md#note-telerik-role-in-validation)
122+
121123
# Next Steps
122124

123125
* Explore [TelerikValidationSummary]({%slug validation-tools-summary%})

knowledge-base/form-conditional-validation.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ The **TelerikForm** supports any [validator that is compatible with the Blazor E
4040
* Perform custom validation in the [Form's `OnSubmit` event]({%slug form-events%}#onsubmit).
4141
* Implement [remote (server-side) custom validation](https://github.com/telerik/blazor-ui/tree/master/form/remote-validation).
4242
* Use [`FormItem` Templates]({%slug form-formitems-template%}). Subscribe to the **change** handlers of the field editors to execute custom logic, show notifications, etc.
43-
* Implement a [conditional `DataAnnotation` attribute](https://stackoverflow.com/questions/26354853/conditionally-required-property-using-data-annotations). To see inline error messages next to the field editor, use the [`ValidationResult` overload that passes a **field name**](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validationresult.-ctor?view=net-6.0#system-componentmodel-dataannotations-validationresult-ctor(system-string-system-collections-generic-ienumerable((system-string)))). In this way the form validator will know which field has failed validation.
43+
* Implement a [custom conditional `DataAnnotations` attribute]({%slug validation-kb-custom-dataannotations-validator%}). To see inline error messages next to the field editor, return the [`ValidationResult` overload that accepts the invalid field name(s)](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validationresult). In this way the form validator will know which field has failed validation.
4444

4545
<div class="skip-repl"></div>
4646

@@ -49,6 +49,10 @@ The **TelerikForm** supports any [validator that is compatible with the Blazor E
4949
new List<string> { validationContext.MemberName });
5050
````
5151

52+
@[template](/_contentTemplates/common/form-validation.md#note-telerik-role-in-validation)
53+
5254
## See Also
5355

56+
* [Custom `DataAnnotations` Validation]({%slug validation-kb-custom-dataannotations-validator%})
57+
* [Fluent Validation]({%slug form-validation%}#fluent-validation)
5458
* [Live Demo: DateRangePicker Custom DataAnnotation Attribute](https://demos.telerik.com/blazor-ui/daterangepicker/validation)
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
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

Comments
 (0)