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.
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:
Basic overview:
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.
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)
}
NavigationStopped
event? (My thought is no as this is also indicated by the Try method returning false.)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.)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.)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.
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. :)