diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs index 0c940a1461..a3a2e0c2b0 100644 --- a/src/Avalonia.Base/Data/BindingNotification.cs +++ b/src/Avalonia.Base/Data/BindingNotification.cs @@ -57,7 +57,6 @@ namespace Avalonia.Data /// The binding value. public BindingNotification(object? value) { - Debug.Assert(value is not BindingNotification); _value = value; } diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs index e8e6633ab7..8b53190f86 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs @@ -187,13 +187,20 @@ internal abstract class ExpressionNode else if (notification.ErrorType == BindingErrorType.DataValidationError) { if (notification.HasValue) - SetValue(notification.Value, notification.Error); + { + if (notification.Value is BindingNotification n) + SetValue(n); + else + SetValue(notification.Value, notification.Error); + } else + { SetDataValidationError(notification.Error!); + } } else { - SetValue(notification.Value, null); + SetValue(notification.Value); } } else diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs index bedf24bd6d..8e1865f209 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Avalonia.Data; using Avalonia.UnitTests; using Xunit; @@ -271,6 +272,39 @@ public partial class BindingExpressionTests GC.KeepAlive(data); } + [Fact] + public void Updates_Data_Validation_For_Required_DataAnnotation() + { + var data = new DataAnnotationsViewModel(); + var target = CreateTargetWithSource( + data, + o => o.RequiredString, + enableDataValidation: true); + + AssertBindingError( + target, + TargetClass.StringProperty, + new DataValidationException("String is required!"), + BindingErrorType.DataValidationError); + } + + [Fact] + public void Handles_Indei_And_DataAnnotations_On_Same_Class() + { + // Issue #15201 + var data = new IndeiDataAnnotationsViewModel(); + var target = CreateTargetWithSource( + data, + o => o.RequiredString, + enableDataValidation: true); + + AssertBindingError( + target, + TargetClass.StringProperty, + new DataValidationException("String is required!"), + BindingErrorType.DataValidationError); + } + public class ExceptionViewModel : NotifyingBase { private int _mustBePositive; @@ -341,6 +375,42 @@ public partial class BindingExpressionTests public override IEnumerable? GetErrors(string propertyName) => null; } + private class DataAnnotationsViewModel : NotifyingBase + { + private string? _requiredString; + + [Required(ErrorMessage = "String is required!")] + public string? RequiredString + { + get { return _requiredString; } + set { _requiredString = value; RaisePropertyChanged(); } + } + } + + private class IndeiDataAnnotationsViewModel : IndeiBase + { + private string? _requiredString; + + [Required(ErrorMessage = "String is required!")] + public string? RequiredString + { + get { return _requiredString; } + set { _requiredString = value; RaisePropertyChanged(); } + } + + public override bool HasErrors => RequiredString is null; + + public override IEnumerable? GetErrors(string propertyName) + { + if (propertyName == nameof(RequiredString) && RequiredString is null) + { + return new[] { "String is required!" }; + } + + return null; + } + } + private static void AssertNoError(TargetClass target, AvaloniaProperty property) { Assert.False(target.BindingNotifications.TryGetValue(property, out var notification));