diff --git a/src/Controls/src/Core/Routing.cs b/src/Controls/src/Core/Routing.cs
index 8a5104be80b6..f8c3cd690f18 100644
--- a/src/Controls/src/Core/Routing.cs
+++ b/src/Controls/src/Core/Routing.cs
@@ -236,6 +236,49 @@ public static void SetRoute(Element obj, string value)
obj.SetValue(RouteProperty, value);
}
+ internal static void ValidateForDuplicates(Element element, string route)
+ {
+ // If setting the same route to the same element, no need to validate
+ var currentRoute = GetRoute(element);
+ if (currentRoute == route)
+ {
+ return;
+ }
+
+ // Only validate user-defined routes
+ if (string.IsNullOrEmpty(route) || !IsUserDefined(route))
+ {
+ return;
+ }
+
+ // Check for duplicate routes among siblings (elements with the same parent)
+ var parent = element.Parent;
+ if (parent == null)
+ {
+ return;
+ }
+
+ foreach (var child in parent.LogicalChildrenInternal)
+ {
+ if (child == element)
+ continue;
+
+ var siblingRoute = GetRoute(child);
+ if (siblingRoute == route)
+ {
+ throw new ArgumentException(
+ $"Duplicated Route: \"{route}\" is already registered to another element of type {child.GetType().Name}. " +
+ $"Routes must be unique among siblings to avoid navigation conflicts.",
+ nameof(route));
+ }
+ }
+ }
+
+ internal static void RemoveElementRoute(Element element)
+ {
+ // No longer needed with sibling-based validation, but keep for API compatibility
+ }
+
static void ValidateRoute(string route, RouteFactory routeFactory)
{
if (string.IsNullOrWhiteSpace(route))
diff --git a/src/Controls/src/Core/Shell/BaseShellItem.cs b/src/Controls/src/Core/Shell/BaseShellItem.cs
index 74f003f0dba3..1d8ace61d754 100644
--- a/src/Controls/src/Core/Shell/BaseShellItem.cs
+++ b/src/Controls/src/Core/Shell/BaseShellItem.cs
@@ -103,7 +103,11 @@ public bool IsEnabled
public string Route
{
get { return Routing.GetRoute(this); }
- set { Routing.SetRoute(this, value); }
+ set
+ {
+ Routing.ValidateForDuplicates(this, value);
+ Routing.SetRoute(this, value);
+ }
}
///
diff --git a/src/Controls/tests/Core.UnitTests/ShellTests.cs b/src/Controls/tests/Core.UnitTests/ShellTests.cs
index e6713c88d94e..a54c61bd9c89 100644
--- a/src/Controls/tests/Core.UnitTests/ShellTests.cs
+++ b/src/Controls/tests/Core.UnitTests/ShellTests.cs
@@ -1674,5 +1674,123 @@ public void ShellContentTitleShouldNotBeAppliedMultipleTimesWithStringFormat()
Assert.Equal("Title: Hello, World!", shellContent.Title);
}
+
+ [Fact]
+ public void DuplicateSiblingRoutesShouldThrowArgumentException()
+ {
+ var shell = new Shell();
+ var sameRoute = "DuplicateRoute";
+
+ var flyoutItem = new FlyoutItem();
+ var shellSection = new ShellSection();
+ var shellContent1 = new ShellContent { Title = "Page1", Content = new ContentPage() };
+ var shellContent2 = new ShellContent { Title = "Page2", Content = new ContentPage() };
+ shellSection.Items.Add(shellContent1);
+ shellSection.Items.Add(shellContent2);
+ flyoutItem.Items.Add(shellSection);
+ shell.Items.Add(flyoutItem);
+
+ shellContent1.Route = sameRoute;
+ var exception = Assert.Throws(() =>
+ {
+ shellContent2.Route = sameRoute;
+ });
+
+ Assert.Equal($"Duplicated Route: \"{sameRoute}\" is already registered to another element of type ShellContent. Routes must be unique among siblings to avoid navigation conflicts. (Parameter 'route')", exception.Message);
+ }
+
+ [Fact]
+ public void SameRouteInDifferentParentsIsAllowed()
+ {
+ var shell = new Shell();
+ var sameRoute = "SharedRoute";
+
+ // Create two different ShellSections, each with their own ShellContent
+ var flyoutItem = new FlyoutItem();
+ var shellSection1 = new ShellSection();
+ var shellSection2 = new ShellSection();
+ var shellContent1 = new ShellContent { Title = "Page1", Content = new ContentPage() };
+ var shellContent2 = new ShellContent { Title = "Page2", Content = new ContentPage() };
+
+ shellSection1.Items.Add(shellContent1);
+ shellSection2.Items.Add(shellContent2);
+ flyoutItem.Items.Add(shellSection1);
+ flyoutItem.Items.Add(shellSection2);
+ shell.Items.Add(flyoutItem);
+
+ // Both should be able to have the same route since they're in different parents
+ shellContent1.Route = sameRoute;
+ shellContent2.Route = sameRoute; // Should not throw - different parents
+
+ Assert.Equal(sameRoute, shellContent1.Route);
+ Assert.Equal(sameRoute, shellContent2.Route);
+ }
+
+ [Fact]
+ public void ChangingRouteAllowsReuseAmongSiblings()
+ {
+ var shell = new Shell();
+ var route = "TestRoute";
+
+ var flyoutItem = new FlyoutItem();
+ var shellSection = new ShellSection();
+ var shellContent1 = new ShellContent { Title = "Page1", Content = new ContentPage() };
+ var shellContent2 = new ShellContent { Title = "Page2", Content = new ContentPage() };
+ shellSection.Items.Add(shellContent1);
+ shellSection.Items.Add(shellContent2);
+ flyoutItem.Items.Add(shellSection);
+ shell.Items.Add(flyoutItem);
+
+ // Set initial route
+ shellContent1.Route = route;
+
+ // Change the route to something else
+ shellContent1.Route = "NewRoute";
+
+ // Now the original route should be available for the sibling
+ shellContent2.Route = route; // Should not throw
+ Assert.Equal(route, shellContent2.Route);
+ }
+
+ [Fact]
+ public void RemovingElementClearsRoute()
+ {
+ var shell = new Shell();
+ var route = "RemovableRoute";
+
+ var flyoutItem = new FlyoutItem();
+ var shellSection = new ShellSection();
+ var shellContent1 = new ShellContent { Title = "Page1", Content = new ContentPage() };
+ var shellContent2 = new ShellContent { Title = "Page2", Content = new ContentPage() };
+ shellSection.Items.Add(shellContent1);
+ shellSection.Items.Add(shellContent2);
+ flyoutItem.Items.Add(shellSection);
+ shell.Items.Add(flyoutItem);
+
+ shellContent1.Route = route;
+
+ // Remove the element from ShellSection.Items
+ shellSection.Items.Remove(shellContent1);
+
+ // Now the route should be available for another element
+ shellContent2.Route = route; // Should not throw
+ Assert.Equal(route, shellContent2.Route);
+ }
+
+ [Fact]
+ public void ReassigningSameRouteToSameElementDoesNotThrow()
+ {
+ var shell = new Shell();
+ var route = "SameRoute";
+
+ var flyoutItem = new FlyoutItem();
+ var shellContent = new ShellContent { Title = "Page" };
+ flyoutItem.Items.Add(shellContent);
+ shell.Items.Add(flyoutItem);
+
+ shellContent.Route = route;
+ shellContent.Route = route; // Should not throw - same element, same route
+ Assert.Equal(route, shellContent.Route);
+ }
}
}
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/XFIssue/Issue6878.cs b/src/Controls/tests/TestCases.HostApp/Issues/XFIssue/Issue6878.cs
index 2cd660260a5a..6f3feec88c6a 100644
--- a/src/Controls/tests/TestCases.HostApp/Issues/XFIssue/Issue6878.cs
+++ b/src/Controls/tests/TestCases.HostApp/Issues/XFIssue/Issue6878.cs
@@ -28,7 +28,6 @@ protected override void Init()
CurrentItem = Items.Last();
- AddTopTab(TopTab);
AddBottomTab("Bottom tab");
}