Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,16 @@ private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier
{
// DataAnnotations only validates public properties, so that's all we'll look for
// If we can't find it, cache 'null' so we don't have to try again next time
propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName);
propertyInfo = cacheKey.ModelType.GetProperty(
cacheKey.FieldName,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

if (propertyInfo is null)
{
propertyInfo = cacheKey.ModelType.GetProperty(
cacheKey.FieldName,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FlattenHierarchy is only relevant for statics IIRC.

And this code can still throw if the previous call returned null and we get into here with some shadowing member in a base type.

}

// No need to lock, because it doesn't matter if we write the same value twice
_propertyInfoCache[cacheKey] = propertyInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,110 @@ public void CanDetachFromEditContext()
Assert.Empty(editContext.GetValidationMessages());
}

[Fact]
public void ValidatesHiddenPropertiesWithoutAmbiguousMatchException()
Comment thread
ilonatommy marked this conversation as resolved.
{
var model = new DerivedModelWithHiddenProperty { OrderID = 150 };
var editContext = new EditContext(model);
editContext.EnableDataAnnotationsValidation(_serviceProvider);

Assert.False(editContext.Validate());
Assert.Equal(new[] { "OrderID:range" }, editContext.GetValidationMessages());

var orderIdIdentifier = new FieldIdentifier(model, nameof(DerivedModelWithHiddenProperty.OrderID));
editContext.NotifyFieldChanged(orderIdIdentifier);
model.OrderID = 50;
editContext.NotifyFieldChanged(orderIdIdentifier);
Assert.Empty(editContext.GetValidationMessages());
}

[Fact]
public void ValidatesHiddenPropertiesWithPropertyCaching()
{
var model = new DerivedModelWithHiddenProperty { OrderID = 150 };
var editContext = new EditContext(model);
editContext.EnableDataAnnotationsValidation(_serviceProvider);
var orderIdIdentifier = new FieldIdentifier(model, nameof(DerivedModelWithHiddenProperty.OrderID));

var sequence = new[] { 150, 50, 200, 75, 99, 101, 1 };
var expected = new[] { "OrderID:range" };
foreach (var value in sequence)
{
model.OrderID = value;
editContext.NotifyFieldChanged(orderIdIdentifier);
var expectedMessages = (value < 1 || value > 100) ? expected : Array.Empty<string>();
Assert.Equal(expectedMessages, editContext.GetValidationMessages());
}
}

[Fact]
public void MatchesPropertyByExactName()
{
var model = new DerivedModelWithHiddenProperty { OrderID = 150 };
var editContext = new EditContext(model);
editContext.EnableDataAnnotationsValidation(_serviceProvider);

var field = new FieldIdentifier(model, "OrderID");
editContext.NotifyFieldChanged(field);
Assert.Equal(new[] { "OrderID:range" }, editContext.GetValidationMessages());
}

[Fact]
public void ValidatesInheritedPropertyFromBaseClass()
{
var model = new DerivedModelWithInheritedOnly { Description = "x" };
var editContext = new EditContext(model);
editContext.EnableDataAnnotationsValidation(_serviceProvider);

var field = new FieldIdentifier(model, nameof(DerivedModelWithInheritedOnly.BaseName));
editContext.NotifyFieldChanged(field);
Assert.Equal(new[] { "BaseName:required" }, editContext.GetValidationMessages());

model.BaseName = "ok";
editContext.NotifyFieldChanged(field);
Assert.Empty(editContext.GetValidationMessages());
}

[Fact]
public void ValidatesPropertyHiddenAtMultipleInheritanceLevels()
{
var model = new DeepDerivedModel { Tag = 150 };
var editContext = new EditContext(model);
editContext.EnableDataAnnotationsValidation(_serviceProvider);

var field = new FieldIdentifier(model, nameof(DeepDerivedModel.Tag));
editContext.NotifyFieldChanged(field);
Assert.Equal(new[] { "Tag:range" }, editContext.GetValidationMessages());

model.Tag = 5;
editContext.NotifyFieldChanged(field);
Assert.Empty(editContext.GetValidationMessages());
}

[Fact]
public void SkipsValidationWhenDerivedShadowHasNoAttributes()
{
var model = new DerivedModelWithUnattributedHiddenProperty { Name = null };
var editContext = new EditContext(model);
editContext.EnableDataAnnotationsValidation(_serviceProvider);

var field = new FieldIdentifier(model, nameof(DerivedModelWithUnattributedHiddenProperty.Name));
editContext.NotifyFieldChanged(field);
Assert.Empty(editContext.GetValidationMessages());
}

[Fact]
public void IgnoresStaticProperty()
{
var model = new ModelWithStaticProperty { Value = 0 };
var editContext = new EditContext(model);
editContext.EnableDataAnnotationsValidation(_serviceProvider);

var field = new FieldIdentifier(model, nameof(ModelWithStaticProperty.StaticValue));
editContext.NotifyFieldChanged(field);
Assert.Empty(editContext.GetValidationMessages());
}

[Fact]
public Task FormLevelAsyncValidationProducesMessages() => RunOnDispatcher(async () =>
{
Expand Down Expand Up @@ -294,4 +398,58 @@ class TestModel
[Required] internal string ThisWillNotBeValidatedBecauseItIsInternal { get; set; }
#pragma warning restore 649
}

class DerivedModelWithHiddenProperty : ModelWithHiddenBaseProperty
{
[Range(1, 100, ErrorMessage = "OrderID:range")]
public new int OrderID { get; set; }
}

class ModelWithHiddenBaseProperty
{
public object OrderID { get; set; }

public object Tag { get; set; }
}

class MidLevelModelWithShadow : ModelWithHiddenBaseProperty
{
public new string Tag { get; set; }
}

class DeepDerivedModel : MidLevelModelWithShadow
{
[Range(1, 100, ErrorMessage = "Tag:range")]
public new int Tag { get; set; }
}

class DerivedModelWithUnattributedHiddenProperty : ModelWithNamedBase
{
public new string Name { get; set; }
}

class ModelWithNamedBase
{
[Required(ErrorMessage = "Name:required")]
public object Name { get; set; }
}

class ModelWithStaticProperty
{
[Range(1, 100, ErrorMessage = "StaticValue:range")]
public static int StaticValue { get; set; }

public int Value { get; set; }
}

class DerivedModelWithInheritedOnly : ModelWithBaseName
{
public string Description { get; set; }
}

class ModelWithBaseName
{
[Required(ErrorMessage = "BaseName:required")]
public string BaseName { get; set; }
}
}
Loading