[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
This commit is contained in:
Stephane Delcroix 2019-02-04 09:41:17 +01:00 коммит произвёл GitHub
Родитель 50d3499670
Коммит c71149c89b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 70 добавлений и 18 удалений

Просмотреть файл

@ -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 = @"
<ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
xmlns:local=""clr-namespace:Xamarin.Forms.Xaml.UnitTests;assembly=Xamarin.Forms.Xaml.UnitTests""
xmlns:missing=""clr-namespace:MissingNamespace;assembly=MissingAssembly"">
<StackLayout>
<local:InstantiateThrows />
<local:InstantiateThrows x:FactoryMethod=""CreateInstance"" />
<local:InstantiateThrows>
<x:Arguments>
<x:Int32>1</x:Int32>
</x:Arguments>
</local:InstantiateThrows>
<missing:Test>
<x:Arguments>
<x:Int32>1</x:Int32>
</x:Arguments>
</missing:Test>
</StackLayout>
</ContentPage>";
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();
}
}

Просмотреть файл

@ -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)));

Просмотреть файл

@ -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)

Просмотреть файл

@ -317,5 +317,6 @@ namespace Xamarin.Forms.Xaml
internal static Func<IList<FallbackTypeInfo>, Type, Type> FallbackTypeResolver { get; set; }
internal static Action<CallbackTypeInfo, object> ValueCreatedCallback { get; set; }
internal static Func<CallbackTypeInfo, object> InstantiationFailedCallback { get; set; }
}
}