dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.24k stars 1.76k forks source link

Simple ContentPropertyAttribute throws System.Reflection.TargetException #21118

Open mrlacey opened 8 months ago

mrlacey commented 8 months ago

Description

Trying to make a simple control that sets ContentProperty but this results in an exception at runtime

System.Reflection.TargetException
  HResult=0x80131603
  Message=Non-static method requires a target.
  Source=System.Private.CoreLib
  StackTrace:
   at System.Reflection.MethodBase.ValidateInvokeTarget(Object target)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.TryAddValue(BindableObject bindable, BindableProperty property, Object value, IServiceProvider serviceProvider, Exception& exception)
   at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.TrySetValue(Object element, BindableProperty property, Boolean attached, Object value, IXmlLineInfo lineInfo, IServiceProvider serviceProvider, Exception& exception)
   at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.TrySetPropertyValue(Object element, XmlName propertyName, String xKey, Object value, Object rootElement, IXmlLineInfo lineInfo, IServiceProvider serviceProvider, Exception& xpe)
   at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.SetPropertyValue(Object xamlelement, XmlName propertyName, Object value, Object rootElement, INode node, HydrationContext context, IXmlLineInfo lineInfo)
   at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.Visit(ElementNode node, INode parentNode)
   at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode)
   at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode)
   at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode)
   at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode)
   at Microsoft.Maui.Controls.Xaml.RootNode.Accept(IXamlNodeVisitor visitor, INode parentNode)
   at Microsoft.Maui.Controls.Xaml.XamlLoader.Visit(RootNode rootnode, HydrationContext visitorContext, Boolean useDesignProperties)
   at Microsoft.Maui.Controls.Xaml.XamlLoader.Load(Object view, String xaml, Assembly rootAssembly, Boolean useDesignProperties)
   at Microsoft.Maui.Controls.Xaml.XamlLoader.Load(Object view, String xaml, Boolean useDesignProperties)
   at Microsoft.Maui.Controls.Xaml.XamlLoader.Load(Object view, Type callingType)
   at Microsoft.Maui.Controls.Xaml.Extensions.LoadFromXaml[TXaml](TXaml view, Type callingType)
   at MauiContentPropertyIssueRepro.MainPage.InitializeComponent() in D:\source\MauiContentPropertyIssueRepro\MauiContentPropertyIssueRepro\Microsoft.Maui.Controls.SourceGen\Microsoft.Maui.Controls.SourceGen.CodeBehindGenerator\MainPage.xaml.sg.cs:line 26
   at MauiContentPropertyIssueRepro.MainPage..ctor() in D:\source\MauiContentPropertyIssueRepro\MauiContentPropertyIssueRepro\MainPage.xaml.cs:line 9
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)

Steps to Reproduce

  1. Create a new project

  2. Add this class:

[ContentProperty(name: "Children")]
public class MyContainer : ContentView
{
    public VerticalStackLayout vsl;

    public MyContainer()
    {
        vsl = new VerticalStackLayout();

        base.Content = vsl; ;
    }

    public IList<IView> Children
    {
        get => (IList<IView>)GetValue(ChildrenProperty);
        set => SetValue(ChildrenProperty, value);
    }

    public static readonly BindableProperty ChildrenProperty =
BindableProperty.Create(nameof(Children), typeof(IList<IView>), typeof(MyContainer), null, propertyChanged: ChildrenChanged);

    private static void ChildrenChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (bindable is MyContainer b)
        {
            if (newValue is IView newView)
            {
                b.vsl.Children.Add(newView);
            }
        }
    }
}
  1. Use it in a XAML file. Like this:
    <local:MyContainer>
        <Label>one</Label>
        <Label>two</Label>
    </local:MyContainer>
  1. See it compiles ok.
  2. Run the app: get exception.
  3. Be sad

Link to public reproduction project repository

No response

Version with bug

7.0.101

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

7.0.101

Affected platforms

Android, Windows, I was not able test on other platforms

Affected platform versions

No response

Did you find any workaround?

Nope. :(

Relevant log output

No response

dotMorten commented 8 months ago

A lot of this sample makes me go "uhm.....". First of all the Children property hides the baseclass children property. Second, it seems like you're making a Layout control, but you're not inheriting from Layout or perhaps "TemplatedView". Lastly the Children property is null, so there's nothing to add the items to and you don't instantiate the collection in xaml. Having said that, here's a quick fix for just that last bit by just returning the Children of vsl, and your sample will run:

    [ContentProperty(name: "Children")]
    public class MyContainer : ContentView
    {
        public VerticalStackLayout vsl;

        public MyContainer()
        {
            vsl = new VerticalStackLayout();
            base.Content = vsl;
        }
        public IList<IView> Children => vsl.Children;
    }

image

The Layout class is a good reference implementation of how to have children: https://github.com/dotnet/maui/blob/main/src/Controls/src/Core/Layout/Layout.cs#L34C3-L40C40

mrlacey commented 8 months ago

My original repro was a simplified version of what I'm actually doing. The hiding of the property in the base class doesn't happen in my "real" code, and I hadn't noticed this issue in (what I thought was) my simplified version.

I still have old code that is based on the above that does work without issue. The same code in another project (that, as far as I can tell, has no tangible differences) doesn't work. I'd still like to know how the error message can point to an explanation, or at least more details about what's happening with the generated code.

However, @dotMorten, I can use your solution to get around my current blocker. Thank you. 😄