NavigationFrame.Avalonia
1.9.9
** Breaking Changes **
See the version list below for details.
dotnet add package NavigationFrame.Avalonia --version 1.9.9
NuGet\Install-Package NavigationFrame.Avalonia -Version 1.9.9
<PackageReference Include="NavigationFrame.Avalonia" Version="1.9.9" />
<PackageVersion Include="NavigationFrame.Avalonia" Version="1.9.9" />
<PackageReference Include="NavigationFrame.Avalonia" />
paket add NavigationFrame.Avalonia --version 1.9.9
#r "nuget: NavigationFrame.Avalonia, 1.9.9"
#:package NavigationFrame.Avalonia@1.9.9
#addin nuget:?package=NavigationFrame.Avalonia&version=1.9.9
#tool nuget:?package=NavigationFrame.Avalonia&version=1.9.9
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 generated
*Routeobjects (e.g.,navigator.NavigateAsync(new ProductRoute(123))). - Smart ViewModel Inference: Automatically locates and wires up ViewModels based on naming conventions (e.g.,
HomePage→HomeViewModel). - Advanced Layout System: Support for nested layouts (Master → Auth → Page) using the
[Layout]attribute. - Lifecycle Management: Rich events (
OnRouteChanged,OnNavigatedTo,CanNavigateFrom) viaIPageContextto handle data loading and cancellation. - Built-in Transitions: Easy-to-use animations (Slide, Fade, Zoom) configurable globally or per-route.
- Dependency Injection: Seamless integration with
Microsoft.Extensions.DependencyInjection. - 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>();
// 3. Register your ViewModels
services.AddTransient<HomeViewModel>();
services.AddTransient<MainLayoutViewModel>();
}
2. Define 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;
3. Initialize Navigation Host
In your main window, bind the NavigationService.Content to a ContentControl.
MainWindow.axaml:
<Window ...>
<ContentControl Content="{Binding NavigationService.Content}" />
</Window>
MainWindowViewModel.cs:
public class MainWindowViewModel : ViewModelBase
{
public INavigationService NavigationService { get; }
public MainWindowViewModel(INavigationService navService)
{
NavigationService = navService;
// Navigate to home on startup
navService.NavigateAsync(new HomePageRoute());
}
}
📖 Usage Guide
Defining Pages
Mark your UserControl with [Route]. The framework will generate a *Route class for it.
[Route]
public partial class HomePage : UserControl
{
public HomePage() => InitializeComponent();
}
Passing Parameters
Define a parameter type (record recommended) and reference it in the Args property.
public record ProductArgs(int Id, string Category);
[Route(Args = typeof(ProductArgs))]
public partial class ProductPage : UserControl
{
// ...
}
Navigate using the generated route:
await navigator.NavigateAsync(new ProductPageRoute(101, "Electronics"));
By default, the generator creates a constructor matching your parameter type (AsTemplate = true). If you prefer to pass the parameter object directly, set AsTemplate = false.
Layouts
Layouts are wrapper controls. Mark them with [Layout] and implement IMountableControl.
[Layout]
public partial class MainLayout : UserControl, IMountableControl
{
// Return the control where the page body should be rendered
public Control GetMountPoint() => this.FindControl<ContentControl>("Body");
}
Apply a layout to a page:
[Route(Layout = typeof(MainLayout))]
public partial class HomePage : UserControl ...
Nested Layouts
Layouts can have parent layouts, creating a hierarchy.
[Layout(ParentLayout = typeof(MainLayout))]
public partial class AuthLayout : UserControl, ILayoutControl ...
ViewModel Wiring
The framework attempts to infer the ViewModel type based on conventions:
Namespace.HomePage→Namespace.HomeViewModelNamespace.Views.HomePage→Namespace.ViewModels.HomeViewModel
If your ViewModel doesn't follow these patterns, specify it explicitly:
[Route(ViewModel = typeof(CustomViewModel))]
public partial class MyPage : UserControl ...
Diagnostics: The compiler will warn you if:
- ViewModel cannot be inferred and is not specified.
- You specify a ViewModel that is identical to the inferred one (Redundant).
Lifecycle Events
Implement IPageContext in your ViewModel to hook into navigation events.
public class HomeViewModel : ViewModelBase, IPageContext
{
public void SetNavigator(INavigator navigator) { ... }
public async Task OnRouteChanged(NavRoute route, Action<NavRoute> override, CancellationToken token)
{
// Load data here. Called when navigating to this page.
}
public async Task<bool> CanNavigateFrom()
{
// Return true to prevent navigation (e.g., unsaved changes)
return HasUnsavedChanges;
}
public async Task OnNavigatedFrom(NavRoute nextRoute) { ... }
public void OnPageReleased() { ... }
}
Transitions and Stack Behavior
Control animations using NavOptions.
Per-Navigation:
var route = new HomePageRoute();
var options = new NavOptions
{
StackBehavior = StackBehaviors.Clear, // clear all other pages
Animation = PageAnimations.SlideLeft
};
navigator.NavigateAsync(route, options);
Global Configuration:
Use [DefaultRoutesConfiguration] in your assembly to set defaults. This will generate a default PageAnimation in your NavRoute class. When animation is not specified in NavOptions, the default will be used.
[assembly: DefaultRoutesConfiguration(
Namespace = "MyApp.Routes",
Transition = Transitions.CrossFade,
Duration = 300
)]
💡 Best Practices & Common Pitfalls
Re-entrant Navigation (Redirecting)
You can initiate a new navigation from within lifecycle callbacks (e.g., redirecting to a Login page in OnRouteChanged), but you must be careful about Deadlocks!!!
The Rule: Never await NavigateAsync inside a callback that is part of the active navigation pipeline (OnNavigatingFrom, OnRouteChanged, OnNavigatedFrom). Never reuse CancellationToken from callback's argument.
Why?
The navigation queue processes requests sequentially. The current navigation is waiting for your callback to return. If you await a new navigation request, that new request waits for the queue to become free. This creates a cycle: Queue waits for Callback waits for New Request waits for Queue.
Correct Patterns:
Fire-and-Forget (Recommended for Redirects)
public async Task OnRouteChanged(...) { if (!IsLoggedIn) { // Fire and forget. The current navigation will be cancelled/interrupted by the new one. _ = _navigator.NavigateAsync(new LoginRoute()); return; } }Dispatcher.Post Schedule the navigation to run after the current cycle.
Dispatcher.UIThread.Post(() => _navigator.NavigateAsync(new LoginRoute()));OnNavigatedTo is Safe
OnNavigatedToruns asynchronously after the navigation commit. It is safe toawait NavigateAsynchere.
🛠️ Diagnostics
The library includes Roslyn analyzers to help you keep your code clean:
- NAV001: ViewModel not found (Warning).
- NAV002/NAV003: Page/Layout must be a Control (Error).
- NAV004: ViewModel inferred info (Info).
- NAV005: Redundant
AsTemplate = trueassignment (Info). - NAV006: Redundant
ViewModelassignment (Info). - NAV007: Potential Deadlock in Navigation Callback.
- NAV008: Redundant Unsafe CancellationToken Reuse.
| 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 | 122 | 12/30/2025 | |
| 2.0.4 | 116 | 12/29/2025 | |
| 2.0.3 | 117 | 12/28/2025 | |
| 2.0.2 | 120 | 12/28/2025 | |
| 2.0.0 | 132 | 12/27/2025 | |
| 1.9.9 | 208 | 12/24/2025 | |
| 1.9.8.1 | 191 | 12/22/2025 | |
| 1.9.8 | 272 | 12/22/2025 | |
| 1.9.6 | 153 | 12/20/2025 | |
| 1.9.0 | 248 | 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 | 442 | 12/8/2025 | |
| 1.4.0 | 567 | 12/7/2025 |