Skip to content

Commit 2063212

Browse files
authored
Use NullabilityInfoContext in DataAnnotationsMetadataProvider
1 parent 73c1a8d commit 2063212

File tree

2 files changed

+31
-87
lines changed

2 files changed

+31
-87
lines changed

src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsMetadataProvider.cs

+13-81
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ internal class DataAnnotationsMetadataProvider :
2727
private const string NullableAttributeFullTypeName = "System.Runtime.CompilerServices.NullableAttribute";
2828
private const string NullableFlagsFieldName = "NullableFlags";
2929

30-
private const string NullableContextAttributeFullName = "System.Runtime.CompilerServices.NullableContextAttribute";
31-
private const string NullableContextFlagsFieldName = "Flag";
32-
3330
private readonly IStringLocalizerFactory? _stringLocalizerFactory;
3431
private readonly MvcOptions _options;
3532
private readonly MvcDataAnnotationsLocalizationOptions _localizationOptions;
@@ -377,10 +374,7 @@ public void CreateValidationMetadata(ValidationMetadataProviderContext context)
377374
}
378375
else
379376
{
380-
addInferredRequiredAttribute = IsNullableReferenceType(
381-
property.DeclaringType!,
382-
member: null,
383-
context.PropertyAttributes!);
377+
addInferredRequiredAttribute = IsRequired(context);
384378
}
385379
}
386380
else if (context.Key.MetadataKind == ModelMetadataKind.Parameter)
@@ -389,11 +383,9 @@ public void CreateValidationMetadata(ValidationMetadataProviderContext context)
389383
// since the parameter will be optional.
390384
if (!context.Key.ParameterInfo!.HasDefaultValue)
391385
{
392-
addInferredRequiredAttribute = IsNullableReferenceType(
393-
context.Key.ParameterInfo!.Member.ReflectedType,
394-
context.Key.ParameterInfo.Member,
395-
context.ParameterAttributes!);
386+
addInferredRequiredAttribute = IsRequired(context);
396387
}
388+
397389
}
398390
else
399391
{
@@ -467,14 +459,17 @@ private static string GetDisplayGroup(FieldInfo field)
467459
return string.Empty;
468460
}
469461

470-
internal static bool IsNullableReferenceType(Type? containingType, MemberInfo? member, IEnumerable<object> attributes)
462+
internal static bool IsRequired(ValidationMetadataProviderContext context)
471463
{
472-
if (HasNullableAttribute(attributes, out var result))
473-
{
474-
return result;
475-
}
476-
477-
return IsNullableBasedOnContext(containingType, member);
464+
var nullabilityContext = new NullabilityInfoContext();
465+
var nullability = context.Key.MetadataKind switch
466+
{
467+
ModelMetadataKind.Parameter => nullabilityContext.Create(context.Key.ParameterInfo!),
468+
ModelMetadataKind.Property => nullabilityContext.Create(context.Key.PropertyInfo!),
469+
_ => null
470+
};
471+
var isOptional = nullability != null && nullability.ReadState != NullabilityState.NotNull;
472+
return !isOptional;
478473
}
479474

480475
// Internal for testing
@@ -508,67 +503,4 @@ internal static bool HasNullableAttribute(IEnumerable<object> attributes, out bo
508503
isNullable = false;
509504
return true; // [Nullable] found but type is not an NNRT
510505
}
511-
512-
internal static bool IsNullableBasedOnContext(Type? containingType, MemberInfo? member)
513-
{
514-
if (containingType is null)
515-
{
516-
return false;
517-
}
518-
519-
// For generic types, inspecting the nullability requirement additionally requires
520-
// inspecting the nullability constraint on generic type parameters. This is fairly non-triviial
521-
// so we'll just avoid calculating it. Users should still be able to apply an explicit [Required]
522-
// attribute on these members.
523-
if (containingType.IsGenericType)
524-
{
525-
return false;
526-
}
527-
528-
// The [Nullable] and [NullableContext] attributes are not inherited.
529-
//
530-
// The [NullableContext] attribute can appear on a method or on the module.
531-
var attributes = member?.GetCustomAttributes(inherit: false) ?? Array.Empty<object>();
532-
var isNullable = AttributesHasNullableContext(attributes);
533-
if (isNullable != null)
534-
{
535-
return isNullable.Value;
536-
}
537-
538-
// Check on the containing type
539-
var type = containingType;
540-
do
541-
{
542-
attributes = type.GetCustomAttributes(inherit: false);
543-
isNullable = AttributesHasNullableContext(attributes);
544-
if (isNullable != null)
545-
{
546-
return isNullable.Value;
547-
}
548-
549-
type = type.DeclaringType;
550-
}
551-
while (type != null);
552-
553-
// If we don't find the attribute on the declaring type then repeat at the module level
554-
attributes = containingType.Module.GetCustomAttributes(inherit: false);
555-
isNullable = AttributesHasNullableContext(attributes);
556-
return isNullable ?? false;
557-
558-
bool? AttributesHasNullableContext(object[] attributes)
559-
{
560-
var nullableContextAttribute = attributes
561-
.FirstOrDefault(a => string.Equals(a.GetType().FullName, NullableContextAttributeFullName, StringComparison.Ordinal));
562-
if (nullableContextAttribute != null)
563-
{
564-
if (nullableContextAttribute.GetType().GetField(NullableContextFlagsFieldName) is FieldInfo field &&
565-
field.GetValue(nullableContextAttribute) is byte @byte)
566-
{
567-
return @byte == 1; // [NullableContext] found
568-
}
569-
}
570-
571-
return null;
572-
}
573-
}
574506
}

src/Mvc/Mvc.DataAnnotations/test/DataAnnotationsMetadataProviderTest.cs

+18-6
Original file line numberDiff line numberDiff line change
@@ -1592,9 +1592,11 @@ public void IsNonNullable_FindsNonNullableProperty()
15921592
// Arrange
15931593
var type = typeof(NullableReferenceTypes);
15941594
var property = type.GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceType));
1595+
var key = ModelMetadataIdentity.ForProperty(property, type, type);
1596+
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(property.GetCustomAttributes(inherit: true)));
15951597

15961598
// Act
1597-
var result = DataAnnotationsMetadataProvider.IsNullableReferenceType(type, member: null, property.GetCustomAttributes(inherit: true));
1599+
var result = DataAnnotationsMetadataProvider.IsRequired(context);
15981600

15991601
// Assert
16001602
Assert.True(result);
@@ -1606,9 +1608,11 @@ public void IsNullableReferenceType_ReturnsFalse_ForKeyValuePairWithoutNullableC
16061608
// Arrange
16071609
var type = typeof(KeyValuePair<string, object>);
16081610
var property = type.GetProperty(nameof(KeyValuePair<string, object>.Key));
1611+
var key = ModelMetadataIdentity.ForProperty(property, type, type);
1612+
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(property.GetCustomAttributes(inherit: true)));
16091613

16101614
// Act
1611-
var result = DataAnnotationsMetadataProvider.IsNullableReferenceType(type, member: null, property.GetCustomAttributes(inherit: true));
1615+
var result = DataAnnotationsMetadataProvider.IsRequired(context);
16121616

16131617
// Assert
16141618
Assert.False(result);
@@ -1621,9 +1625,11 @@ public void IsNullableReferenceType_ReturnsTrue_ForKeyValuePairWithNullableConst
16211625
// Arrange
16221626
var type = typeof(KeyValuePair<string, object>);
16231627
var property = type.GetProperty(nameof(KeyValuePair<string, object>.Key))!;
1628+
var key = ModelMetadataIdentity.ForProperty(property, type, type);
1629+
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(property.GetCustomAttributes(inherit: true)));
16241630

16251631
// Act
1626-
var result = DataAnnotationsMetadataProvider.IsNullableReferenceType(type, member: null, property.GetCustomAttributes(inherit: true));
1632+
var result = DataAnnotationsMetadataProvider.IsRequired(context);
16271633

16281634
// Assert
16291635
// While we'd like for result to be 'true', we don't have a very good way of actually calculating it correctly.
@@ -1638,9 +1644,11 @@ public void IsNonNullable_FindsNullableProperty()
16381644
// Arrange
16391645
var type = typeof(NullableReferenceTypes);
16401646
var property = type.GetProperty(nameof(NullableReferenceTypes.NullableReferenceType));
1647+
var key = ModelMetadataIdentity.ForProperty(property, type, type);
1648+
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(property.GetCustomAttributes(inherit: true)));
16411649

16421650
// Act
1643-
var result = DataAnnotationsMetadataProvider.IsNullableReferenceType(type, member: null, property.GetCustomAttributes(inherit: true));
1651+
var result = DataAnnotationsMetadataProvider.IsRequired(context);
16441652

16451653
// Assert
16461654
Assert.False(result);
@@ -1653,9 +1661,11 @@ public void IsNonNullable_FindsNonNullableParameter()
16531661
var type = typeof(NullableReferenceTypes);
16541662
var method = type.GetMethod(nameof(NullableReferenceTypes.Method));
16551663
var parameter = method.GetParameters().Where(p => p.Name == "nonNullableParameter").Single();
1664+
var key = ModelMetadataIdentity.ForParameter(parameter);
1665+
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(parameter.GetCustomAttributes(inherit: true)));
16561666

16571667
// Act
1658-
var result = DataAnnotationsMetadataProvider.IsNullableReferenceType(type, method, parameter.GetCustomAttributes(inherit: true));
1668+
var result = DataAnnotationsMetadataProvider.IsRequired(context);
16591669

16601670
// Assert
16611671
Assert.True(result);
@@ -1668,9 +1678,11 @@ public void IsNonNullable_FindsNullableParameter()
16681678
var type = typeof(NullableReferenceTypes);
16691679
var method = type.GetMethod(nameof(NullableReferenceTypes.Method));
16701680
var parameter = method.GetParameters().Where(p => p.Name == "nullableParameter").Single();
1681+
var key = ModelMetadataIdentity.ForParameter(parameter);
1682+
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(parameter.GetCustomAttributes(inherit: true)));
16711683

16721684
// Act
1673-
var result = DataAnnotationsMetadataProvider.IsNullableReferenceType(type, method, parameter.GetCustomAttributes(inherit: true));
1685+
var result = DataAnnotationsMetadataProvider.IsRequired(context);
16741686

16751687
// Assert
16761688
Assert.False(result);

0 commit comments

Comments
 (0)