NavigationFrame.Avalonia
2.0.5
See the version list below for details.
dotnet add package NavigationFrame.Avalonia --version 2.0.5
NuGet\Install-Package NavigationFrame.Avalonia -Version 2.0.5
<PackageReference Include="NavigationFrame.Avalonia" Version="2.0.5" />
<PackageVersion Include="NavigationFrame.Avalonia" Version="2.0.5" />
<PackageReference Include="NavigationFrame.Avalonia" />
paket add NavigationFrame.Avalonia --version 2.0.5
#r "nuget: NavigationFrame.Avalonia, 2.0.5"
#:package NavigationFrame.Avalonia@2.0.5
#addin nuget:?package=NavigationFrame.Avalonia&version=2.0.5
#tool nuget:?package=NavigationFrame.Avalonia&version=2.0.5
NavigationFrame.Avalonia
A modern, flexible, and source-generator-powered navigation framework for Avalonia applications. Inspired by Blazor and web navigation patterns, it brings strongly-typed routing, powerful layout systems, and automatic ViewModel wiring to your desktop apps.
✨ Key Features
- Source Generator Powered: Zero runtime reflection for route discovery. Routes are generated at compile time for maximum performance and type safety.
- Strongly Typed Navigation: Say goodbye to magic strings. Navigate using objects (e.g.,
navigator.NavigateAsync(new ProductRoute(123))). - Smart ViewModel Inference: Automatically locates and wires up ViewModels based on naming conventions (e.g.,
HomePage→HomeViewModel). Supports 4 distinct lookup strategies. - Advanced Layout System: Support for nested layouts (Master → Settings → UISettingsPage) using the
[Layout]attribute. - Rich Lifecycle Management: Granular interfaces (
INavigatingFrom,IPreloadable,INavigatedTo, etc.) to handle data loading, navigation guards, and cleanup. - Smart UX Features:
- Built-in Transitions: Easy-to-use animations (Slide, Fade, Zoom) configurable globally or per-route.
- Intelligent Progress Indicator:
IsNavigatingproperty with configurableShowDelayandHideDelayto prevent flickering for fast navigations.
- Dependency Injection Ready: Seamless integration with
Microsoft.Extensions.DependencyInjection, including support for Scoped Services per page. - Developer Experience: Includes compile-time diagnostics to catch errors early (e.g., missing ViewModels, redundant configurations).
🚀 Getting Started
1. Setup Dependency Injection
Register the core services in your App.axaml.cs:
using NavigationFrame.Avalonia;
using Microsoft.Extensions.DependencyInjection;
public void ConfigureServices(IServiceCollection services)
{
// 1. Register ViewFactory (Generated or Manual)
services.AddSingleton<IViewFactory, ViewFactory>(); // See step 2
// 2. Register NavigationService
services.AddSingleton<INavigationService, NavigationService>();
// or, scoped navigation service, treats a page without layout as a `Scope`
// and all the pages that use a same top-level layout as a `Scope`
services.AddSingleton<INavigationService, ScopedNavigationService>();
// 3. Register your ViewModels
services.AddTransient<HomeViewModel>();
services.AddTransient<MainLayoutViewModel>();
}
2. Implement ViewFactory
Generated ViewFactory
Create a partial class marked with [ViewFactory]. The source generator will implement the logic to resolve your views.
using NavigationFrame.Avalonia;
namespace MyApp.Services;
[ViewFactory]
public partial class ViewFactory : IViewFactory;
Manual ViewFactory
If you prefer to implement the factory manually, create a class that implements IViewFactory.
3. Initialize Navigation Host
In your window, bind the NavigationService.Content to a ContentControl 's Content.
MainWindow.axaml:
<Window ...>
<Panel>
<ContentControl Content="{Binding NavigationService.Content}" />
<ProgressBar IsIndeterminate="True"
IsVisible="{Binding NavigationService.IsNavigating}"
VerticalAlignment="Top"/>
</Panel>
</Window>
MainWindowViewModel.cs:
public class MainWindowViewModel : ViewModelBase
{
public INavigationService NavigationService { get; }
public MainWindowViewModel(INavigationService navService)
{
NavigationService = navService;
}
}
MainWindow.cs:
public class MainWindow : Window
{
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
if(DataContext is MainWindowViewModel { NavigationService: INavigationService navService })
{
// Setup default behaviors
navService.HandleBackwardInput = true; // Alt+Left or MouseBack
navService.HandleForwardInput = true; // Alt+Right or MouseForward
// Navigate to the start page
_ = navService.NavigateAsync(new LandingPageRoute());
}
}
}
📖 Usage Guide
Defining Pages
Mark your Page's Route class (record recommended) with [Route<TPage>]. The framework will generate the implementation of IRoute for it.
// Simple Route
[Route<HomePage>]
public partial record HomePageRoute;
// Route with Parameters
[Route<ProductPage>]
public partial record ProductPageRoute(int Id, string Category);
// Navigate:
await navigator.NavigateAsync(new ProductPageRoute(101, "Electronics"));
Custom Base Route
You can inherit from your own base Route class to add shared properties (e.g., for Menu generation).
public abstract record TabRoute
{
public abstract string TabName { get; }
public abstract string Icon { get; }
}
[Route<SettingsPage>]
public partial record SettingsPageRoute : TabRoute
{
public override string TabName => "Settings";
public override string Icon => "Gear";
}
[Route<HomePage>]
public partial record HomePageRoute : TabRoute
{
public override string TabName => "Home";
public override string Icon => "Home";
}
Navigation Methods
The INavigationService provides a comprehensive set of methods:
NavigateAsync(route, options): Pushes a new page onto the stack.GoBackAsync(): Pops the current page.GoForwardAsync(): Navigates to the next page (if any) in the stack.RefreshAsync(): Reloads the current page (triggersIPreloadableagain).GoToAsync(route, options): Navigates to an existing page in the stack if found; otherwise pushes a new one.- Matching: You can specify
MatchStrategyinNavOptions(ByValue, ByType, or ByReference).
- Matching: You can specify
Post(mode, route, options): Safe way to trigger navigation from within a lifecycle callback (prevents deadlock).
Layouts
Layouts are wrapper controls. Mark them with [Layout] and implement IMountableControl.
[Layout]
public partial class MainLayout : UserControl, IMountableControl
{
// Return the placeholder where the page content should be placed
public Control GetMountPoint() => this.FindControl<ContentControl>("Body");
}
Apply a layout to a route:
[Route<HomePage, MainLayout>]
public partial record HomePageRoute;
Nested Layouts
Create a hierarchy by specifying a parent layout for your layout.
// AuthLayout is wrapped by MainLayout
[Layout<MainLayout>]
public partial class AuthLayout : UserControl, IMountableControl;
ViewModel Wiring
The framework infers the ViewModel type using 4 strategies in order:
Namespace.HomePage→Namespace.HomeViewModelNamespace.Views.HomePage→Namespace.ViewModels.HomeViewModelNamespace.HomePage→Namespace.ViewModels.HomeViewModel(Appended .ViewModels)- Global Search: Any class named
HomeViewModelin the assembly.
If inference fails or you want a specific ViewModel:
[Route<MyPage, MyLayout>(DataContext = typeof(CustomViewModel))]
public partial record MyPageRoute;
Navigation Lifecycle & Interfaces
Implement these interfaces in your ViewModel or View to hook into events.
| Interface | Method | Description |
|---|---|---|
IRequireNavigator |
SetNavigator(INavigator) |
Receive the navigator instance. |
IPreloadable |
PreloadAsync(route, mode, token) |
Background Thread. Load data before the page is shown. Exception safe (captured in task). |
IRequireInit |
InitializeAsync(token) |
UI Thread. One-time initialization when the view is created. Blocks navigation until done. |
INavigatingFrom |
OnNavigatingFromAsync() |
Guard. Return false to cancel navigation (e.g., "Unsaved Changes"). |
INavigatedTo |
OnNavigatedToAsync(route, preload, mode) |
Called when page is active. Await the preload task here to handle data/errors. |
INavigatedFrom |
OnNavigatedFromAsync() |
Page is no longer active. Pause background tasks here. |
IBodyAware |
OnBodyChangedAsync(route, body) |
For Layouts. Called when the inner content changes (e.g., to update window title). |
IReleaseAware |
OnReleased() |
Called when the component is no longer referenced by the framework. |
public partial class ProductViewModel : ViewModelBase, IPreloadable, IRequireNavigator,, INavigatingFrom
{
private INavigator _navigator;
public void SetNavigator(INavigator navigator)
{
_navigator = navigator;
}
private Product? _product;
public async Task PreloadAsync(IRoute route, NavigationMode mode,CancellationToken token)
{
// Load data here.
// This runs on a background thread. Dispatch to UI thread if needed.
if (route is ProductRoute productRoute)
{
// here we store the result in a field and use it until `OnNavigatedToAsync`
_product = await LoadProductAsync(productRoute.Id, token);
}
}
public async Task<bool> OnNavigatingFromAsync()
{
if (HasUnsavedChanges)
{
// Show confirmation dialog, return false to cancel navigation
return await _dialogService.ShowConfirmAsync("Discard changes?");
}
return true; // return true to allow navigating away
}
}
Resource Release
The framework never helps to invoke IDisposable.Dispose(), according to the Microsoft documentation. Who created the instance is responsible for disposing it. The generated IViewFactory implementation uses IServiceProvider to create instances, which means it's up to DI container to manage the lifetime of the instance. If you manually implement IViewFactory, you're responsible for disposing the created instances.
If your component implements IReleaseAware, the framework will call OnReleased() when the component is no longer referenced by the framework. You can use this to release resources, such as closing database connections or disposing of IDisposable resources.
Transitions and Stack Behavior
Control animations and stack manipulation using NavOptions.
var options = new NavOptions
{
StackBehavior = StackBehaviors.Clear, // Clear history
Animation = PageAnimations.SlideLeft, // Built-in animation
IsEphemeral = true, // Skip adding to history
MatchStrategy = MatchStrategies.ByValue
};
await navigator.NavigateAsync(new HomePageRoute(), options);
💡 Best Practices
1. Avoiding Deadlocks (Re-entrancy)
Never await a navigation method inside a lifecycle callback (like OnNavigatingFromAsync). The navigation queue is blocked waiting for your callback!
Incorrect:
public async Task OnNavigatedFromAsync() {
await _navigator.GoBackAsync(); // DEADLOCK
}
Correct:
public async Task OnNavigatedFromAsync() {
_navigator.Post(NavigationMode.GoBack, null); // Safe
}
2. Handling Data Loading
Use IPreloadable for heavy lifting. It runs in parallel with the old page's exit animation. Await the result in OnNavigatedToAsync.
public Task PreloadAsync(IRoute route, NavigationMode mode, CancellationToken t)
{
// Start loading, return the task.
// Do NOT await here if you want to block 'OnNavigatedTo' manually later.
return _service.LoadDataAsync(t);
}
public async Task OnNavigatedToAsync(IRoute route, Task preload, NavigationMode mode)
{
try
{
await preload; // Wait for data, catch exceptions here
UpdateUi();
}
catch (Exception ex)
{
ShowError(ex);
}
}
3. DI Configuration
- Use
ScopedNavigationServicerather thanNavigationService. - Register
ScopedNavigationServiceas a singleton/transient service, depend on yourMainViewModel. - Register
PageViewModel(s) andLayoutViewModel(s) as scoped services.
🛠️ Diagnostics
The library includes Roslyn analyzers to help you keep your code clean and safe:
| Rule ID | Severity | Description |
|---|---|---|
| NAV001 | Error | Page must be a Control: Classes marked with [Route] must inherit from Avalonia.Controls.Control. |
| NAV002 | Error | Layout must be a Control: Classes marked with [Layout] must inherit from Avalonia.Controls.Control. |
| NAV003 | Warning | ViewModel not found: The generator could not infer the ViewModel type. Specify it explicitly. |
| NAV004 | Info | ViewModel inferred: Informational message showing which ViewModel was automatically paired. |
| NAV005 | Info | Redundant AsTemplate: AsTemplate = true is the default; you can remove this argument. |
| NAV006 | Info | Redundant ViewModel: The explicitly specified ViewModel matches the inferred one; you can remove this argument. |
| NAV007 | Error | Deadlock in Callback: Awaiting navigation inside a lifecycle callback (e.g., OnNavigatingFromAsync) causes a deadlock. |
| NAV008 | Warning | Unsafe Re-entrancy: Fire-and-forget navigation inside a callback is unsafe without Dispatcher.UIThread.Post. |
| NAV009 | Warning | Discouraged Await: Awaiting navigation in OnNavigatedToAsync is safe but discouraged as it delays completion. |
| NAV010 | Error | Invalid BaseOn Type: The type specified in BaseOn must be an abstract record class that inherits from IRoute. |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Avalonia (>= 11.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
-
net8.0
- Avalonia (>= 11.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
-
net9.0
- Avalonia (>= 11.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 2.0.7 | 120 | 12/31/2025 | |
| 2.0.6 | 117 | 12/31/2025 | |
| 2.0.5 | 121 | 12/30/2025 | |
| 2.0.4 | 115 | 12/29/2025 | |
| 2.0.3 | 117 | 12/28/2025 | |
| 2.0.2 | 120 | 12/28/2025 | |
| 2.0.0 | 131 | 12/27/2025 | |
| 1.9.9 | 206 | 12/24/2025 | |
| 1.9.8.1 | 191 | 12/22/2025 | |
| 1.9.8 | 271 | 12/22/2025 | |
| 1.9.6 | 151 | 12/20/2025 | |
| 1.9.0 | 247 | 12/14/2025 | |
| 1.7.0 | 230 | 12/12/2025 | |
| 1.6.0 | 534 | 12/10/2025 | |
| 1.5.1 | 450 | 12/8/2025 | |
| 1.5.0 | 441 | 12/8/2025 | |
| 1.4.0 | 566 | 12/7/2025 |