Categories
.NET .NET 8 .NET 9 .NET MAUI Android Blazor C# Code Desktop Developer General Hybrid iOS macOS Mobile Shell Tips Visual Studio VS Code Web Windows Xamarin Xamarin.Forms

.NET MAUI: Managing Query Parameters with Shell Navigation

This is an article in the Developer Tips series, which offers concise hints to enhance productivity. You can access all the articles in this series here.

The Shell feature in .NET MAUI is extensive, offering numerous capabilities. This article will focus on managing query parameters within shell navigation in .NET MAUI.

Incorporating query parameters during navigation is paramount in real-world apps, as they enable the transfer of data in scenarios such as transitioning from a list page to a details page, thereby preserving the contextual relevance.

The GoToAsync() method serves as the comprehensive solution for all your navigation needs in Shell and is overloaded to cater various requirements.

Below are the two types that have been defined for use within this method.

  • ShellNavigationState
    • This defines the path for Shell to navigate to
  • ShellNavigationQueryParameters
    • Implements the IDictionary<string, object> interface
    • Parameters to use for a specific navigation operation

In almost every instance, developers would typically pass a string as a route parameter to this method, while the use of the ShellNavigationState instance is quite rare. This is primarily due to the implicit conversion capabilities of this type from string and Uri.

Navigation URIs can have three components:

  • Route
    • Pages that are defined in the visual hierarchy of the Shell
  • Page
    • That are not defined in the visual hierarchy, such as Detail pages, but registered to participate in the Shell navigation
  • Query Parameters
    • Parameters that can be passed to the destination page while navigating

The typical structure of the Navigation URI is:

//route/page?queryParameters

Query Parameters:

Shell navigation parameters consist of a collection of key-value pairs, with each pair identified by a key, of type string, sometimes referred to as an id. The key and value are separated by an equals sign, and multiple key-value pairs are joined using an ampersand sign.

The query parameters are appended to the route parameter subsequent to the question mark symbol.

//route/page?key1=value1&key2=value2

The primitive data can be passed as string-based query parameters using the navigation URI structure like order?id=1234.

Most importantly, in order to accommodate intricate types, the query parameter value can be passed as objects. This is where the ShellNavigationQueryParameters type comes into the picture.

// Here, order refers to an object that represents a single item 
// within the list of orders, which has been selected from a collection.
await Shell.Current.GoToAsync("order/details", new ShellNavigationQueryParameters {{"Order", order}});

Besides the ID, the order object might also include additional info about it which needs to be passed to the details page.

In context of this method overloads that accept query parameters as IDictionary<string, object>, it is indeed feasible to pass them as a Dictionary of key-value pairs. The distinction lies in the fact that ShellNavigationQueryParameters are designated for single-use (Once processed, the object is cleared from the memory at the destination), whereas when passed as a Dictionary, it is retained for the duration that the page remains accessible within the Navigation stack.

Query parameters may also be appended to requests for backward navigation.

// Occasionally, a user may cancel the transaction
await Shell.Current.GoToAsync("../..?cancel=true");

Receiving Parameters:

To handle the shell navigation parameters on the receiving side, one may utilize either of the options outlined below.

  • QueryProperty
  • IQueryAttributable

QueryProperty is an attribute that can be applied to a destination Page or its corresponding ViewModel (provided that it is mapped as its BindingContext). Multiple instances of this attribute can be defined on the type for different parameters as necessary.

This attribute comprises two properties, and the constructor is overloaded to facilitate the definition of both values with ease.

  • Name
    • The name of the property to which the value shall be transferred
  • QueryId
    • The identifier of the query parameter that contains the value

Note: As an example, the parameter id (id) is defined as a string literal. But it is recommended to use string constants for parameter id, as this considerably improves both readability and maintainability.

// Typical query: order?id=1234
[QueryProperty(nameof(OrderId), "id"]
public class OrderViewModel : BaseViewModel
{
    public int OrderId { get; protected set; }
}
// When it is received as an object
[QueryProperty(nameof(Order), "Order"]
public class OrderViewModel : BaseViewModel
{
    public Order Order { get; protected set; }
}

Note: String-based query parameter values that are received via this QueryProperty attribute are automatically URL decoded.

IQueryAttributable is an interface that requires the implementing type to define a method titled ApplyQueryAttributes, which takes a parameter of type IDictionary<string, object>.

public interface IQueryAttributable
{
    void ApplyQueryAttributes(IDictionary<string, object> query);
}

For this, I would opt for an explicit implementation of this interface method to conceal it from external access.

// Typical query: order?id=1234
public class OrderViewModel : BaseViewModel, IQueryAttributable
{
    public int OrderId { get; protected set; }

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.TryGetValue("id", out var orderId))
        {
             // Since value is of type object, casting is necessary
            OrderId = (int)orderId;
        }
    }
}

Note: String-based query parameter values that are received via this IQueryAttributable interface aren’t automatically URL decoded. Suggest to use HttpUtility.UrlDecode() method for the same.

.NET 9 – Support for Full Trimming/AOT:

When full trimming/AOT is enabled, receiving navigation data using the QueryProperty attribute is incompatible. Instead, implement the IQueryAttributable interface on types that need to accept query parameters.

Further Reading:

Happy coding. Stay connected as we continue to learn and share the experiences from this exciting journey of being a .NET developer.

Discover more from Developer Thoughts

Subscribe now to keep reading and get access to the full archive.

Continue reading