Skip to content

Commit

Permalink
[X] do not apply Bindings if DataType doesnt match
Browse files Browse the repository at this point in the history
Make sure the behavior of bindings is consistent accross implementations
(compiled/not compiled)

fixes a bunch of issues
  • Loading branch information
StephaneDelcroix committed Apr 25, 2024
1 parent abada83 commit cdef2e5
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 7 deletions.
11 changes: 9 additions & 2 deletions src/Controls/src/Core/Binding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public string UpdateSourceEventName
}
}

internal Type DataType { get; set; }

internal override void Apply(bool fromTarget)
{
base.Apply(fromTarget);
Expand All @@ -120,7 +122,13 @@ internal override void Apply(object context, BindableObject bindObj, BindablePro
object src = _source;
var isApplied = IsApplied;

base.Apply(src ?? context, bindObj, targetProperty, fromBindingContextChanged, specificity);
var bindingContext = src ?? Context ?? context;
if (DataType != null && bindingContext != null && !DataType.IsAssignableFrom(bindingContext.GetType()))
{
bindingContext = null;
}

base.Apply(bindingContext, bindObj, targetProperty, fromBindingContextChanged, specificity);

if (src != null && isApplied && fromBindingContextChanged)
return;
Expand All @@ -131,7 +139,6 @@ internal override void Apply(object context, BindableObject bindObj, BindablePro
}
else
{
object bindingContext = src ?? Context ?? context;
if (_expression == null)
_expression = new BindingExpression(this, SelfPath);
_expression.Apply(bindingContext, bindObj, targetProperty, specificity);
Expand Down
5 changes: 5 additions & 0 deletions src/Controls/src/Core/IXamlDataTypeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Microsoft.Maui.Controls.Xaml;
interface IXamlDataTypeProvider
{
string BindingDataType { get; }
}
11 changes: 10 additions & 1 deletion src/Controls/src/Xaml/MarkupExtensions/BindingExtension.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System.ComponentModel;
using System.Linq.Expressions;
using Microsoft.Maui.Controls.Internals;

namespace Microsoft.Maui.Controls.Xaml
{
[ContentProperty(nameof(Path))]
[AcceptEmptyServiceProvider]
public sealed class BindingExtension : IMarkupExtension<BindingBase>
{
public string Path { get; set; } = Binding.SelfPath;
Expand All @@ -21,12 +21,21 @@ public sealed class BindingExtension : IMarkupExtension<BindingBase>

BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
{
Type bindingXDataType = null;
if ((serviceProvider.GetService(typeof(IXamlTypeResolver)) is IXamlTypeResolver typeResolver)
&& (serviceProvider.GetService(typeof(IXamlDataTypeProvider)) is IXamlDataTypeProvider dataTypeProvider)
&& dataTypeProvider.BindingDataType != null)
{
typeResolver.TryResolve(dataTypeProvider.BindingDataType, out bindingXDataType);
}

if (TypedBinding == null)
return new Binding(Path, Mode, Converter, ConverterParameter, StringFormat, Source)
{
UpdateSourceEventName = UpdateSourceEventName,
FallbackValue = FallbackValue,
TargetNullValue = TargetNullValue,
DataType = bindingXDataType,
};

TypedBinding.Mode = Mode;
Expand Down
56 changes: 56 additions & 0 deletions src/Controls/src/Xaml/XamlServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ internal XamlServiceProvider(INode node, HydrationContext context)
IXmlLineInfoProvider = new XmlLineInfoProvider(xmlLineInfo);

IValueConverterProvider = new ValueConverterProvider();

if (node is IElementNode elementNode)
Add(typeof(IXamlDataTypeProvider), new XamlDataTypeProvider(elementNode));
}

public XamlServiceProvider() => IValueConverterProvider = new ValueConverterProvider();
Expand Down Expand Up @@ -262,4 +265,57 @@ public string LookupNamespace(string prefix)
public string LookupPrefix(string namespaceName) => throw new NotImplementedException();
public void Add(string prefix, string ns) => namespaces.Add(prefix, ns);
}

class XamlDataTypeProvider : IXamlDataTypeProvider
{
public XamlDataTypeProvider(IElementNode node)
{
static IElementNode GetParent(IElementNode node)
{
return node switch
{
{ Parent: ListNode { Parent: IElementNode parentNode } } => parentNode,
{ Parent: IElementNode parentNode } => parentNode,
_ => null,
};
}

static bool IsBindingContextBinding(IElementNode node)
{
if ( ApplyPropertiesVisitor.TryGetPropertyName(node, node.Parent, out XmlName name)
&& name.NamespaceURI == ""
&& name.LocalName == nameof(BindableObject.BindingContext))
return true;
return false;
}

INode dataTypeNode = null;
IElementNode n = node as IElementNode;

// Special handling for BindingContext={Binding ...}
// The order of checks is:
// - x:DataType on the binding itself
// - SKIP looking for x:DataType on the parent
// - continue looking for x:DataType on the parent's parent...
IElementNode skipNode = null;
if (IsBindingContextBinding(node))
{
skipNode = GetParent(node);
}

while (n != null)
{
if (n != skipNode && n.Properties.TryGetValue(XmlName.xDataType, out dataTypeNode))
{
break;
}

n = GetParent(n);
}
if (dataTypeNode is ValueNode valueNode)
BindingDataType = valueNode.Value as string;

}
public string BindingDataType { get; }
}
}
14 changes: 10 additions & 4 deletions src/Controls/tests/Xaml.UnitTests/BindingsCompiler.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ public class Tests
{
[SetUp] public void Setup() => DispatcherProvider.SetCurrent(new DispatcherProviderStub());
[TearDown] public void TearDown() => DispatcherProvider.SetCurrent(null);

[TestCase(false)]
[TestCase(true)]
public void Test(bool useCompiledXaml)

[Test]
public void TestCompiledBindings([Values(false, true)]bool useCompiledXaml)
{
if (useCompiledXaml)
MockCompiler.Compile(typeof(BindingsCompiler));
Expand Down Expand Up @@ -113,6 +112,13 @@ public void Test(bool useCompiledXaml)
layout.BindingContext = new object();
Assert.AreEqual(null, layout.label0.Text);
}

[Test]
public void BindingsNotAppliedWithWrongContext([Values(false, true)]bool useCompiledXaml)
{
var page = new BindingsCompiler(useCompiledXaml) { BindingContext = new {Text="Foo"} };
Assert.AreEqual(null, page.label0.Text);
}
}
}

Expand Down

0 comments on commit cdef2e5

Please sign in to comment.