From c71149c89b1dbd3e97d14d1583deb8697ed1dbeb Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Mon, 4 Feb 2019 09:41:17 +0100 Subject: [PATCH] [X] add callback for previewer on failing ctor (#5101) Add a callback when object instantiation or creation fails, so the previewer can replace it by an educated guess --- .../DesignTimeLoaderTests.cs | 37 +++++++++++++++++++ .../FactoryMethodMissingCtor.xaml.cs | 18 +++------ Xamarin.Forms.Xaml/CreateValuesVisitor.cs | 32 +++++++++++++--- Xamarin.Forms.Xaml/XamlLoader.cs | 1 + 4 files changed, 70 insertions(+), 18 deletions(-) diff --git a/Xamarin.Forms.Xaml.UnitTests/DesignTimeLoaderTests.cs b/Xamarin.Forms.Xaml.UnitTests/DesignTimeLoaderTests.cs index 4c1be6282..e21366894 100644 --- a/Xamarin.Forms.Xaml.UnitTests/DesignTimeLoaderTests.cs +++ b/Xamarin.Forms.Xaml.UnitTests/DesignTimeLoaderTests.cs @@ -22,6 +22,7 @@ namespace Xamarin.Forms.Xaml.UnitTests Device.PlatformServices = null; XamlLoader.FallbackTypeResolver = null; XamlLoader.ValueCreatedCallback = null; + XamlLoader.InstantiationFailedCallback = null; Xamarin.Forms.Internals.ResourceLoader.ExceptionHandler = null; } @@ -450,5 +451,41 @@ namespace Xamarin.Forms.Xaml.UnitTests Assert.That(myButton.BackgroundColor, Is.Not.EqualTo(Color.Blue)); } + + [Test] + public void CanReplaceTypeWhenInstantiationThrows() + { + var xaml = @" + + + + + + + 1 + + + + + 1 + + + + "; + XamlLoader.FallbackTypeResolver = (p, type) => type ?? typeof(Button); + XamlLoader.InstantiationFailedCallback = (type) => new Button(); + var o = XamlLoader.Create(xaml, true); + Assert.DoesNotThrow(() => XamlLoader.Create(xaml, true)); + } + } + + public class InstantiateThrows + { + public InstantiateThrows() => throw new InvalidOperationException(); + public static InstantiateThrows CreateInstance() => throw new InvalidOperationException(); + public InstantiateThrows(int value) => throw new InvalidOperationException(); } } \ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml.cs index 919d654bb..5fc3bf428 100644 --- a/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml.cs +++ b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml.cs @@ -7,11 +7,7 @@ namespace Xamarin.Forms.Xaml.UnitTests [XamlCompilation(XamlCompilationOptions.Skip)] public partial class FactoryMethodMissingCtor : MockView { - public FactoryMethodMissingCtor() - { - InitializeComponent(); - } - + public FactoryMethodMissingCtor() => InitializeComponent(); public FactoryMethodMissingCtor(bool useCompiledXaml) { //this stub will be replaced at compile time @@ -20,15 +16,11 @@ namespace Xamarin.Forms.Xaml.UnitTests [TestFixture] public class Tests { - [SetUp] - public void SetUp() - { - Device.PlatformServices = new MockPlatformServices(); - } + [SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices(); + [TearDown] public void TearDown() => Device.PlatformServices = null; - [TestCase(false)] - [TestCase(true)] - public void Throw(bool useCompiledXaml) + [Test] + public void Throw([Values(false, true)]bool useCompiledXaml) { if (useCompiledXaml) Assert.Throws(new XamlParseExceptionConstraint(7, 4), () => MockCompiler.Compile(typeof(FactoryMethodMissingCtor))); diff --git a/Xamarin.Forms.Xaml/CreateValuesVisitor.cs b/Xamarin.Forms.Xaml/CreateValuesVisitor.cs index f3b5684ae..e711cef11 100644 --- a/Xamarin.Forms.Xaml/CreateValuesVisitor.cs +++ b/Xamarin.Forms.Xaml/CreateValuesVisitor.cs @@ -80,8 +80,14 @@ namespace Xamarin.Forms.Xaml if (converted != null && converted.GetType() == type) value = converted; } - if (value == null) - value = Activator.CreateInstance(type); + if (value == null) { + try { + value = Activator.CreateInstance(type); + } + catch (TargetInvocationException tie) { + value = XamlLoader.InstantiationFailedCallback?.Invoke(new XamlLoader.CallbackTypeInfo { XmlNamespace = node.XmlType.NamespaceUri, XmlTypeName = node.XmlType.Name }) ?? throw tie; + } + } } catch (TargetInvocationException e) when (e.InnerException is XamlParseException || e.InnerException is XmlException) { throw e.InnerException; @@ -181,7 +187,13 @@ namespace Xamarin.Forms.Xaml ci.GetParameters().Length != 0 && ci.IsPublic && ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof (ParameterAttribute)))); object[] arguments = CreateArgumentsArray(node, ctorInfo); - return ctorInfo.Invoke(arguments); + try { + return ctorInfo.Invoke(arguments); + } + catch (Exception e) when (e is TargetInvocationException || e is MissingMemberException) { + return XamlLoader.InstantiationFailedCallback?.Invoke(new XamlLoader.CallbackTypeInfo { XmlNamespace = node.XmlType.NamespaceUri, XmlTypeName = node.XmlType.Name }) ?? throw e; + } + } public object CreateFromFactory(Type nodeType, IElementNode node) @@ -191,7 +203,12 @@ namespace Xamarin.Forms.Xaml if (!node.Properties.ContainsKey(XmlName.xFactoryMethod)) { //non-default ctor - return Activator.CreateInstance(nodeType, arguments); + try { + return Activator.CreateInstance(nodeType, arguments); + } + catch (Exception e) when (e is TargetInvocationException || e is MissingMemberException) { + return XamlLoader.InstantiationFailedCallback?.Invoke(new XamlLoader.CallbackTypeInfo { XmlNamespace = node.XmlType.NamespaceUri, XmlTypeName = node.XmlType.Name }) ?? throw e; + } } var factoryMethod = ((string)((ValueNode)node.Properties[XmlName.xFactoryMethod]).Value); @@ -219,7 +236,12 @@ namespace Xamarin.Forms.Xaml var mi = nodeType.GetRuntimeMethods().FirstOrDefault(isMatch); if (mi == null) throw new MissingMemberException($"No static method found for {nodeType.FullName}::{factoryMethod} ({string.Join(", ", types.Select(t => t.FullName))})"); - return mi.Invoke(null, arguments); + try { + return mi.Invoke(null, arguments); + } + catch (TargetInvocationException tie) { + return XamlLoader.InstantiationFailedCallback?.Invoke(new XamlLoader.CallbackTypeInfo { XmlNamespace = node.XmlType.NamespaceUri, XmlTypeName = node.XmlType.Name}) ?? throw tie; + } } public object[] CreateArgumentsArray(IElementNode enode) diff --git a/Xamarin.Forms.Xaml/XamlLoader.cs b/Xamarin.Forms.Xaml/XamlLoader.cs index ba0f188ec..4c0df353d 100644 --- a/Xamarin.Forms.Xaml/XamlLoader.cs +++ b/Xamarin.Forms.Xaml/XamlLoader.cs @@ -317,5 +317,6 @@ namespace Xamarin.Forms.Xaml internal static Func, Type, Type> FallbackTypeResolver { get; set; } internal static Action ValueCreatedCallback { get; set; } + internal static Func InstantiationFailedCallback { get; set; } } } \ No newline at end of file