Skip to content

Commit eae32ef

Browse files
kubafloPureWeen
authored andcommitted
[Shell] Fix InvalidCastException when using QueryPropertyAttribute with nullable types (#33429)
> [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description Fixes #33420 This PR fixes a that occurs when using with nullable types during Shell navigation. ## Root Cause In `ShellContent.ApplyQueryAttributes`, the code used `Convert.ChangeType(value, prop.PropertyType)` which fails when the property type is nullable (e.g., `long?`, `int?`) because `Convert.ChangeType` does not handle `Nullable<T>` types directly. When a user navigates with a nullable parameter like: ```csharp Dictionary<string, object> param = new() { { nameof(DetailsPage.ID), (long?)1 } }; await Shell.Current.GoToAsync(nameof(DetailsPage), param); ``` And the destination page has: ```csharp [QueryProperty(nameof(ID), nameof(ID))] public partial class DetailsPage : ContentPage { public long? ID { get; set; } } ``` The app would crash with `InvalidCastException`. ## Solution Use `Nullable.GetUnderlyingType()` to extract the underlying type before conversion: 1. Check if the property type is nullable using `Nullable.GetUnderlyingType()` 2. If nullable, extract the underlying type (e.g., `long` from `long?`) 3. Convert the value to the underlying type 4. Assign (automatic wrapping in nullable occurs) 5. Handle null values explicitly ## Changes **Fix:** - `src/Controls/src/Core/Shell/ShellContent.cs` - Added nullable type handling in `ApplyQueryAttributes` **Tests:** - Added UI test `Issue33420` that reproduces the issue and verifies the fix - Test navigates with a nullable `long?` parameter and verifies navigation succeeds ## Testing ✅ Tests fail without the fix (reproduces the crash) ✅ Tests pass with the fix (navigation works) ✅ Verified on iOS simulator ## Platforms Affected All platforms (iOS, Android, Windows, MacCatalyst) - Shell navigation code is shared. ---------
1 parent 30ecf6a commit eae32ef

4 files changed

Lines changed: 148 additions & 2 deletions

File tree

‎src/Controls/src/Core/Shell/ShellContent.cs‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,18 @@ static void ApplyQueryAttributes(object content, ShellRouteParameters query, She
416416
}
417417
else
418418
{
419-
var castValue = Convert.ChangeType(value, prop.PropertyType);
420-
prop.SetValue(content, castValue);
419+
// Handle nullable types
420+
Type targetType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
421+
422+
if (value == null)
423+
{
424+
prop.SetValue(content, null);
425+
}
426+
else
427+
{
428+
var castValue = Convert.ChangeType(value, targetType);
429+
prop.SetValue(content, castValue);
430+
}
421431
}
422432
}
423433
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
xmlns:local="clr-namespace:Maui.Controls.Sample.Issues"
5+
x:Class="Maui.Controls.Sample.Issues.Issue33420"
6+
Shell.NavBarIsVisible="False">
7+
8+
<ShellContent Route="MainPage" ContentTemplate="{DataTemplate local:Issue33420MainPage}" />
9+
10+
</Shell>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
namespace Maui.Controls.Sample.Issues
2+
{
3+
[Issue(IssueTracker.Github, 33420, "System.InvalidCastException when using QueryPropertyAttribute with nullable types", PlatformAffected.All)]
4+
public partial class Issue33420 : Shell
5+
{
6+
public Issue33420()
7+
{
8+
InitializeComponent();
9+
10+
Routing.RegisterRoute(nameof(Issue33420DetailsPage), typeof(Issue33420DetailsPage));
11+
}
12+
}
13+
14+
public class Issue33420MainPage : ContentPage
15+
{
16+
public Issue33420MainPage()
17+
{
18+
var button = new Button
19+
{
20+
Text = "Navigate with Nullable",
21+
AutomationId = "NavigateButton"
22+
};
23+
24+
button.Clicked += async (s, e) =>
25+
{
26+
try
27+
{
28+
var param = new Dictionary<string, object>
29+
{
30+
{ nameof(Issue33420DetailsPage.ID), (long?)1 }
31+
};
32+
await Shell.Current.GoToAsync(nameof(Issue33420DetailsPage), param);
33+
}
34+
catch (Exception ex)
35+
{
36+
// If navigation fails, update button text to indicate error
37+
button.Text = $"Error: {ex.GetType().Name}";
38+
}
39+
};
40+
41+
Content = new VerticalStackLayout
42+
{
43+
Children =
44+
{
45+
new Label
46+
{
47+
Text = "Test nullable QueryProperty navigation",
48+
AutomationId = "MainLabel"
49+
},
50+
button
51+
},
52+
Padding = 10,
53+
Spacing = 10
54+
};
55+
}
56+
}
57+
58+
[QueryProperty(nameof(ID), nameof(ID))]
59+
public class Issue33420DetailsPage : ContentPage
60+
{
61+
private readonly Label _resultLabel;
62+
63+
public long? ID
64+
{
65+
get => (long?)GetValue(IDProperty);
66+
set => SetValue(IDProperty, value);
67+
}
68+
69+
public static readonly BindableProperty IDProperty =
70+
BindableProperty.Create(nameof(ID), typeof(long?), typeof(Issue33420DetailsPage), null, propertyChanged: OnIDChanged);
71+
72+
private static void OnIDChanged(BindableObject bindable, object oldValue, object newValue)
73+
{
74+
if (bindable is Issue33420DetailsPage page)
75+
{
76+
page._resultLabel.Text = $"Success: ID={newValue}";
77+
}
78+
}
79+
80+
public Issue33420DetailsPage()
81+
{
82+
_resultLabel = new Label
83+
{
84+
Text = "Waiting for navigation...",
85+
AutomationId = "ResultLabel"
86+
};
87+
88+
Content = new VerticalStackLayout
89+
{
90+
Children = { _resultLabel },
91+
Padding = 10
92+
};
93+
}
94+
}
95+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using NUnit.Framework;
2+
using UITest.Appium;
3+
using UITest.Core;
4+
5+
namespace Microsoft.Maui.TestCases.Tests.Issues;
6+
7+
public class Issue33420 : _IssuesUITest
8+
{
9+
public override string Issue => "System.InvalidCastException when using QueryPropertyAttribute with nullable types";
10+
11+
public Issue33420(TestDevice device) : base(device) { }
12+
13+
[Test]
14+
[Category(UITestCategories.Shell)]
15+
public void NullableQueryPropertyNavigationShouldNotCrash()
16+
{
17+
// Wait for main page to load
18+
App.WaitForElement("MainLabel");
19+
20+
// Tap navigation button with nullable parameter
21+
App.Tap("NavigateButton");
22+
23+
// If the bug is present, the app will crash
24+
// If fixed, we should navigate successfully to the details page
25+
App.WaitForElement("ResultLabel");
26+
27+
// Verify the nullable value was passed correctly
28+
var resultText = App.FindElement("ResultLabel").GetText();
29+
Assert.That(resultText, Is.EqualTo("Success: ID=1"));
30+
}
31+
}

0 commit comments

Comments
 (0)