@@ -21,13 +21,6 @@ internal class DataAnnotationsMetadataProvider :
2121 IDisplayMetadataProvider ,
2222 IValidationMetadataProvider
2323{
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-
3124 private readonly IStringLocalizerFactory ? _stringLocalizerFactory ;
3225 private readonly MvcOptions _options ;
3326 private readonly MvcDataAnnotationsLocalizationOptions _localizationOptions ;
@@ -360,25 +353,9 @@ public void CreateValidationMetadata(ValidationMetadataProviderContext context)
360353 else if ( context . Key . MetadataKind == ModelMetadataKind . Property )
361354 {
362355 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 )
377357 {
378- addInferredRequiredAttribute = IsNullableReferenceType (
379- property . DeclaringType ! ,
380- member : null ,
381- context . PropertyAttributes ! ) ;
358+ addInferredRequiredAttribute = IsRequired ( context ) ;
382359 }
383360 }
384361 else if ( context . Key . MetadataKind == ModelMetadataKind . Parameter )
@@ -387,11 +364,9 @@ public void CreateValidationMetadata(ValidationMetadataProviderContext context)
387364 // since the parameter will be optional.
388365 if ( ! context . Key . ParameterInfo ! . HasDefaultValue )
389366 {
390- addInferredRequiredAttribute = IsNullableReferenceType (
391- context . Key . ParameterInfo ! . Member . ReflectedType ,
392- context . Key . ParameterInfo . Member ,
393- context . ParameterAttributes ! ) ;
367+ addInferredRequiredAttribute = IsRequired ( context ) ;
394368 }
369+
395370 }
396371 else
397372 {
@@ -465,108 +440,16 @@ private static string GetDisplayGroup(FieldInfo field)
465440 return string . Empty ;
466441 }
467442
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 )
480444 {
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 ;
571454 }
572455}
0 commit comments