@@ -16,98 +16,118 @@ namespace Microsoft.AspNetCore.Components.Forms
1616 /// </summary>
1717 public static class EditContextDataAnnotationsExtensions
1818 {
19- private static ConcurrentDictionary < ( Type ModelType , string FieldName ) , PropertyInfo ? > _propertyInfoCache
20- = new ConcurrentDictionary < ( Type , string ) , PropertyInfo ? > ( ) ;
21-
2219 /// <summary>
2320 /// Adds DataAnnotations validation support to the <see cref="EditContext"/>.
2421 /// </summary>
2522 /// <param name="editContext">The <see cref="EditContext"/>.</param>
23+ [ Obsolete ( "Use " + nameof ( EnableDataAnnotationsValidation ) + " instead." ) ]
2624 public static EditContext AddDataAnnotationsValidation ( this EditContext editContext )
2725 {
28- if ( editContext == null )
29- {
30- throw new ArgumentNullException ( nameof ( editContext ) ) ;
31- }
32-
33- var messages = new ValidationMessageStore ( editContext ) ;
34-
35- // Perform object-level validation on request
36- editContext . OnValidationRequested +=
37- ( sender , eventArgs ) => ValidateModel ( ( EditContext ) sender ! , messages ) ;
38-
39- // Perform per-field validation on each field edit
40- editContext . OnFieldChanged +=
41- ( sender , eventArgs ) => ValidateField ( editContext , messages , eventArgs . FieldIdentifier ) ;
42-
26+ EnableDataAnnotationsValidation ( editContext ) ;
4327 return editContext ;
4428 }
4529
46- private static void ValidateModel ( EditContext editContext , ValidationMessageStore messages )
30+ /// <summary>
31+ /// Enables DataAnnotations validation support for the <see cref="EditContext"/>.
32+ /// </summary>
33+ /// <param name="editContext">The <see cref="EditContext"/>.</param>
34+ /// <returns>A disposable object whose disposal will remove DataAnnotations validation support from the <see cref="EditContext"/>.</returns>
35+ public static IDisposable EnableDataAnnotationsValidation ( this EditContext editContext )
36+ {
37+ return new DataAnnotationsEventSubscriptions ( editContext ) ;
38+ }
39+
40+ private sealed class DataAnnotationsEventSubscriptions : IDisposable
4741 {
48- var validationContext = new ValidationContext ( editContext . Model ) ;
49- var validationResults = new List < ValidationResult > ( ) ;
50- Validator . TryValidateObject ( editContext . Model , validationContext , validationResults , true ) ;
42+ private static readonly ConcurrentDictionary < ( Type ModelType , string FieldName ) , PropertyInfo ? > _propertyInfoCache = new ( ) ;
5143
52- // Transfer results to the ValidationMessageStore
53- messages . Clear ( ) ;
54- foreach ( var validationResult in validationResults )
44+ private readonly EditContext _editContext ;
45+ private readonly ValidationMessageStore _messages ;
46+
47+ public DataAnnotationsEventSubscriptions ( EditContext editContext )
5548 {
56- if ( validationResult == null )
57- {
58- continue ;
59- }
49+ _editContext = editContext ?? throw new ArgumentNullException ( nameof ( editContext ) ) ;
50+ _messages = new ValidationMessageStore ( _editContext ) ;
6051
61- if ( ! validationResult . MemberNames . Any ( ) )
62- {
63- messages . Add ( new FieldIdentifier ( editContext . Model , fieldName : string . Empty ) , validationResult . ErrorMessage ! ) ;
64- continue ;
65- }
52+ _editContext . OnFieldChanged += OnFieldChanged ;
53+ _editContext . OnValidationRequested += OnValidationRequested ;
54+ }
6655
67- foreach ( var memberName in validationResult . MemberNames )
56+ private void OnFieldChanged ( object ? sender , FieldChangedEventArgs eventArgs )
57+ {
58+ var fieldIdentifier = eventArgs . FieldIdentifier ;
59+ if ( TryGetValidatableProperty ( fieldIdentifier , out var propertyInfo ) )
6860 {
69- messages . Add ( editContext . Field ( memberName ) , validationResult . ErrorMessage ! ) ;
61+ var propertyValue = propertyInfo . GetValue ( fieldIdentifier . Model ) ;
62+ var validationContext = new ValidationContext ( fieldIdentifier . Model )
63+ {
64+ MemberName = propertyInfo . Name
65+ } ;
66+ var results = new List < ValidationResult > ( ) ;
67+
68+ Validator . TryValidateProperty ( propertyValue , validationContext , results ) ;
69+ _messages . Clear ( fieldIdentifier ) ;
70+ _messages . Add ( fieldIdentifier , results . Select ( result => result . ErrorMessage ! ) ) ;
71+
72+ // We have to notify even if there were no messages before and are still no messages now,
73+ // because the "state" that changed might be the completion of some async validation task
74+ _editContext . NotifyValidationStateChanged ( ) ;
7075 }
7176 }
7277
73- editContext . NotifyValidationStateChanged ( ) ;
74- }
75-
76- private static void ValidateField ( EditContext editContext , ValidationMessageStore messages , in FieldIdentifier fieldIdentifier )
77- {
78- if ( TryGetValidatableProperty ( fieldIdentifier , out var propertyInfo ) )
78+ private void OnValidationRequested ( object ? sender , ValidationRequestedEventArgs e )
7979 {
80- var propertyValue = propertyInfo . GetValue ( fieldIdentifier . Model ) ;
81- var validationContext = new ValidationContext ( fieldIdentifier . Model )
80+ var validationContext = new ValidationContext ( _editContext . Model ) ;
81+ var validationResults = new List < ValidationResult > ( ) ;
82+ Validator . TryValidateObject ( _editContext . Model , validationContext , validationResults , true ) ;
83+
84+ // Transfer results to the ValidationMessageStore
85+ _messages . Clear ( ) ;
86+ foreach ( var validationResult in validationResults )
8287 {
83- MemberName = propertyInfo . Name
84- } ;
85- var results = new List < ValidationResult > ( ) ;
88+ if ( validationResult == null )
89+ {
90+ continue ;
91+ }
92+
93+ if ( ! validationResult . MemberNames . Any ( ) )
94+ {
95+ _messages . Add ( new FieldIdentifier ( _editContext . Model , fieldName : string . Empty ) , validationResult . ErrorMessage ! ) ;
96+ continue ;
97+ }
98+
99+ foreach ( var memberName in validationResult . MemberNames )
100+ {
101+ _messages . Add ( _editContext . Field ( memberName ) , validationResult . ErrorMessage ! ) ;
102+ }
103+ }
86104
87- Validator . TryValidateProperty ( propertyValue , validationContext , results ) ;
88- messages . Clear ( fieldIdentifier ) ;
89- messages . Add ( fieldIdentifier , results . Select ( result => result . ErrorMessage ! ) ) ;
105+ _editContext . NotifyValidationStateChanged ( ) ;
106+ }
90107
91- // We have to notify even if there were no messages before and are still no messages now,
92- // because the "state" that changed might be the completion of some async validation task
93- editContext . NotifyValidationStateChanged ( ) ;
108+ public void Dispose ( )
109+ {
110+ _messages . Clear ( ) ;
111+ _editContext . OnFieldChanged -= OnFieldChanged ;
112+ _editContext . OnValidationRequested -= OnValidationRequested ;
113+ _editContext . NotifyValidationStateChanged ( ) ;
94114 }
95- }
96115
97- private static bool TryGetValidatableProperty ( in FieldIdentifier fieldIdentifier , [ NotNullWhen ( true ) ] out PropertyInfo ? propertyInfo )
98- {
99- var cacheKey = ( ModelType : fieldIdentifier . Model . GetType ( ) , fieldIdentifier . FieldName ) ;
100- if ( ! _propertyInfoCache . TryGetValue ( cacheKey , out propertyInfo ) )
116+ private static bool TryGetValidatableProperty ( in FieldIdentifier fieldIdentifier , [ NotNullWhen ( true ) ] out PropertyInfo ? propertyInfo )
101117 {
102- // DataAnnotations only validates public properties, so that's all we'll look for
103- // If we can't find it, cache 'null' so we don't have to try again next time
104- propertyInfo = cacheKey . ModelType . GetProperty ( cacheKey . FieldName ) ;
118+ var cacheKey = ( ModelType : fieldIdentifier . Model . GetType ( ) , fieldIdentifier . FieldName ) ;
119+ if ( ! _propertyInfoCache . TryGetValue ( cacheKey , out propertyInfo ) )
120+ {
121+ // DataAnnotations only validates public properties, so that's all we'll look for
122+ // If we can't find it, cache 'null' so we don't have to try again next time
123+ propertyInfo = cacheKey . ModelType . GetProperty ( cacheKey . FieldName ) ;
105124
106- // No need to lock, because it doesn't matter if we write the same value twice
107- _propertyInfoCache [ cacheKey ] = propertyInfo ;
108- }
125+ // No need to lock, because it doesn't matter if we write the same value twice
126+ _propertyInfoCache [ cacheKey ] = propertyInfo ;
127+ }
109128
110- return propertyInfo != null ;
129+ return propertyInfo != null ;
130+ }
111131 }
112132 }
113133}
0 commit comments