Microsoft-ui-xaml: Proposal: Enable extensibility in Frame navigation

Created on 13 May 2019  路  2Comments  路  Source: microsoft/microsoft-ui-xaml

Proposal: Enable extensibility in Frame navigation

Summary

Extend the current WinUI/UWP navigation model to support navigation based on more than just a page Type, and enable custom logic in the creation of the page instance that is navigated to.
This will enable good coding practices and improve support for MVVM and Cross-Platform Frameworks.

Rationale

The current WinUI (UWP) navigation model requires specifying navigation based on a Page type and requires that pages have parameterless constructors. This forces code in pages to use the ServiceLocator pattern for dependency resolution rather than dependency injection.
By adding a way to specify how a navigation request results in the creation of a page it will allow the use of pages with parameterized constructors. It also enables the possibility of navigating based on more than just page types.

I believe these changes would help in:

  • writing code that more clearly indicates dependencies, for ease of support/maintenance.
  • writing code with a better separation of concerns/dependencies, to better enable the following of good development practices.
  • improve integration/support by 3rd party MVVM Frameworks as some rely on different navigation strategies.
  • help in providing consistency in cross-platform scenarios for frameworks that include UWP as a target but don't work well (or at all) with the current page based navigation option.

Functional Requirements

  1. This proposal does not involve changing the current navigation behavior and only includes an optional way to extend it. Any developer who wishes will be able to continue using the current navigation method without issue and this will avoid any unintended consequences for existing code.
  2. Remove the need to have a parameterless constructor for the type of page navigating to.
  3. Provide a way to write custom code to resolve the page to navigate to.
  4. Enable navigation based on an object instance, not just a type.

Important Notes

Basic overview:

  • Add a way to specify how navigation requests are resolved.
  • Add new navigation methods.
  • When calling one of the new methods, a comparable method in the resolver is called to determine which page to navigate to.
  • If calling a new method but a custom resolver hasn't been specified then a NavigationFailed event is raised with a suitable error message. (e.g. exception.Message = "NavigationResolver not specified", SourcePageType = null)
  • Make testing the logic within a page easier.

API ideas

New property on Frame

INavigationResolver NavigationResolver { get; set; }

New methods on Frame

// Similar to existing Navigate and NavigateToType methods. 
// 'type' could be any type, not just a page
//  other params would work like in the existing methods. 
bool TryNavigate(Type type, object parameter = null, NavigationTransitionInfo infoOverride = null)

// For navigation based on an instance of an object. "parameter" not needed as it would be a property of the target object.
bool TryNavigate(object target, NavigationTransitionInfo infoOverride = null)

_The naming is to reflect the TryXxxx naming pattern and avoid collisions with the existing Navigate and NavigateToType methods._

The implementation of these methods (without error handling) would be something like this:

public bool TryNavigate(Type type, object parameter = null, NavigationTransitionInfo infoOverride = null)
{
    if (this.NavigationResolver.TryNavigateByType(type, parameter, infoOverride, out Page pageInstance))
    {
        return this.NavigateInternal(pageInstance, parameter, infoOverride);
    }

    return false;
}

public bool TryNavigate(object target, NavigationTransitionInfo infoOverride = null)
{
    if (this.NavigationResolver.TryNavigateToObject(target, infoOverride, out Page pageInstance))
    {
        return this.NavigateInternal(pageInstance, infoOverride);
    }

    return false;
}

where NavigateInternal() does the navigation to the created page instance.

New interface INavigationResolver

public interface INavigationResolver
{
    bool TryNavigateByType(Type type, object parameter, NavigationTransitionInfo infoOverride, out Page page);
    bool TryNavigateToObject(object target, NavigationTransitionInfo infoOverride, out Page page);
}

The methods defined in this interface would work in parallel with the new methods on Frame and return the page instance to navigate to.

Example usage

Navigate to a page with constructor params

Set up custom navigation handling. App-wide frame reference set for simplicity, not part of the spec here.

App.Frame = new MyNavigationResolver();

Set up rules for custom navigation.

public class MyNavigationResolver : INavigationResolver
{
    public bool TryNavigateByType(Type type, object parameter, NavigationTransitionInfo infoOverride, out Page page)
    {
        switch (type.Name)
        {
            // Just a simple example. Reality may involve passing multiple values and/or using a DI container/framework
            case nameof(UserPage):
                var vm = this.viewModelLocator.GetUserViewModel(parameter);
                page = new UserPage(vm);
                return true;
                break;
            // Other cases not shown for brevity
            default:
                page = null;
                return false;
                break;
        }
    }

    public bool TryNavigateToObject(object target, NavigationTransitionInfo infoOverride, out Page page)
    {
        // Not shown - see below
    }
}

In the above, this.viewModelLocator would be set in a constructor but not shown here to keep the example focused on the important parts of the code.

How page being navigated to is defined:

public sealed partial class UserPage : Page
{
    public UserViewModel ViewModel { get; }

    public UserPage(UserViewModel viewModel)
    {
        InitializeComponent();
        this.ViewModel = viewModel;
    }
}

Triggering navigation with above page and resolver. ("5" is the user Id. hard-coded for demonstration purposes.)

App.Frame.TryNavigate(typeof(UserPage), 5);

Navigate based on an object instance

App.Frame.TryNavigate(vm.SelectedItem);

pseudo code of resolver implementation

public bool TryNavigateToObject(object target, NavigationTransitionInfo infoOverride, out Page page)
{
    // Assume `target` is a ViewModel instance (convention in the app)
    // Get page type based on VM type (via reflection, hard-coded, or whatever)
    // Create Page instance and pass VM (similar to above)
}

Open Questions

  • What are the consequences for maintaining the back stack (including [Get|Set]NavigationState) and handling suspend/resume? (possible internal issues that aren't obvious without reviewing the code.)
  • If a custom navigation request is canceled (within the custom resolver), should it raise a NavigationStopped event? (My thought is no as this is also indicated by the Try method returning false.)
  • Should the FrameNavigating event be supported in combination with a new TryNavigate method? (My inclination is no, as it risks splitting navigation logic into multiple classes. Keep it all in the resolver.)
  • This makes navigation parameters potentially available in the page constructor--in addition to the OnNavigatedTo event. This is a good/useful thing but could also result in possible negative consequences if misused. Is this an issue or something that needs extra consideration?
  • Should there be async support? (triggering navigation is currently synchronous but this proposal potentially moves some functionality that would otherwise happen after navigation--in OnNavigatedTo--to during the process while TryNavigate is executing. It's reasonable to assume a desire to make async calls within the resolver but doing so might encourage actions that are time-consuming when navigation should be fast.)
area-Navigation feature proposal needs-winui-3 team-Controls

Most helpful comment

Great writeup! We had a similar proposal brewing internally before we went OSS. Hopefully with WinUI 3.0 we can start making more improvements like this with the community's help. :)

All 2 comments

Great writeup! We had a similar proposal brewing internally before we went OSS. Hopefully with WinUI 3.0 we can start making more improvements like this with the community's help. :)

Awesome writeup! This topic is indeed in our payload of consideration for investments soon after we wean off of our current focus on WinUI 3.0 - the roadmap to which is here: introduce in the near term: https://github.com/microsoft/microsoft-ui-xaml/blob/master/docs/roadmap.md.

Please do take a look. Your feedback on that discussion document would be highly beneficial.

Was this page helpful?
0 / 5 - 0 ratings