Browse Source

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 <julien@lebosquain.net>
pull/20682/head
Steven Kirk 1 month ago
committed by GitHub
parent
commit
d9a6ae517f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 108
      api/Avalonia.nupkg.xml
  2. 3
      src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs
  3. 2
      src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs
  4. 2
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  5. 2
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  6. 3
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs
  7. 2
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs
  8. 2
      src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs
  9. 2
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  10. 2
      src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs
  11. 2
      src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs
  12. 13
      src/Avalonia.Controls/AppBuilder.cs
  13. 5
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs

108
api/Avalonia.nupkg.xml

@ -25,6 +25,60 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.BindingPlugins</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.DataValidationBase</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.ExceptionValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IDataValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IndeiValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IPropertyAccessorPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IStreamPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.PropertyAccessorBase</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.PropertyError</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Diagnostics.AppliedStyle</Target> <Target>T:Avalonia.Diagnostics.AppliedStyle</Target>
@ -253,6 +307,60 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.BindingPlugins</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.DataValidationBase</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.ExceptionValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IDataValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IndeiValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IPropertyAccessorPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IStreamPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.PropertyAccessorBase</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.PropertyError</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Diagnostics.AppliedStyle</Target> <Target>T:Avalonia.Diagnostics.AppliedStyle</Target>

3
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. /// Holds a registry of plugins used for bindings.
/// </summary> /// </summary>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public static class BindingPlugins internal static class BindingPlugins
{ {
internal static readonly List<IPropertyAccessorPlugin> s_propertyAccessors = new() internal static readonly List<IPropertyAccessorPlugin> s_propertyAccessors = new()
{ {
@ -17,7 +17,6 @@ namespace Avalonia.Data.Core.Plugins
internal static readonly List<IDataValidationPlugin> s_dataValidators = new() internal static readonly List<IDataValidationPlugin> s_dataValidators = new()
{ {
new DataAnnotationsValidationPlugin(),
new IndeiValidationPlugin(), new IndeiValidationPlugin(),
new ExceptionValidationPlugin(), new ExceptionValidationPlugin(),
}; };

2
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 /// and convert any values received from the inner property accessor into
/// <see cref="BindingNotification"/>s. /// <see cref="BindingNotification"/>s.
/// </remarks> /// </remarks>
public abstract class DataValidationBase : PropertyAccessorBase, IObserver<object?> internal abstract class DataValidationBase : PropertyAccessorBase, IObserver<object?>
{ {
private readonly IPropertyAccessor _inner; private readonly IPropertyAccessor _inner;

2
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// Validates properties that report errors by throwing exceptions. /// Validates properties that report errors by throwing exceptions.
/// </summary> /// </summary>
public class ExceptionValidationPlugin : IDataValidationPlugin internal class ExceptionValidationPlugin : IDataValidationPlugin
{ {
/// <inheritdoc/> /// <inheritdoc/>
public bool Match(WeakReference<object?> reference, string memberName) => true; public bool Match(WeakReference<object?> reference, string memberName) => true;

2
src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// Defines how data validation is observed by an <see cref="BindingExpression"/>. /// Defines how data validation is observed by an <see cref="BindingExpression"/>.
/// </summary> /// </summary>
public interface IDataValidationPlugin internal interface IDataValidationPlugin
{ {
/// <summary> /// <summary>
/// Checks whether this plugin can handle data validation on the specified object. /// Checks whether this plugin can handle data validation on the specified object.

3
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs

@ -3,8 +3,7 @@ using System;
namespace Avalonia.Data.Core.Plugins namespace Avalonia.Data.Core.Plugins
{ {
/// <summary> /// <summary>
/// Defines an accessor to a property on an object returned by a /// Defines an accessor to a property on an object.
/// <see cref="IPropertyAccessorPlugin"/>
/// </summary> /// </summary>
public interface IPropertyAccessor : IDisposable public interface IPropertyAccessor : IDisposable
{ {

2
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// Defines how a member is read, written and observed by a binding. /// Defines how a member is read, written and observed by a binding.
/// </summary> /// </summary>
public interface IPropertyAccessorPlugin internal interface IPropertyAccessorPlugin
{ {
/// <summary> /// <summary>
/// Checks whether this plugin can handle accessing the properties of the specified object. /// Checks whether this plugin can handle accessing the properties of the specified object.

2
src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// Defines a plugin that handles the '^' stream binding operator. /// Defines a plugin that handles the '^' stream binding operator.
/// </summary> /// </summary>
public interface IStreamPlugin internal interface IStreamPlugin
{ {
/// <summary> /// <summary>
/// Checks whether this plugin handles the specified value. /// Checks whether this plugin handles the specified value.

2
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -9,7 +9,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>. /// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>.
/// </summary> /// </summary>
public class IndeiValidationPlugin : IDataValidationPlugin internal class IndeiValidationPlugin : IDataValidationPlugin
{ {
private static readonly WeakEvent<INotifyDataErrorInfo, DataErrorsChangedEventArgs> private static readonly WeakEvent<INotifyDataErrorInfo, DataErrorsChangedEventArgs>
ErrorsChangedWeakEvent = WeakEvent.Register<INotifyDataErrorInfo, DataErrorsChangedEventArgs>( ErrorsChangedWeakEvent = WeakEvent.Register<INotifyDataErrorInfo, DataErrorsChangedEventArgs>(

2
src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// Defines a default base implementation for a <see cref="IPropertyAccessor"/>. /// Defines a default base implementation for a <see cref="IPropertyAccessor"/>.
/// </summary> /// </summary>
public abstract class PropertyAccessorBase : IPropertyAccessor internal abstract class PropertyAccessorBase : IPropertyAccessor
{ {
private Action<object?>? _listener; private Action<object?>? _listener;

2
src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// An <see cref="IPropertyAccessor"/> that represents an error. /// An <see cref="IPropertyAccessor"/> that represents an error.
/// </summary> /// </summary>
public class PropertyError : IPropertyAccessor internal class PropertyError : IPropertyAccessor
{ {
private readonly BindingNotification _error; private readonly BindingNotification _error;

13
src/Avalonia.Controls/AppBuilder.cs

@ -1,10 +1,12 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Data.Core.Plugins;
namespace Avalonia namespace Avalonia
{ {
@ -265,6 +267,17 @@ namespace Avalonia
return Self; return Self;
} }
/// <summary>
/// Adds support for validation using <c>System.ComponentModel.DataAnnotations</c>.
/// </summary>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public AppBuilder WithDataAnnotationsValidation()
{
if (!BindingPlugins.DataValidators.Any(x => x is DataAnnotationsValidationPlugin))
BindingPlugins.DataValidators.Insert(0, new DataAnnotationsValidationPlugin());
return Self;
}
/// <summary> /// <summary>
/// Registers an action that is executed with the current font manager. /// Registers an action that is executed with the current font manager.
/// </summary> /// </summary>

5
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs

@ -2,7 +2,9 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
@ -273,6 +275,9 @@ public partial class BindingExpressionTests
[Fact] [Fact]
public void Updates_Data_Validation_For_Required_DataAnnotation() 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 data = new DataAnnotationsViewModel();
var target = CreateTargetWithSource( var target = CreateTargetWithSource(
data, data,

Loading…
Cancel
Save