Abanoub Zak
Abanoub Zak

Reputation: 205

Issue with Binding and RelativeSource in .NET MAUI 9 - XamlCompilationOptions.Skip Needed for Release Build

I'm working on a .NET MAUI 9 application and running into a problem with binding when using RelativeSource while setting x:DataType in XAML. Specifically, when I try to bind a command in my ContentPage to a button using RelativeSource, I get an error during the release build. The error mentions "Invalid IL code" during InitializeComponent unless I set XamlCompilationOptions.Skip.

Here's the code I am working with:

    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="entities:Person">
            <!--  Person Frame  -->
            <Border
                Margin="{StaticResource PersonFrameMargin}"
                Padding="{StaticResource PersonFramePadding}"
                Background="{StaticResource LightGradientBackground}">
                <Border.StrokeShape>
                    <RoundRectangle CornerRadius="{StaticResource PersonFrameRadius}" />
                </Border.StrokeShape>
                <!--  Person Grid  -->
                <Grid Margin="{StaticResource PersonGridMargin}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="auto" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="auto" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto" />
                        <RowDefinition Height="auto" />
                    </Grid.RowDefinitions>
                    <!--  Circular Person Image  -->
                    <Border
                        Grid.Column="0"
                        Margin="{StaticResource PersonImageMargin}"
                        Background="{StaticResource LightGradientBackground}"
                        HeightRequest="{StaticResource PersonImageSize}"
                        HorizontalOptions="Center"
                        VerticalOptions="Start"
                        WidthRequest="{StaticResource PersonImageSize}">
                        <Border.StrokeShape>
                            <RoundRectangle CornerRadius="{StaticResource PersonImageCornerRadius}" />
                        </Border.StrokeShape>
                        <Image
                            Aspect="AspectFill"
                            HeightRequest="{StaticResource PersonImageSize}"
                            WidthRequest="{StaticResource PersonImageSize}">
                            <Image.Source>
                                <MultiBinding Converter="{StaticResource ByteArrayAndGenderToImageSourceConverter}">
                                    <Binding Path="Image.Value" />
                                    <Binding Path="Gender" />
                                </MultiBinding>
                            </Image.Source>
                        </Image>
                    </Border>
                    <!--  Person Name And Date StackLayout  -->
                    <StackLayout Grid.Column="1" Spacing="{StaticResource PersonNameAndDateStackLayoutSpacing}">
                        <Label
                            FontAttributes="Bold"
                            FontSize="Large"
                            LineBreakMode="WordWrap"
                            Text="{Binding FullName}" />
                        <Label FontSize="Medium" Text="{Binding LastInquiringDate.Value, StringFormat={StaticResource PersonDateFormat}}" />
                    </StackLayout>
                    <!--  Person Round Edit Button  -->
                    <Button
                        Grid.Column="2"
                        Margin="{StaticResource PersonRoundEditButtonMargin}"
                        Command="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.EditPersonCommand}"
                        CommandParameter="{Binding Id}"
                        CornerRadius="{StaticResource PersonRoundEditButtonRadius}"
                        HeightRequest="{StaticResource PersonRoundEditButtonSize}"
                        ImageSource="pencil.png"
                        VerticalOptions="Start"
                        WidthRequest="{StaticResource PersonRoundEditButtonSize}" />

                    <!--  Phone Numbers StackLayout  -->
                    <StackLayout
                        Grid.Row="1"
                        Grid.Column="0"
                        Grid.ColumnSpan="3">
                        <BoxView
                            Margin="{StaticResource LineMargin}"
                            Background="{StaticResource White}"
                            HeightRequest="{StaticResource LineSize}" />
                        <!--  Phone Numbers Label  -->
                        <Label FontSize="Large" Text="Phone Numbers:" />
                        <!--  Phone Numbers StackLayout  -->
                        <CollectionView ItemsSource="{Binding PhoneNumbers}" SelectionMode="None">
                            <CollectionView.ItemTemplate>
                                <DataTemplate x:DataType="entities:PhoneNumber">
                                    <!--  Phone Grid  -->
                                    <Grid Margin="{StaticResource PhoneGridMargin}">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="*" />
                                            <ColumnDefinition Width="auto" />
                                        </Grid.ColumnDefinitions>
                                        <!--  Phone Numbers  -->
                                        <Label
                                            Grid.Column="0"
                                            FontAttributes="Bold"
                                            FontSize="Medium"
                                            Text="{Binding FullNumber}"
                                            VerticalOptions="Center" />
                                        <!--  WhatsApp Button  -->
                                        <ImageButton
                                            Grid.Column="1"
                                            Command="{Binding Path=BindingContext.SendWhatsAppMessagesCommand, Source={RelativeSource AncestorType={x:Type ContentPage}}}"
                                            CommandParameter="{Binding}"
                                            HeightRequest="{StaticResource WhatsAppButtonSize}"
                                            HorizontalOptions="End"
                                            Source="whatsapp_icon.png"
                                            WidthRequest="{StaticResource WhatsAppButtonSize}" />
                                    </Grid>
                                </DataTemplate>
                            </CollectionView.ItemTemplate>
                        </CollectionView>
                    </StackLayout>
                </Grid>
            </Border>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

More specifically the Edit button:

<!--  Person Round Edit Button  -->
                    <Button
                        Grid.Column="2"
                        Margin="{StaticResource PersonRoundEditButtonMargin}"
                        Command="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.EditPersonCommand}"
                        CommandParameter="{Binding Id}"
                        CornerRadius="{StaticResource PersonRoundEditButtonRadius}"
                        HeightRequest="{StaticResource PersonRoundEditButtonSize}"
                        ImageSource="pencil.png"
                        VerticalOptions="Start"
                        WidthRequest="{StaticResource PersonRoundEditButtonSize}" />

The problem arises because I need to use RelativeSource to bind a command from the ContentPage's BindingContext, but I also have x:DataType set for type safety and the need to pass the Person's Id along with the command. When I don't disable XAML compilation by using:

<MauiEnableXamlCBindingWithSourceCompilation>false</MauiEnableXamlCBindingWithSourceCompilation>

The solution won't build and throws XC0045 error.

Steps to reproduce:

  1. Set x:DataType on the ContentPage.
  2. Bind a command from the BindingContext of an ancestor element using RelativeSource like shown above.
  3. Attempt to build the project in release mode.

What I've tried:

<TrimMode>partial</TrimMode>
<Optimize>false</Optimize>
<AndroidLinkMode>None</AndroidLinkMode>

However, these changes did not resolve the issue.

Expected behavior: The binding should work without needing to disable XAML compilation and should allow the command to be bound correctly from the ViewModel.

Question: Is there a way to correctly use RelativeSource binding with x:DataType in .NET MAUI 9 without encountering the "Invalid IL code" error in release builds? If not, is there a workaround to avoid disabling XAML compilation?

Update:

Using inline x:DataType as Jon Skeet suggested does remove the XC0045 error when building.

I will update the solution and test the release build again, and then I will update this question accordingly.

Upvotes: 4

Views: 1003

Answers (3)

Hewit HN
Hewit HN

Reputation: 21

I am facing the issue too, after upgrading to .netcore 9.0 and publishing to store. With error:

Invalid IL code in InitializeComponent (): IL_744e: stloc

I have to move <sys:Double x:Key="FlyoutItemIconSize">22</sys:Double> to the global resources/application resources:

<Application.Resources>
    <ResourceDictionary>
        <sys:Double x:Key="FlyoutItemIconSize">22</sys:Double>
    </ResourceDictionary>
</Application.Resources>

Then it is OK.

Upvotes: 0

Abanoub Zak
Abanoub Zak

Reputation: 205

The issue I encountered in my .NET MAUI 9 application was caused by two separate problems, both related to binding and XAML compilation. I will break down the solutions for each problem below:

Problem 1: Invalid IL Code Error

The first issue was the "Invalid IL Code" error that occurred during the release build. This problem was caused by using StaticResource bindings for properties like:

<Thickness x:Key="RoundButtonsMargin">5,10</Thickness>
<x:Double x:Key="RoundButtonsSize">50</x:Double>
<x:Int32 x:Key="RoundButtonsRadius">25</x:Int32>

and binding them like this:

<Button
    Grid.Column="1"
    Margin="{StaticResource RoundButtonsMargin}"
    Command="{Binding CancelCommand}"
    CornerRadius="{StaticResource RoundButtonsRadius}"
    HeightRequest="{StaticResource RoundButtonsSize}"
    ImageSource="cancel.png"
    WidthRequest="{StaticResource RoundButtonsSize}" />

Solution to Invalid IL Code Error:

The fix for this issue was removing the usage of the static resources (RoundButtonsMargin and RoundButtonsRadius). Once I removed these static resource bindings, the "Invalid IL Code" error during the release build was resolved. After making this change, the app started successfully without throwing the error at runtime.

I still might need to do more tests to see if the issue relates to using mismatched data types (e.g., using Single instead of Double).


Problem 2: XC0045 Error when Building Solution

The second issue I encountered was the XC0045 error when building the solution, which was related to the use of x:DataType in conjunction with RelativeSource.

Solution to XC0045 Error:

To resolve this error, I followed Jon Skeet's suggestion to use inline x:DataType within the Path instead of separately binding it with AncestorType only, which fixed the error. Jon mentioned that when using RelativeSource with an AncestorType that isn’t element-derived, MAUI automatically uses the FindAncestorBindingContext mode. He recommended binding directly to the view model instead of accessing the BindingContext through RelativeSource, and using inline x:DataType for clarity and to resolve the issue.

Here's how I modified the code:

Instead of:

<Button
    Grid.Column="2"
    Margin="{StaticResource PersonRoundEditButtonMargin}"
    Command="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.EditPersonCommand}"
    CommandParameter="{Binding Id}"
    CornerRadius="{StaticResource PersonRoundEditButtonRadius}"
    HeightRequest="{StaticResource PersonRoundEditButtonSize}"
    ImageSource="pencil.png"
    VerticalOptions="Start"
    WidthRequest="{StaticResource PersonRoundEditButtonSize}" />

I used:

<Button
    Grid.Column="2"
    Margin="{StaticResource PersonRoundEditButtonMargin}"
    Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:OverdueInquiringViewModel}}, Path=EditPersonCommand, x:DataType=viewmodels:OverdueInquiringViewModel}"
    CommandParameter="{Binding Id}"
    CornerRadius="{StaticResource PersonRoundEditButtonRadius}"
    HeightRequest="{StaticResource PersonRoundEditButtonSize}"
    ImageSource="pencil.png"
    VerticalOptions="Start"
    WidthRequest="{StaticResource PersonRoundEditButtonSize}" />

Using inline x:DataType for the Command binding resolved the XC0045 error and allowed the solution to build successfully.


Summary:

  1. Invalid IL Code Error: The issue was caused by using static resources for properties like RoundButtonsMargin and RoundButtonsRadius. Removing these bindings resolved the error.
  2. XC0045 Error: The solution was to use inline x:DataType for the Command binding, as suggested by Jon Skeet.

With these two changes, my app now builds correctly in release mode without requiring XamlCompilationOptions.Skip.

Upvotes: 3

Jon Skeet
Jon Skeet

Reputation: 1503080

I haven't seen the exact problem you have (and the fact that you end up with invalid IL at the moment suggests this is worth reporting as a bug), but I'd suggest binding directly to your view model instead of going via the BindingContext, in your Command binding. I only learned about this yesterday, but if you use RelativeSource with an AncestorType which isn't an Element-derived class, MAUI automatically uses a mode of FindAncestorBindingContext. You can set the data type in the binding itself to indicate the type you're trying to bind to. It's slightly annoying to put the same type twice, but it works.

So for example, your binding would be (split over multiple lines here just for clarity, but obviously it all needs to be in the same line in your XAML):

Command="{Binding
    Source={RelativeSource AncestorType={x:Type local:YourViewModelType}}, 
    Path=EditPersonCommand,
    x:DataType=local:YourViewModelType
    }"

Upvotes: 7

Related Questions