@@ -21,13 +21,6 @@ internal class DataAnnotationsMetadataProvider :
21
21
IDisplayMetadataProvider ,
22
22
IValidationMetadataProvider
23
23
{
24
- // The [Nullable] attribute is synthesized by the compiler. It's best to just compare the type name.
25
- private const string NullableAttributeFullTypeName = "System.Runtime.CompilerServices.NullableAttribute" ;
26
- private const string NullableFlagsFieldName = "NullableFlags" ;
27
-
28
- private const string NullableContextAttributeFullName = "System.Runtime.CompilerServices.NullableContextAttribute" ;
29
- private const string NullableContextFlagsFieldName = "Flag" ;
30
-
31
24
private readonly IStringLocalizerFactory ? _stringLocalizerFactory ;
32
25
private readonly MvcOptions _options ;
33
26
private readonly MvcDataAnnotationsLocalizationOptions _localizationOptions ;
@@ -360,25 +353,9 @@ public void CreateValidationMetadata(ValidationMetadataProviderContext context)
360
353
else if ( context . Key . MetadataKind == ModelMetadataKind . Property )
361
354
{
362
355
var property = context . Key . PropertyInfo ;
363
- if ( property is null )
364
- {
365
- // PropertyInfo was unavailable on ModelIdentity prior to 3.1.
366
- // Making a cogent argument about the nullability of the property requires inspecting the declared type,
367
- // since looking at the runtime type may result in false positives: https://github.com/dotnet/aspnetcore/issues/14812
368
- // The only way we could arrive here is if the ModelMetadata was constructed using the non-default provider.
369
- // We'll cursorily examine the attributes on the property, but not the ContainerType to make a decision about it's nullability.
370
-
371
- if ( HasNullableAttribute ( context . PropertyAttributes ! , out var propertyHasNullableAttribute ) )
372
- {
373
- addInferredRequiredAttribute = propertyHasNullableAttribute ;
374
- }
375
- }
376
- else
356
+ if ( property is not null )
377
357
{
378
- addInferredRequiredAttribute = IsNullableReferenceType (
379
- property . DeclaringType ! ,
380
- member : null ,
381
- context . PropertyAttributes ! ) ;
358
+ addInferredRequiredAttribute = IsRequired ( context ) ;
382
359
}
383
360
}
384
361
else if ( context . Key . MetadataKind == ModelMetadataKind . Parameter )
@@ -387,11 +364,9 @@ public void CreateValidationMetadata(ValidationMetadataProviderContext context)
387
364
// since the parameter will be optional.
388
365
if ( ! context . Key . ParameterInfo ! . HasDefaultValue )
389
366
{
390
- addInferredRequiredAttribute = IsNullableReferenceType (
391
- context . Key . ParameterInfo ! . Member . ReflectedType ,
392
- context . Key . ParameterInfo . Member ,
393
- context . ParameterAttributes ! ) ;
367
+ addInferredRequiredAttribute = IsRequired ( context ) ;
394
368
}
369
+
395
370
}
396
371
else
397
372
{
@@ -465,108 +440,16 @@ private static string GetDisplayGroup(FieldInfo field)
465
440
return string . Empty ;
466
441
}
467
442
468
- internal static bool IsNullableReferenceType ( Type ? containingType , MemberInfo ? member , IEnumerable < object > attributes )
469
- {
470
- if ( HasNullableAttribute ( attributes , out var result ) )
471
- {
472
- return result ;
473
- }
474
-
475
- return IsNullableBasedOnContext ( containingType , member ) ;
476
- }
477
-
478
- // Internal for testing
479
- internal static bool HasNullableAttribute ( IEnumerable < object > attributes , out bool isNullable )
443
+ internal static bool IsRequired ( ValidationMetadataProviderContext context )
480
444
{
481
- // [Nullable] is compiler synthesized, comparing by name.
482
- var nullableAttribute = attributes
483
- . FirstOrDefault ( a => string . Equals ( a . GetType ( ) . FullName , NullableAttributeFullTypeName , StringComparison . Ordinal ) ) ;
484
- if ( nullableAttribute == null )
485
- {
486
- isNullable = false ;
487
- return false ; // [Nullable] not found
488
- }
489
-
490
- // We don't handle cases where generics and NNRT are used. This runs into a
491
- // fundamental limitation of ModelMetadata - we use a single Type and Property/Parameter
492
- // to look up the metadata. However when generics are involved and NNRT is in use
493
- // the distance between the [Nullable] and member we're looking at is potentially
494
- // unbounded.
495
- //
496
- // See: https://github.com/dotnet/roslyn/blob/master/docs/features/nullable-reference-types.md#annotations
497
- if ( nullableAttribute . GetType ( ) . GetField ( NullableFlagsFieldName ) is FieldInfo field &&
498
- field . GetValue ( nullableAttribute ) is byte [ ] flags &&
499
- flags . Length > 0 &&
500
- flags [ 0 ] == 1 ) // First element is the property/parameter type.
501
- {
502
- isNullable = true ;
503
- return true ; // [Nullable] found and type is an NNRT
504
- }
505
-
506
- isNullable = false ;
507
- return true ; // [Nullable] found but type is not an NNRT
508
- }
509
-
510
- internal static bool IsNullableBasedOnContext ( Type ? containingType , MemberInfo ? member )
511
- {
512
- if ( containingType is null )
513
- {
514
- return false ;
515
- }
516
-
517
- // For generic types, inspecting the nullability requirement additionally requires
518
- // inspecting the nullability constraint on generic type parameters. This is fairly non-triviial
519
- // so we'll just avoid calculating it. Users should still be able to apply an explicit [Required]
520
- // attribute on these members.
521
- if ( containingType . IsGenericType )
522
- {
523
- return false ;
524
- }
525
-
526
- // The [Nullable] and [NullableContext] attributes are not inherited.
527
- //
528
- // The [NullableContext] attribute can appear on a method or on the module.
529
- var attributes = member ? . GetCustomAttributes ( inherit : false ) ?? Array . Empty < object > ( ) ;
530
- var isNullable = AttributesHasNullableContext ( attributes ) ;
531
- if ( isNullable != null )
532
- {
533
- return isNullable . Value ;
534
- }
535
-
536
- // Check on the containing type
537
- var type = containingType ;
538
- do
539
- {
540
- attributes = type . GetCustomAttributes ( inherit : false ) ;
541
- isNullable = AttributesHasNullableContext ( attributes ) ;
542
- if ( isNullable != null )
543
- {
544
- return isNullable . Value ;
545
- }
546
-
547
- type = type . DeclaringType ;
548
- }
549
- while ( type != null ) ;
550
-
551
- // If we don't find the attribute on the declaring type then repeat at the module level
552
- attributes = containingType . Module . GetCustomAttributes ( inherit : false ) ;
553
- isNullable = AttributesHasNullableContext ( attributes ) ;
554
- return isNullable ?? false ;
555
-
556
- bool ? AttributesHasNullableContext ( object [ ] attributes )
557
- {
558
- var nullableContextAttribute = attributes
559
- . FirstOrDefault ( a => string . Equals ( a . GetType ( ) . FullName , NullableContextAttributeFullName , StringComparison . Ordinal ) ) ;
560
- if ( nullableContextAttribute != null )
561
- {
562
- if ( nullableContextAttribute . GetType ( ) . GetField ( NullableContextFlagsFieldName ) is FieldInfo field &&
563
- field . GetValue ( nullableContextAttribute ) is byte @byte )
564
- {
565
- return @byte == 1 ; // [NullableContext] found
566
- }
567
- }
568
-
569
- return null ;
570
- }
445
+ var nullabilityContext = new NullabilityInfoContext ( ) ;
446
+ var nullability = context . Key . MetadataKind switch
447
+ {
448
+ ModelMetadataKind . Parameter => nullabilityContext . Create ( context . Key . ParameterInfo ! ) ,
449
+ ModelMetadataKind . Property => nullabilityContext . Create ( context . Key . PropertyInfo ! ) ,
450
+ _ => null
451
+ } ;
452
+ var isOptional = nullability != null && nullability . ReadState != NullabilityState . NotNull ;
453
+ return ! isOptional ;
571
454
}
572
455
}
0 commit comments