}
[Test]
- public async Task GlobalRegisterAbsoluteMatching()
+ public async Task RouteWithGlobalPageRoute()
+ {
+
+ var shell = new Shell() { RouteScheme = "app", Route= "xaminals", RouteHost = "thehost" };
+ var item1 = CreateShellItem(asImplicit: true, shellItemRoute: "animals", shellSectionRoute: "domestic", shellContentRoute: "dogs");
+ var item2 = CreateShellItem(asImplicit: true, shellItemRoute: "animals", shellSectionRoute: "domestic", shellContentRoute: "cats");
+
+ shell.Items.Add(item1);
+ shell.Items.Add(item2);
+
+ Routing.RegisterRoute("catdetails", typeof(ContentPage));
+ await shell.GoToAsync("//cats/catdetails?name=3");
+
+ Assert.AreEqual("app://thehost/xaminals/animals/domestic/cats/catdetails", shell.CurrentState.Location.ToString());
+ }
+
+ [Test]
+ public async Task AbsoluteRoutingToPage()
+ {
+
+ var shell = new Shell() { RouteScheme = "app", Route = "xaminals", RouteHost = "thehost" };
+ var item1 = CreateShellItem(asImplicit: true, shellItemRoute: "animals", shellSectionRoute: "domestic", shellContentRoute: "dogs");
+ shell.Items.Add(item1);
+
+ Routing.RegisterRoute("catdetails", typeof(ContentPage));
+
+ Assert.That(async () => await shell.GoToAsync($"//catdetails"), Throws.Exception);
+ }
+
+
+ [Test]
+ public async Task LocationRemovesImplicit()
+ {
+
+ var shell = new Shell() { RouteScheme = "app" };
+ var item1 = CreateShellItem(asImplicit: true, shellContentRoute: "rootlevelcontent1");
+
+ shell.Items.Add(item1);
+
+ Assert.AreEqual("app:///rootlevelcontent1", shell.CurrentState.Location.ToString());
+ }
+
+
+ [Test]
+ public async Task GlobalRegisterAbsoluteMatching()
{
var shell = new Shell() { RouteScheme = "app", Route = "shellroute" };
Routing.RegisterRoute("/seg1/seg2/seg3", typeof(object));
- var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("app://seg1/seg2/seg3"));
+ var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("/seg1/seg2/seg3"));
- Assert.AreEqual("/seg1/seg2/seg3", request.Request.ShortUri.ToString());
+ Assert.AreEqual("app:///shellroute/seg1/seg2/seg3", request.Request.FullUri.ToString());
}
[Test]
shell.Items.Add(item1);
shell.Items.Add(item2);
await shell.GoToAsync("//item1/section1/rootlevelcontent1");
- var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("section1/edit"));
+ var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("section1/edit"), true);
Assert.AreEqual(1, request.Request.GlobalRoutes.Count);
Assert.AreEqual("item1/section1/edit", request.Request.GlobalRoutes.First());
shell.Items.Add(item1);
await shell.GoToAsync("//rootlevelcontent1");
- var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("edit"));
+ var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("edit"), true);
Assert.AreEqual("section1/edit", request.Request.GlobalRoutes.First());
}
shell.Items.Add(item1);
await shell.GoToAsync("//rootlevelcontent1");
- var location = shell.CurrentState.Location;
- await shell.GoToAsync("edit");
+ var location = shell.CurrentState.FullLocation;
+ await shell.GoToAsync("edit", false, true);
Assert.AreEqual(editShellContent, shell.CurrentItem.CurrentItem.CurrentItem);
}
[Test]
+ public async Task AbsoluteNavigationToRelativeWithGlobal()
+ {
+ var shell = new Shell() { RouteScheme = "app", RouteHost = "xamarin.com", Route = "xaminals" };
+
+ var item1 = CreateShellItem(asImplicit: true, shellContentRoute: "dogs");
+ var item2 = CreateShellItem(asImplicit: true, shellSectionRoute: "domestic", shellContentRoute: "cats", shellItemRoute: "animals");
+
+ shell.Items.Add(item1);
+ shell.Items.Add(item2);
+
+ Routing.RegisterRoute("catdetails", typeof(ContentPage));
+ await shell.GoToAsync($"app://xamarin.com/xaminals/animals/domestic/cats/catdetails?name=domestic");
+
+ Assert.AreEqual(
+ "app://xamarin.com/xaminals/animals/domestic/cats/catdetails",
+ shell.CurrentState.FullLocation.ToString()
+ );
+ }
+
+ [Test]
+ public async Task RelativeNavigationWithRoute()
+ {
+ var shell = new Shell() { RouteScheme = "app", RouteHost = "xamarin.com", Route = "xaminals" };
+
+ var item1 = CreateShellItem(asImplicit: true, shellContentRoute: "dogs");
+ var item2 = CreateShellItem(asImplicit: true, shellSectionRoute: "domestic", shellContentRoute: "cats", shellItemRoute: "animals");
+
+ shell.Items.Add(item1);
+ shell.Items.Add(item2);
+
+ Routing.RegisterRoute("catdetails", typeof(ContentPage));
+ Assert.That(async () => await shell.GoToAsync($"cats/catdetails?name=domestic"), Throws.Exception);
+
+ // once relative routing with a stack is fixed then we can remove the above exception check and add below back in
+ // await shell.GoToAsync($"cats/catdetails?name=domestic")
+ //Assert.AreEqual(
+ // "app://xamarin.com/xaminals/animals/domestic/cats/catdetails",
+ // shell.CurrentState.Location.ToString()
+ // );
+
+ }
+
+
+ [Test]
public async Task ConvertToStandardFormat()
{
var shell = new Shell() { RouteScheme = "app", Route = "shellroute", RouteHost = "host" };
CreateUri("app://shellroute/path"),
CreateUri("app:/shellroute/path"),
CreateUri("app://host/shellroute/path"),
- CreateUri("app:/host/shellroute/path")
+ CreateUri("app:/host/shellroute/path"),
+ CreateUri("app:/host/shellroute\\path")
};
internal class ShellUriHandler
{
- static readonly char[] _pathSeparator = { '/', '\\' };
+ static readonly char[] _pathSeparators = { '/', '\\' };
+ const string _pathSeparator = "/";
- static Uri FormatUri(Uri path)
+ internal static Uri FormatUri(Uri path)
{
if (path.IsAbsoluteUri)
- return path;
+ return new Uri(FormatUri(path.OriginalString), UriKind.Absolute);
return new Uri(FormatUri(path.OriginalString), UriKind.Relative);
}
- static string FormatUri(string path)
+ internal static string FormatUri(string path)
{
- return path.Replace("\\", "/");
+ return path.Replace(_pathSeparators[1], _pathSeparator[0]);
+ }
+
+ internal static Uri CreateUri(string path)
+ {
+ path = FormatUri(path);
+
+ // on iOS if the uri starts with // it'll instantiate as absolute with
+ // file: as the default scheme where as android just crashes
+ // so this checks if it starts with / and just forces relative
+ if (path.StartsWith(_pathSeparator, StringComparison.Ordinal))
+ return new Uri(path, UriKind.Relative);
+
+ if (Uri.TryCreate(path, UriKind.Absolute, out Uri result))
+ return result;
+
+ return new Uri(path, UriKind.Relative);
}
public static Uri ConvertToStandardFormat(Shell shell, Uri request)
else
pathAndQuery = request.OriginalString;
- var segments = new List<string>(pathAndQuery.Split(_pathSeparator, StringSplitOptions.RemoveEmptyEntries));
+ var segments = new List<string>(pathAndQuery.Split(_pathSeparators, StringSplitOptions.RemoveEmptyEntries));
if (segments[0] != shell.RouteHost)
if (segments[1] != shell.Route)
segments.Insert(1, shell.Route);
- var path = String.Join("/", segments.ToArray());
+ var path = String.Join(_pathSeparator, segments.ToArray());
string uri = $"{shell.RouteScheme}://{path}";
return new Uri(uri);
}
- public static NavigationRequest GetNavigationRequest(Shell shell, Uri uri)
+ internal static NavigationRequest GetNavigationRequest(Shell shell, Uri uri, bool enableRelativeShellRoutes = false)
{
uri = FormatUri(uri);
// figure out the intent of the Uri
NavigationRequest.WhatToDoWithTheStack whatDoIDo = NavigationRequest.WhatToDoWithTheStack.PushToIt;
if (uri.IsAbsoluteUri)
whatDoIDo = NavigationRequest.WhatToDoWithTheStack.ReplaceIt;
- else if (uri.OriginalString.StartsWith("//") || uri.OriginalString.StartsWith("\\\\"))
+ else if (uri.OriginalString.StartsWith("//", StringComparison.Ordinal) || uri.OriginalString.StartsWith("\\\\", StringComparison.Ordinal))
whatDoIDo = NavigationRequest.WhatToDoWithTheStack.ReplaceIt;
else
whatDoIDo = NavigationRequest.WhatToDoWithTheStack.PushToIt;
Uri request = ConvertToStandardFormat(shell, uri);
- var possibleRouteMatches = GenerateRoutePaths(shell, request, uri);
+ var possibleRouteMatches = GenerateRoutePaths(shell, request, uri, enableRelativeShellRoutes);
if (possibleRouteMatches.Count == 0)
var theWinningRoute = possibleRouteMatches[0];
RequestDefinition definition =
new RequestDefinition(
- ConvertToStandardFormat(shell, new Uri(theWinningRoute.PathFull, UriKind.RelativeOrAbsolute)),
- new Uri(theWinningRoute.PathNoImplicit, UriKind.RelativeOrAbsolute),
+ ConvertToStandardFormat(shell, CreateUri(theWinningRoute.PathFull)),
theWinningRoute.Item,
theWinningRoute.Section,
theWinningRoute.Content,
internal static List<RouteRequestBuilder> GenerateRoutePaths(Shell shell, Uri request)
{
request = FormatUri(request);
- return GenerateRoutePaths(shell, request, request);
+ return GenerateRoutePaths(shell, request, request, false);
}
- internal static List<RouteRequestBuilder> GenerateRoutePaths(Shell shell, Uri request, Uri originalRequest)
+ internal static List<RouteRequestBuilder> GenerateRoutePaths(Shell shell, Uri request, Uri originalRequest, bool enableRelativeShellRoutes)
{
- request = FormatUri(request);
- originalRequest = FormatUri(originalRequest);
-
var routeKeys = Routing.GetRouteKeys();
for (int i = 0; i < routeKeys.Length; i++)
{
+ if (routeKeys[i] == originalRequest.OriginalString)
+ {
+ var builder = new RouteRequestBuilder(routeKeys[i], routeKeys[i], null, new string[] { routeKeys[i] });
+ return new List<RouteRequestBuilder> { builder };
+ }
routeKeys[i] = FormatUri(routeKeys[i]);
}
+ request = FormatUri(request);
+ originalRequest = FormatUri(originalRequest);
+
List<RouteRequestBuilder> possibleRoutePaths = new List<RouteRequestBuilder>();
if (!request.IsAbsoluteUri)
request = ConvertToStandardFormat(shell, request);
string localPath = request.LocalPath;
bool relativeMatch = false;
- if (!originalRequest.IsAbsoluteUri && !originalRequest.OriginalString.StartsWith("/") && !originalRequest.OriginalString.StartsWith("\\"))
+ if (!originalRequest.IsAbsoluteUri &&
+ !originalRequest.OriginalString.StartsWith("//", StringComparison.Ordinal))
relativeMatch = true;
- var segments = localPath.Split(_pathSeparator, StringSplitOptions.RemoveEmptyEntries);
+ var segments = localPath.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (!relativeMatch)
{
for (int i = 0; i < routeKeys.Length; i++)
{
var route = routeKeys[i];
- var uri = ConvertToStandardFormat(shell, new Uri(route, UriKind.RelativeOrAbsolute));
- // Todo is this supported?
+ var uri = ConvertToStandardFormat(shell, CreateUri(route));
if (uri.Equals(request))
{
- var builder = new RouteRequestBuilder(route, route, null, segments);
- return new List<RouteRequestBuilder> { builder };
+ throw new Exception($"Global routes currently cannot be the only page on the stack, so absolute routing to global routes is not supported. For now, just navigate to: {originalRequest.OriginalString.Replace("//","")}");
+ //var builder = new RouteRequestBuilder(route, route, null, segments);
+ //return new List<RouteRequestBuilder> { builder };
}
}
}
while (currentLocation.Shell != null)
{
- List<RouteRequestBuilder> pureRoutesMatch = new List<RouteRequestBuilder>();
- List<RouteRequestBuilder> pureGlobalRoutesMatch = new List<RouteRequestBuilder>();
+ var pureRoutesMatch = new List<RouteRequestBuilder>();
+ var pureGlobalRoutesMatch = new List<RouteRequestBuilder>();
- SearchPath(currentLocation.LowestChild, null, segments, pureRoutesMatch, 0);
- SearchPath(currentLocation.LowestChild, null, segments, pureGlobalRoutesMatch, 0, ignoreGlobalRoutes: false);
- pureRoutesMatch = GetBestMatches(pureRoutesMatch);
- pureGlobalRoutesMatch = GetBestMatches(pureGlobalRoutesMatch);
+ //currently relative routes to shell routes isn't supported as we aren't creating navigation stacks
+ if (enableRelativeShellRoutes)
+ {
+ SearchPath(currentLocation.LowestChild, null, segments, pureRoutesMatch, 0);
+ ExpandOutGlobalRoutes(pureRoutesMatch, routeKeys);
+ pureRoutesMatch = GetBestMatches(pureRoutesMatch);
+ if (pureRoutesMatch.Count > 0)
+ {
+ return pureRoutesMatch;
+ }
+ }
- if (pureRoutesMatch.Count > 0)
- return pureRoutesMatch;
+ SearchPath(currentLocation.LowestChild, null, segments, pureGlobalRoutesMatch, 0, ignoreGlobalRoutes: false);
+ ExpandOutGlobalRoutes(pureGlobalRoutesMatch, routeKeys);
+ pureGlobalRoutesMatch = GetBestMatches(pureGlobalRoutesMatch);
if (pureGlobalRoutesMatch.Count > 0)
+ {
+ // currently relative routes to shell routes isn't supported as we aren't creating navigation stacks
+ // So right now we will just throw an exception so that once this is implemented
+ // GotoAsync doesn't start acting inconsistently and all of a suddent starts creating routes
+ if (!enableRelativeShellRoutes && pureGlobalRoutesMatch[0].SegmentsMatched.Count > 0)
+ {
+ throw new Exception($"Relative routing to shell elements is currently not supported. Try prefixing your uri with ///: ///{originalRequest}");
+ }
+
return pureGlobalRoutesMatch;
+ }
currentLocation.Pop();
}
- string searchPath = String.Join("/", segments);
+ string searchPath = String.Join(_pathSeparator, segments);
if (routeKeys.Contains(searchPath))
{
return bestMatches;
bestMatches.Clear();
- foreach (var possibleRoutePath in possibleRoutePaths)
+ ExpandOutGlobalRoutes(possibleRoutePaths, routeKeys);
+ }
+
+ possibleRoutePaths = GetBestMatches(possibleRoutePaths);
+ return possibleRoutePaths;
+ }
+
+ internal static void ExpandOutGlobalRoutes(List<RouteRequestBuilder> possibleRoutePaths, string[] routeKeys)
+ {
+ foreach (var possibleRoutePath in possibleRoutePaths)
+ {
+ while (routeKeys.Contains(possibleRoutePath.NextSegment) || routeKeys.Contains(possibleRoutePath.RemainingPath))
{
- while (routeKeys.Contains(possibleRoutePath.NextSegment) || routeKeys.Contains(possibleRoutePath.RemainingPath))
+ if (routeKeys.Contains(possibleRoutePath.NextSegment))
+ possibleRoutePath.AddGlobalRoute(possibleRoutePath.NextSegment, possibleRoutePath.NextSegment);
+ else
+ possibleRoutePath.AddGlobalRoute(possibleRoutePath.RemainingPath, possibleRoutePath.RemainingPath);
+ }
+
+ while (!possibleRoutePath.IsFullMatch)
+ {
+ NodeLocation nodeLocation = new NodeLocation();
+ nodeLocation.SetNode(possibleRoutePath.LowestChild);
+ List<RouteRequestBuilder> pureGlobalRoutesMatch = new List<RouteRequestBuilder>();
+ while (nodeLocation.Shell != null && pureGlobalRoutesMatch.Count == 0)
{
- if(routeKeys.Contains(possibleRoutePath.NextSegment))
- possibleRoutePath.AddGlobalRoute(possibleRoutePath.NextSegment, possibleRoutePath.NextSegment);
- else
- possibleRoutePath.AddGlobalRoute(possibleRoutePath.RemainingPath, possibleRoutePath.RemainingPath);
+ SearchPath(nodeLocation.LowestChild, null, possibleRoutePath.RemainingSegments, pureGlobalRoutesMatch, 0, ignoreGlobalRoutes: false);
+ nodeLocation.Pop();
}
- while (!possibleRoutePath.IsFullMatch)
+ // nothing found or too many things found
+ if (pureGlobalRoutesMatch.Count != 1 || pureGlobalRoutesMatch[0].GlobalRouteMatches.Count == 0)
{
- NodeLocation nodeLocation = new NodeLocation();
- nodeLocation.SetNode(possibleRoutePath.LowestChild);
- List<RouteRequestBuilder> pureGlobalRoutesMatch = new List<RouteRequestBuilder>();
- while (nodeLocation.Shell != null && pureGlobalRoutesMatch.Count == 0)
- {
- SearchPath(nodeLocation.LowestChild, null, possibleRoutePath.RemainingSegments, pureGlobalRoutesMatch, 0, ignoreGlobalRoutes: false);
- nodeLocation.Pop();
- }
-
- // nothing found or too many things found
- if (pureGlobalRoutesMatch.Count != 1)
- {
- break;
- }
+ break;
+ }
- for (var i = 0; i < pureGlobalRoutesMatch[0].GlobalRouteMatches.Count; i++)
- {
- var match = pureGlobalRoutesMatch[0];
- possibleRoutePath.AddGlobalRoute(match.GlobalRouteMatches[i], match.SegmentsMatched[i]);
- }
+ for (var i = 0; i < pureGlobalRoutesMatch[0].GlobalRouteMatches.Count; i++)
+ {
+ var match = pureGlobalRoutesMatch[0];
+ possibleRoutePath.AddGlobalRoute(match.GlobalRouteMatches[i], match.SegmentsMatched[i]);
}
}
}
-
- possibleRoutePaths = GetBestMatches(possibleRoutePaths);
- return possibleRoutePaths;
}
internal static List<RouteRequestBuilder> GetBestMatches(List<RouteRequestBuilder> possibleRoutePaths)
if (Content != null && !Routing.IsImplicit(Content))
paths.Add(Content.Route);
- string uri = String.Join("/", paths);
+ string uri = String.Join(_pathSeparator, paths);
return new Uri($"{Shell.RouteScheme}://{uri}");
}
for (var i = 0; i < keys.Length; i++)
{
var key = FormatUri(keys[i]);
- if (key.StartsWith("/") && !(node is Shell))
+ if (key.StartsWith(_pathSeparator, StringComparison.Ordinal) && !(node is Shell))
continue;
- var segments = key.Split(_pathSeparator, StringSplitOptions.RemoveEmptyEntries);
+ var segments = key.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (segments[0] == route)
{
{
get
{
- var segments = _path.Split(_pathSeparator, StringSplitOptions.RemoveEmptyEntries).ToList().Skip(1).ToList();
+ var segments = _path.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList().Skip(1).ToList();
if (segments.Count == 0)
return new object[0];
{
get
{
- var segments = _path.Split(_pathSeparator, StringSplitOptions.RemoveEmptyEntries);
+ var segments = _path.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (segments.Length == 0)
return string.Empty;
{
get
{
- var segments = _path.Split(_pathSeparator, StringSplitOptions.RemoveEmptyEntries).ToList().Skip(1).ToList();
+ var segments = _path.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList().Skip(1).ToList();
if (segments.Count == 0)
return true;
if (nextMatch >= _allSegments.Length)
return null;
- return Routing.FormatRoute(String.Join("/", _allSegments.Skip(nextMatch)));
+ return Routing.FormatRoute(String.Join(_uriSeparator, _allSegments.Skip(nextMatch)));
}
}
public string[] RemainingSegments
string MakeUriString(List<string> segments)
{
- if (segments[0].StartsWith("/") || segments[0].StartsWith("\\"))
+ if (segments[0].StartsWith(_uriSeparator, StringComparison.Ordinal) || segments[0].StartsWith("\\", StringComparison.Ordinal))
return String.Join(_uriSeparator, segments);
return $"//{String.Join(_uriSeparator, segments)}";
[DebuggerDisplay("RequestDefinition = {Request}, StackRequest = {StackRequest}")]
- public class NavigationRequest
+ internal class NavigationRequest
{
public enum WhatToDoWithTheStack
{
[DebuggerDisplay("Full = {FullUri}, Short = {ShortUri}")]
- public class RequestDefinition
+ internal class RequestDefinition
{
- public RequestDefinition(Uri fullUri, Uri shortUri, ShellItem item, ShellSection section, ShellContent content, List<string> globalRoutes)
+ public RequestDefinition(Uri fullUri, ShellItem item, ShellSection section, ShellContent content, List<string> globalRoutes)
{
FullUri = fullUri;
- ShortUri = shortUri;
Item = item;
Section = section;
Content = content;
GlobalRoutes = globalRoutes;
}
- public RequestDefinition(string fullUri, string shortUri, ShellItem item, ShellSection section, ShellContent content, List<string> globalRoutes) :
- this(new Uri(fullUri, UriKind.Absolute), new Uri(shortUri, UriKind.Absolute), item, section, content, globalRoutes)
+ public RequestDefinition(string fullUri, ShellItem item, ShellSection section, ShellContent content, List<string> globalRoutes) :
+ this(new Uri(fullUri, UriKind.Absolute), item, section, content, globalRoutes)
{
}
public Uri FullUri { get; }
- public Uri ShortUri { get; }
public ShellItem Item { get; }
public ShellSection Section { get; }
public ShellContent Content { get; }