From d9a6ae517fd59ffd2dbbc674e81d59a628c1458f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 11 Feb 2026 18:36:04 +0100 Subject: [PATCH] Hide binding plugins and disable Data Annotations validation by default (#20623) * Make binding plugin API internal. Making everything pluggable like has performance implications even if the plugins are never customized (which will be the 99% case). `IPropertyAccessor` is used in `CompiledBindingPathBuilder` so needs to be kept public for the moment. * Disable Data Annotations validation by default. It [conflicts with `CommunityToolkit.Mvvm`](https://github.com/AvaloniaUI/Avalonia/issues/8397) which is now the default MVVM framework, so require it to be enabled in the `AppBuilder`. * Update API supressions. * Add back missing using --------- Co-authored-by: Julien Lebosquain --- api/Avalonia.nupkg.xml | 108 ++++++++++++++++++ .../Data/Core/Plugins/BindingPlugins.cs | 3 +- .../Data/Core/Plugins/DataValidationBase.cs | 2 +- .../Core/Plugins/ExceptionValidationPlugin.cs | 2 +- .../Core/Plugins/IDataValidationPlugin.cs | 2 +- .../Data/Core/Plugins/IPropertyAccessor.cs | 3 +- .../Core/Plugins/IPropertyAccessorPlugin.cs | 2 +- .../Data/Core/Plugins/IStreamPlugin.cs | 2 +- .../Core/Plugins/IndeiValidationPlugin.cs | 2 +- .../Data/Core/Plugins/PropertyAccessorBase.cs | 2 +- .../Data/Core/Plugins/PropertyError.cs | 2 +- src/Avalonia.Controls/AppBuilder.cs | 13 +++ .../BindingExpressionTests.DataValidation.cs | 5 + 13 files changed, 136 insertions(+), 12 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index b7efaf7869..e6a73822e9 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -25,6 +25,60 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Data.Core.Plugins.BindingPlugins + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.DataValidationBase + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.ExceptionValidationPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IDataValidationPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IndeiValidationPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IPropertyAccessorPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IStreamPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.PropertyAccessorBase + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.PropertyError + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Diagnostics.AppliedStyle @@ -253,6 +307,60 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Data.Core.Plugins.BindingPlugins + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.DataValidationBase + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.ExceptionValidationPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IDataValidationPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IndeiValidationPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IPropertyAccessorPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IStreamPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.PropertyAccessorBase + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.PropertyError + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Diagnostics.AppliedStyle diff --git a/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs b/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs index 520c345ad5..50e137eac1 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs @@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins /// Holds a registry of plugins used for bindings. /// [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] - public static class BindingPlugins + internal static class BindingPlugins { internal static readonly List s_propertyAccessors = new() { @@ -17,7 +17,6 @@ namespace Avalonia.Data.Core.Plugins internal static readonly List s_dataValidators = new() { - new DataAnnotationsValidationPlugin(), new IndeiValidationPlugin(), new ExceptionValidationPlugin(), }; diff --git a/src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs b/src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs index 6bef9f69f6..85ebf6ff78 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs @@ -11,7 +11,7 @@ namespace Avalonia.Data.Core.Plugins /// and convert any values received from the inner property accessor into /// s. /// - public abstract class DataValidationBase : PropertyAccessorBase, IObserver + internal abstract class DataValidationBase : PropertyAccessorBase, IObserver { private readonly IPropertyAccessor _inner; diff --git a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs index 899b542f80..25c77e3282 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs @@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties that report errors by throwing exceptions. /// - public class ExceptionValidationPlugin : IDataValidationPlugin + internal class ExceptionValidationPlugin : IDataValidationPlugin { /// public bool Match(WeakReference reference, string memberName) => true; diff --git a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs index 5d2f06d2c6..339c8a656d 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Defines how data validation is observed by an . /// - public interface IDataValidationPlugin + internal interface IDataValidationPlugin { /// /// Checks whether this plugin can handle data validation on the specified object. diff --git a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs index 8941e86598..8db19bbffa 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs @@ -3,8 +3,7 @@ using System; namespace Avalonia.Data.Core.Plugins { /// - /// Defines an accessor to a property on an object returned by a - /// + /// Defines an accessor to a property on an object. /// public interface IPropertyAccessor : IDisposable { diff --git a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs index e2d7312ac4..fb24969c7e 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Defines how a member is read, written and observed by a binding. /// - public interface IPropertyAccessorPlugin + internal interface IPropertyAccessorPlugin { /// /// Checks whether this plugin can handle accessing the properties of the specified object. diff --git a/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs index b741cfaca2..da86543507 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Defines a plugin that handles the '^' stream binding operator. /// - public interface IStreamPlugin + internal interface IStreamPlugin { /// /// Checks whether this plugin handles the specified value. diff --git a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs index 385d96a7b8..a06514b5da 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs @@ -9,7 +9,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties on objects that implement . /// - public class IndeiValidationPlugin : IDataValidationPlugin + internal class IndeiValidationPlugin : IDataValidationPlugin { private static readonly WeakEvent ErrorsChangedWeakEvent = WeakEvent.Register( diff --git a/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs b/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs index df36347e93..ad4e46ca08 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Defines a default base implementation for a . /// - public abstract class PropertyAccessorBase : IPropertyAccessor + internal abstract class PropertyAccessorBase : IPropertyAccessor { private Action? _listener; diff --git a/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs b/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs index 7143a4269a..dae52e8541 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// An that represents an error. /// - public class PropertyError : IPropertyAccessor + internal class PropertyError : IPropertyAccessor { private readonly BindingNotification _error; diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs index 9d6bcff813..9259be8594 100644 --- a/src/Avalonia.Controls/AppBuilder.cs +++ b/src/Avalonia.Controls/AppBuilder.cs @@ -1,10 +1,12 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Reflection; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform; using Avalonia.Media; using Avalonia.Metadata; +using Avalonia.Data.Core.Plugins; namespace Avalonia { @@ -265,6 +267,17 @@ namespace Avalonia return Self; } + /// + /// Adds support for validation using System.ComponentModel.DataAnnotations. + /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] + public AppBuilder WithDataAnnotationsValidation() + { + if (!BindingPlugins.DataValidators.Any(x => x is DataAnnotationsValidationPlugin)) + BindingPlugins.DataValidators.Insert(0, new DataAnnotationsValidationPlugin()); + return Self; + } + /// /// Registers an action that is executed with the current font manager. /// diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs index 8dd75fedf0..04aa7b8de2 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs @@ -2,7 +2,9 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using Avalonia.Data; +using Avalonia.Data.Core.Plugins; using Avalonia.UnitTests; using Xunit; @@ -273,6 +275,9 @@ public partial class BindingExpressionTests [Fact] public void Updates_Data_Validation_For_Required_DataAnnotation() { + if (!BindingPlugins.DataValidators.Any(x => x is DataAnnotationsValidationPlugin)) + BindingPlugins.DataValidators.Insert(0, new DataAnnotationsValidationPlugin()); + var data = new DataAnnotationsViewModel(); var target = CreateTargetWithSource( data,