Skip to content

Commit

Permalink
Add GotoAsync overload for single use query parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
PureWeen committed Jun 12, 2023
1 parent c34957a commit e05faf3
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 40 deletions.
25 changes: 25 additions & 0 deletions src/Controls/src/Core/Shell/Shell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,31 @@ public Task GoToAsync(ShellNavigationState state, bool animate, IDictionary<stri
return _navigationManager.GoToAsync(state, animate, false, parameters: new ShellRouteParameters(parameters));
}

#pragma warning disable RS0016 // Add public types and members to the declared API
/// <summary>
///
/// </summary>
/// <param name="state"></param>
/// <param name="singleUseQueryParameter">Any parameters passed here won't be reused when the page is navigated back to</param>
/// <returns></returns>
public Task GoToAsync(ShellNavigationState state, KeyValuePair<string, object> singleUseQueryParameter)
{
return _navigationManager.GoToAsync(state, null, false, parameters: new ShellRouteParameters(singleUseQueryParameter));
}

/// <summary>
///
/// </summary>
/// <param name="state"></param>
/// <param name="animate"></param>
/// <param name="singleUseQueryParameter">Any parameters passed here won't be reused when the page is navigated back to</param>
/// <returns></returns>
public Task GoToAsync(ShellNavigationState state, bool animate, KeyValuePair<string, object> singleUseQueryParameter)
{
return _navigationManager.GoToAsync(state, animate, false, parameters: new ShellRouteParameters(singleUseQueryParameter));
}
#pragma warning restore RS0016 // Add public types and members to the declared API

public void AddLogicalChild(Element element)
{
if (element == null)
Expand Down
13 changes: 13 additions & 0 deletions src/Controls/src/Core/Shell/ShellContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,10 @@ static void ApplyQueryAttributes(object content, ShellRouteParameters query, She
var type = content.GetType();
var queryPropertyAttributes = type.GetCustomAttributes(typeof(QueryPropertyAttribute), true);
if (queryPropertyAttributes.Length == 0)
{
ClearQueryIfAppliedToPage(query, content);
return;
}

foreach (QueryPropertyAttribute attrib in queryPropertyAttributes)
{
Expand Down Expand Up @@ -327,6 +330,16 @@ static void ApplyQueryAttributes(object content, ShellRouteParameters query, She
prop.SetValue(content, null);
}
}

ClearQueryIfAppliedToPage(query, content);

static void ClearQueryIfAppliedToPage(ShellRouteParameters query, object content)
{
// Once we've applied the attributes to ContentPage lets remove the
// parameters used during navigation
if (content is ContentPage)
query.ResetToQueryParameters();
}
}
}
}
33 changes: 2 additions & 31 deletions src/Controls/src/Core/Shell/ShellNavigationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ internal async Task GoToAsync(

var uri = navigationRequest.Request.FullUri;
var queryString = navigationRequest.Query;
var queryData = ParseQueryString(queryString);
parameters.Merge(queryData);
parameters.SetQueryStringParameters(queryString);
ApplyQueryAttributes(_shell, parameters, false, false);

var shellItem = navigationRequest.Request.Item;
Expand Down Expand Up @@ -306,17 +305,7 @@ public static void ApplyQueryAttributes(Element element, ShellRouteParameters qu
baseShellItem = element?.Parent as BaseShellItem;

//filter the query to only apply the keys with matching prefix
var filteredQuery = new ShellRouteParameters(query.Count);

foreach (var q in query)
{
if (!q.Key.StartsWith(prefix, StringComparison.Ordinal))
continue;
var key = q.Key.Substring(prefix.Length);
if (key.IndexOf(".", StringComparison.Ordinal) != -1)
continue;
filteredQuery.Add(key, q.Value);
}
var filteredQuery = new ShellRouteParameters(query, prefix);


if (baseShellItem is ShellContent)
Expand Down Expand Up @@ -488,24 +477,6 @@ public static ShellNavigationSource CalculateNavigationSource(Shell shell, Shell
return ShellNavigationSource.Push;
}

static Dictionary<string, string> ParseQueryString(string query)
{
if (query.StartsWith("?", StringComparison.Ordinal))
query = query.Substring(1);
Dictionary<string, string> lookupDict = new(StringComparer.Ordinal);
if (query == null)
return lookupDict;
foreach (var part in query.Split('&'))
{
var p = part.Split('=');
if (p.Length != 2)
continue;
lookupDict[p[0]] = p[1];
}

return lookupDict;
}

public static ShellNavigationParameters GetNavigationParameters(
ShellItem shellItem,
ShellSection shellSection,
Expand Down
66 changes: 58 additions & 8 deletions src/Controls/src/Core/Shell/ShellRouteParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace Microsoft.Maui.Controls
{
internal class ShellRouteParameters : Dictionary<string, object>
{
KeyValuePair<string, object>? _singleUseQueryParameter;

public ShellRouteParameters()
{
}
Expand All @@ -15,25 +17,73 @@ public ShellRouteParameters(ShellRouteParameters shellRouteParams) : base(shellR
{
}

public ShellRouteParameters(IDictionary<string, object> shellRouteParams) : base(shellRouteParams)
internal ShellRouteParameters(ShellRouteParameters query, string prefix)
: base(query.Count)
{
foreach (var q in query)
{
if (!q.Key.StartsWith(prefix, StringComparison.Ordinal))
continue;
var key = q.Key.Substring(prefix.Length);
if (key.IndexOf(".", StringComparison.Ordinal) != -1)
continue;
this.Add(key, q.Value);
}
}

internal ShellRouteParameters(IDictionary<string, object> shellRouteParams) : base(shellRouteParams)
{
}

public ShellRouteParameters(int count)
: base(count)
internal ShellRouteParameters(KeyValuePair<string, object> singleUseQueryParameter)
{
this.Add(singleUseQueryParameter.Key, singleUseQueryParameter.Value);
_singleUseQueryParameter = singleUseQueryParameter;
}

internal void Merge(IDictionary<string, string> input)
internal void ResetToQueryParameters()
{
if (input == null || input.Count == 0)
if (_singleUseQueryParameter is null)
return;

foreach (var item in input)
Add(item.Key, item.Value);
if (this.ContainsKey(_singleUseQueryParameter.Value.Key))
{
this.Remove(_singleUseQueryParameter.Value.Key);
_singleUseQueryParameter = null;
}
}

internal void SetQueryStringParameters(string query)
{
var queryStringParameters = ParseQueryString(query);
if (queryStringParameters == null || queryStringParameters.Count == 0)
return;

foreach (var item in queryStringParameters)
{
if (!this.ContainsKey(item.Key))
this[item.Key] = item.Value;
}
}
}

static Dictionary<string, string> ParseQueryString(string query)
{
if (query.StartsWith("?", StringComparison.Ordinal))
query = query.Substring(1);
Dictionary<string, string> lookupDict = new(StringComparer.Ordinal);
if (query == null)
return lookupDict;
foreach (var part in query.Split('&'))
{
var p = part.Split('=');
if (p.Length != 2)
continue;
lookupDict[p[0]] = p[1];
}

return lookupDict;
}
}

internal static class ShellParameterExtensions
{
Expand Down
104 changes: 104 additions & 0 deletions src/Controls/tests/Core.UnitTests/ShellParameterPassingTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -376,6 +377,109 @@ public async Task BasicShellParameterTest()
Assert.Equal(obj, testPage.ComplexObject);
}

[Fact]
public async Task ExtraParametersDontGetRetained()
{
var shell = new Shell();
var item = CreateShellItem(shellSectionRoute: "section2");
Routing.RegisterRoute("details", typeof(ShellTestPage));
shell.Items.Add(item);
var obj = new object();
var parameter = new ShellRouteParameters
{
{"DoubleQueryParameter", 2d },
{ "ComplexObject", obj}
};

await shell.GoToAsync(new ShellNavigationState($"details?{nameof(ShellTestPage.SomeQueryParameter)}=1234"), parameter);
var testPage = (shell.CurrentItem.CurrentItem as IShellSectionController).PresentedPage as ShellTestPage;

await shell.Navigation.PushAsync(new ContentPage());

testPage.SomeQueryParameter = String.Empty;
testPage.DoubleQueryParameter = -1d;
testPage.ComplexObject = null;

await shell.GoToAsync("..");

Assert.Equal("1234", testPage.SomeQueryParameter);
Assert.Equal(-1d, testPage.DoubleQueryParameter);
Assert.Null(testPage.ComplexObject);

// ensure that AppliedQueryAttributes is called with correct parameters each time
Assert.Equal(2, testPage.AppliedQueryAttributes.Count);
Assert.Equal(3, testPage.AppliedQueryAttributes[0].Count);
Assert.Equal(1, testPage.AppliedQueryAttributes[1].Count);
Assert.Equal($"{nameof(ShellTestPage.SomeQueryParameter)}", testPage.AppliedQueryAttributes[1].Keys.First());
}

[Fact]
public async Task ExtraParametersArentReAppliedWhenNavigatingBackToShellContent()
{
var shell = new Shell();
var item = CreateShellItem(shellContentRoute: "start");
var withParams = CreateShellItem(page: new ShellTestPage(), shellContentRoute: "withParams", templated: true);
shell.Items.Add(item);
shell.Items.Add(withParams);
var obj = new object();
var parameter = new ShellRouteParameters
{
{ "ComplexObject", obj},
{ nameof(ShellTestPage.SomeQueryParameter), "1234"}
};

await shell.GoToAsync(new ShellNavigationState($"//start"));
await shell.GoToAsync(new ShellNavigationState($"//withParams"), parameter);

var testPage = (shell.CurrentItem.CurrentItem.CurrentItem as IShellContentController).GetOrCreateContent() as ShellTestPage;

// Validate parameter was set during first navigation
Assert.Equal(obj, testPage.ComplexObject);

// Clear parameters
testPage.ComplexObject = null;
testPage.SomeQueryParameter = null;

// Navigate away and back to page with params
await shell.GoToAsync(new ShellNavigationState($"//start"));
shell.CurrentItem = withParams;
await Task.Yield();

var testPage2 = (shell.CurrentItem.CurrentItem as IShellSectionController).PresentedPage as ShellTestPage;
Assert.Null(testPage2.SomeQueryParameter);
Assert.Null(testPage2.ComplexObject);
Assert.Equal(testPage2, testPage);
}

[Fact]
public async Task ExtraParamsReplaceQueryStringParams()
{
var shell = new Shell();
var item = CreateShellItem(shellSectionRoute: "section2");
Routing.RegisterRoute("details", typeof(ShellTestPage));
shell.Items.Add(item);
var obj = new object();
var parameter = new ShellRouteParameters
{
{$"{nameof(ShellTestPage.SomeQueryParameter)}", "4321" },
{ "ComplexObject", obj}
};

await shell.GoToAsync(new ShellNavigationState($"details?{nameof(ShellTestPage.SomeQueryParameter)}=1234"), parameter);
var testPage = (shell.CurrentItem.CurrentItem as IShellSectionController).PresentedPage as ShellTestPage;

// Parameters passed in will win
Assert.Equal("4321", testPage.SomeQueryParameter);
await shell.Navigation.PushAsync(new ContentPage());
await shell.GoToAsync("..");

// Parameters will return to previous query parameter values
Assert.Equal("1234", testPage.SomeQueryParameter);

// Previous parameter set by extra passed parameters doesn't get set to null
Assert.Equal(obj, testPage.ComplexObject);
}

[Fact]
public async Task DotDotNavigationPassesShellParameters()
{
Expand Down
8 changes: 7 additions & 1 deletion src/Controls/tests/Core.UnitTests/ShellTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ protected ShellSection MakeSimpleShellSection(string route, string contentRoute,
[QueryProperty("SomeQueryParameter", "SomeQueryParameter")]
[QueryProperty("CancelNavigationOnBackButtonPressed", "CancelNavigationOnBackButtonPressed")]
[QueryProperty("ComplexObject", "ComplexObject")]
public class ShellTestPage : ContentPage
public class ShellTestPage : ContentPage, IQueryAttributable
{
public string CancelNavigationOnBackButtonPressed { get; set; }
public ShellTestPage()
Expand Down Expand Up @@ -128,6 +128,12 @@ protected override bool OnBackButtonPressed()

return base.OnBackButtonPressed();
}

public List<IDictionary<string, object>> AppliedQueryAttributes = new List<IDictionary<string, object>>();
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
AppliedQueryAttributes.Add(new Dictionary<string, object>(query));
}
}

protected ShellItem CreateShellItem(
Expand Down

0 comments on commit e05faf3

Please sign in to comment.