11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4- using System . Collections ;
54using System . Collections . Concurrent ;
65using System . ComponentModel . DataAnnotations ;
76using System . Diagnostics . CodeAnalysis ;
8- using System . Linq ;
97using System . Reflection ;
108using System . Reflection . Metadata ;
119using System . Runtime . InteropServices ;
12- using System . Text . RegularExpressions ;
1310using Microsoft . AspNetCore . Http . Validation ;
1411using Microsoft . Extensions . DependencyInjection ;
1512using Microsoft . Extensions . Options ;
@@ -160,9 +157,9 @@ private void ValidateWithDefaultValidator(ValidationContext validationContext)
160157#pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
161158 private bool TryValidateTypeInfo ( ValidationContext validationContext )
162159 {
163- var options = _serviceProvider ? . GetService < IOptions < ValidationOptions > > ( ) ? . Value ;
160+ var options = _serviceProvider ? . GetRequiredService < IOptions < ValidationOptions > > ( ) . Value ! ;
164161
165- if ( options == null || ! options . TryGetValidatableTypeInfo ( _editContext . Model . GetType ( ) , out var typeInfo ) )
162+ if ( ! options . TryGetValidatableTypeInfo ( _editContext . Model . GetType ( ) , out var typeInfo ) )
166163 {
167164 return false ;
168165 }
@@ -175,7 +172,7 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
175172
176173 var containerMapping = new Dictionary < string , object ? > ( ) ;
177174
178- validateContext . OnValidationError += ( key , _ , container ) => containerMapping [ key ] = container ;
175+ validateContext . OnValidationError += context => containerMapping [ context . Key ] = context . Container ;
179176
180177 var validationTask = typeInfo . ValidateAsync ( _editContext . Model , validateContext , CancellationToken . None ) ;
181178
@@ -198,9 +195,6 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
198195 // directly to ValidationMessageStore in the OnValidationError handler.
199196 var fieldContainer = containerMapping [ fieldKey ] ?? _editContext . Model ;
200197
201- // Alternative: Reverse mapping based on object graph walk.
202- //var fieldContainer = GetFieldContainer(_editContext.Model, fieldKey);
203-
204198 var lastDotIndex = fieldKey . LastIndexOf ( '.' ) ;
205199 var fieldName = lastDotIndex >= 0 ? fieldKey [ ( lastDotIndex + 1 ) ..] : fieldKey ;
206200
@@ -212,70 +206,6 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
212206 }
213207#pragma warning restore ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
214208
215- // TODO(OR): Replace this with a more robust implementation or a different approach. E.g. collect references during the validation process itself.
216- [ UnconditionalSuppressMessage ( "Trimming" , "IL2075" , Justification = "Model types are expected to be defined in assemblies that do not get trimmed." ) ]
217- private static object GetFieldContainer ( object obj , string fieldKey )
218- {
219- // The method does not check all possible null access and index bound errors as the path is constructed internally and assumed to be correct.
220- var dotSegments = fieldKey . Split ( '.' ) [ ..^ 1 ] ;
221- var currentObject = obj ;
222-
223- for ( int i = 0 ; i < dotSegments . Length ; i ++ )
224- {
225- string segment = dotSegments [ i ] ;
226-
227- if ( currentObject == null )
228- {
229- string traversedPath = string . Join ( "." , dotSegments . Take ( i ) ) ;
230- throw new ArgumentException ( $ "Cannot access segment '{ segment } ' because the path '{ traversedPath } ' resolved to null.") ;
231- }
232-
233- Match match = _pathSegmentRegex . Match ( segment ) ;
234- if ( ! match . Success )
235- {
236- throw new ArgumentException ( $ "Invalid path segment: '{ segment } '.") ;
237- }
238-
239- string propertyName = match . Groups [ 1 ] . Value ;
240- string ? indexStr = match . Groups [ 2 ] . Success ? match . Groups [ 2 ] . Value : null ;
241-
242- Type currentType = currentObject . GetType ( ) ;
243- PropertyInfo propertyInfo = currentType ! . GetProperty ( propertyName , BindingFlags . Public | BindingFlags . Instance ) ! ;
244- object propertyValue = propertyInfo ! . GetValue ( currentObject ) ! ;
245-
246- if ( indexStr == null ) // Simple property access
247- {
248- currentObject = propertyValue ;
249- }
250- else // Indexed access
251- {
252- if ( ! int . TryParse ( indexStr , out int index ) )
253- {
254- throw new ArgumentException ( $ "Invalid index '{ indexStr } ' in segment '{ segment } '.") ;
255- }
256-
257- if ( propertyValue is Array array )
258- {
259- currentObject = array . GetValue ( index ) ! ;
260- }
261- else if ( propertyValue is IList list )
262- {
263- currentObject = list [ index ] ! ;
264- }
265- else if ( propertyValue is IEnumerable enumerable )
266- {
267- currentObject = enumerable . Cast < object > ( ) . ElementAt ( index ) ;
268- }
269- else
270- {
271- throw new ArgumentException ( $ "Property '{ propertyName } ' is not an array, list, or enumerable. Cannot access by index in segment '{ segment } '.") ;
272- }
273- }
274-
275- }
276- return currentObject ! ;
277- }
278-
279209 public void Dispose ( )
280210 {
281211 _messages . Clear ( ) ;
@@ -310,11 +240,5 @@ internal void ClearCache()
310240 {
311241 _propertyInfoCache . Clear ( ) ;
312242 }
313-
314- private static readonly Regex _pathSegmentRegex = PathSegmentRegexGen ( ) ;
315-
316- // Regex to parse "PropertyName" or "PropertyName[index]"
317- [ GeneratedRegex ( @"^([a-zA-Z_]\w*)(?:\[(\d+)\])?$" , RegexOptions . Compiled ) ]
318- private static partial Regex PathSegmentRegexGen ( ) ;
319243 }
320244}
0 commit comments