A simple, predictable navigation library for .NET MAUI. It resolves pages and view models through DI, supports both Shell and non-Shell apps, and gives you a single, type-safe API for every navigation scenario.
Note: This library was renamed from
Maui.Plugins.PageResolvertoPlugin.Maui.SmartNavigationin v3.0 for .NET 10. See the migration guide below.
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.UseSmartNavigation(); // add this
return builder.Build();
}
}public class MyViewModel
{
private readonly INavigationManager _navigation;
public MyViewModel(INavigationManager navigation)
{
_navigation = navigation;
}
public Task ShowDetails()
=> _navigation.PushAsync<DetailsPage>();
}Or via extension methods on INavigation:
await Navigation.PushAsync<MyPage>();await _navigation.PushAsync<MyPage>(myParam1, "bob", 4);await _navigation.PushModalAsync<SettingsPage>();
await _navigation.PopModalAsync();await _navigation.GoToAsync(new Route("details"));Note: Ths is for illustration and not the recommended usage of the Route record type - see wiki page "Best Practices" (coming soon).
await _navigation.GoBackAsync();GoBackAsync automatically chooses the correct navigation context (Shell, modal, or the stack).
- Works with Shell and non-Shell navigation using the same API
- Fully DI-resolved pages and view models
- No framework, no magic – just type-safe navigation
- Optional async initialisation lifecycle for ViewModels
- Plays nicely with MVVM, MVU, or no pattern at all
- Minimal setup, no ceremony
Designed to take the guesswork out of picking the right lifecycle method for initialising ViewModels. It gives you something close to OnInitializedAsync in Blazor.
SmartNavigation abstracts MAUI’s three navigation systems (Shell, navigation stack, and modal stack) into one unified service:
public interface INavigationManager
{
Task GoToAsync(Route route, string? query = null);
Task GoBackAsync();
Task PushAsync<TPage>(object? args = null) where TPage : Page;
Task PopAsync();
Task PushModalAsync<TPage>(object? args = null) where TPage : Page;
Task PopModalAsync();
}Implement IViewModelLifecycle on your ViewModel and SmartNavigation will run async initialisation automatically when the page is navigated to:
public class MyViewModel : IViewModelLifecycle
{
public Task OnInitAsync(bool isFirstNavigation)
{
if (isFirstNavigation)
return LoadDataAsync();
return Task.CompletedTask;
}
}Attach via XAML:
<ContentPage xmlns:behaviours="clr-namespace:Plugin.Maui.SmartNavigation.Behaviours;assembly=Plugin.Maui.SmartNavigation">
<ContentPage.Behaviours>
<behaviours:NavigatedInitBehaviour />
</ContentPage.Behaviours>
</ContentPage>The optional source generator can register pages, view models, and services automatically. Enable it:
[UseAutoDependencies]
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
return MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseAutodependencies() // Generated extension method
.Build();
}
}Defaults:
- Pages – transient
- ViewModels – transient
- Services – singleton
(Must follow naming conventions - see wiki (coming soon))
Override default lifetimes using attributes:
[Transient]
public class CustomScopedService : ICustomScopedService { }SmartNavigation works seamlessly with both:
- Shell –
GoToAsync(Route)with type-safe routes - Navigation stack –
PushAsync<TPage>() - Modal –
PushModalAsync<TPage>() - Automatic back logic –
GoBackAsync()picks the correct behaviour
No special configuration is required.
- Package rename
<!-- Old -->
<PackageReference Include="Goldie.MauiPlugins.PageResolver" Version="2.x" />
<!-- New -->
<PackageReference Include="Plugin.Maui.SmartNavigation" Version="3.0" />- Namespaces changed
// Old
using Maui.Plugins.PageResolver;
// New
using Plugin.Maui.SmartNavigation;- Source generator is now opt-in
[UseAutoDependencies]- Remove old bootstrapping
UsePageResolver()is no longer required or present.
- Update NuGet package
- Update namespaces
- Add
[UseAutoDependencies]if using the generator - Update any custom mappings or extensions
- Remove any calls to
UsePageResolver() - Test navigation flows (API surface unchanged where not noted)
The demo project shows:
- Basic navigation
- Parameter passing
- Modal navigation
- Shell routing
- ViewModel lifecycle
- Popup support (Mopups)
- Service scopes
- Navigation patterns for modular apps
See: src/DemoProject
Note: This is for the legacy version. New video coming soon.
See the wiki (coming soon) for guides, examples, and best practices.