1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
- using System . Collections ;
5
4
using System . Collections . Concurrent ;
6
5
using System . ComponentModel . DataAnnotations ;
7
6
using System . Diagnostics . CodeAnalysis ;
8
- using System . Linq ;
9
7
using System . Reflection ;
10
8
using System . Reflection . Metadata ;
11
9
using System . Runtime . InteropServices ;
12
- using System . Text . RegularExpressions ;
13
10
using Microsoft . AspNetCore . Http . Validation ;
14
11
using Microsoft . Extensions . DependencyInjection ;
15
12
using Microsoft . Extensions . Options ;
@@ -160,9 +157,9 @@ private void ValidateWithDefaultValidator(ValidationContext validationContext)
160
157
#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.
161
158
private bool TryValidateTypeInfo ( ValidationContext validationContext )
162
159
{
163
- var options = _serviceProvider ? . GetService < IOptions < ValidationOptions > > ( ) ? . Value ;
160
+ var options = _serviceProvider ? . GetRequiredService < IOptions < ValidationOptions > > ( ) . Value ! ;
164
161
165
- if ( options == null || ! options . TryGetValidatableTypeInfo ( _editContext . Model . GetType ( ) , out var typeInfo ) )
162
+ if ( ! options . TryGetValidatableTypeInfo ( _editContext . Model . GetType ( ) , out var typeInfo ) )
166
163
{
167
164
return false ;
168
165
}
@@ -175,7 +172,7 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
175
172
176
173
var containerMapping = new Dictionary < string , object ? > ( ) ;
177
174
178
- validateContext . OnValidationError += ( key , _ , container ) => containerMapping [ key ] = container ;
175
+ validateContext . OnValidationError += context => containerMapping [ context . Key ] = context . Container ;
179
176
180
177
var validationTask = typeInfo . ValidateAsync ( _editContext . Model , validateContext , CancellationToken . None ) ;
181
178
@@ -198,9 +195,6 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
198
195
// directly to ValidationMessageStore in the OnValidationError handler.
199
196
var fieldContainer = containerMapping [ fieldKey ] ?? _editContext . Model ;
200
197
201
- // Alternative: Reverse mapping based on object graph walk.
202
- //var fieldContainer = GetFieldContainer(_editContext.Model, fieldKey);
203
-
204
198
var lastDotIndex = fieldKey . LastIndexOf ( '.' ) ;
205
199
var fieldName = lastDotIndex >= 0 ? fieldKey [ ( lastDotIndex + 1 ) ..] : fieldKey ;
206
200
@@ -212,70 +206,6 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
212
206
}
213
207
#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.
214
208
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
-
279
209
public void Dispose ( )
280
210
{
281
211
_messages . Clear ( ) ;
@@ -310,11 +240,5 @@ internal void ClearCache()
310
240
{
311
241
_propertyInfoCache . Clear ( ) ;
312
242
}
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 ( ) ;
319
243
}
320
244
}
0 commit comments