NavigationFrame.Avalonia
2.0.0
See the version list below for details.
dotnet add package NavigationFrame.Avalonia --version 2.0.0
NuGet\Install-Package NavigationFrame.Avalonia -Version 2.0.0
<PackageReference Include="NavigationFrame.Avalonia" Version="2.0.0" />
<PackageVersion Include="NavigationFrame.Avalonia" Version="2.0.0" />
<PackageReference Include="NavigationFrame.Avalonia" />
paket add NavigationFrame.Avalonia --version 2.0.0
#r "nuget: NavigationFrame.Avalonia, 2.0.0"
#:package NavigationFrame.Avalonia@2.0.0
#addin nuget:?package=NavigationFrame.Avalonia&version=2.0.0
#tool nuget:?package=NavigationFrame.Avalonia&version=2.0.0
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 lifecycle interfaces (
INavigatingFrom,IDataPreloader,INavigatedTo, etc.) to handle data loading, navigation guards, and cleanup. - 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. 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.
MainWindow.axaml:
<Window>
<ContentControl Content="{Binding NavigationService.Content}" />
</Window>
In your ViewModel, expose NavigationService property.
MainWindowViewModel.cs:
public class MainWindowViewModel : ViewModelBase
{
public INavigationService NavigationService { get; }
public MainWindowViewModel(INavigationService navService)
{
NavigationService = navService;
}
}
📖 Usage Guide
Defining Pages (Manual)
Inherit NavRoute to create a strongly-typed route for each page.
Defining Pages (Generated)
Mark your Page's 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))] // The generated route will use same constructor signature as ProductArgs
public partial class ProductPage : UserControl
{
// ...
}
Navigate using the generated route:
await navigator.NavigateAsync(new ProductPageRoute(Id: 101, Category: "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 placeholder where you reserved for body
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, IMountableControl;
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).
Navigation Lifecycle & Interfaces
The framework provides granular interfaces to hook into navigation events. You can implement any combination of these interfaces in your ViewModel or View.
1. Obtaining Navigator (IRequireNavigator)
Implement IRequireNavigator to receive the INavigator instance.
public partial class HomeViewModel : ViewModelBase, IRequireNavigator
{
private INavigator _navigator;
public void SetNavigator(INavigator navigator)
{
_navigator = navigator;
}
}
2. Data Loading (IDataPreloader)
Implement IDataPreloader to load data when the route changes. This is called on a background thread.
public partial class HomeViewModel : ViewModelBase, IDataPreloader
{
public async Task PreloadAsync(NavRoute 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)
{
await LoadProductAsync(productRoute.Id, token);
}
}
}
3. Initialization (IRequireInit)
Implement IRequireInit for logic that must run on the UI thread once when the component is first created.
public partial class HomeViewModel : ViewModelBase, IRequireInit
{
public async Task InitializeAsync(CancellationToken token)
{
// UI-thread initialization
await InitializeMyControlsAsync();
}
}
4. Navigation Guard (INavigatingFrom)
Implement INavigatingFrom to intercept and potentially cancel navigation away from the current page.
public partial class HomeViewModel : ViewModelBase, INavigatingFrom
{
public async Task<bool> OnNavigatingFromAsync()
{
if (HasUnsavedChanges)
{
// Show confirmation dialog, return false to cancel navigation
return await _dialogService.ShowConfirmAsync("Discard changes?");
}
return true;
}
}
5. Navigation Notifications
INavigatedTo: Called when navigation to the page is complete.INavigatedFrom: Called after navigation away from the page is complete.
public partial class HomeViewModel : ViewModelBase, INavigatedTo, INavigatedFrom
{
public async Task OnNavigatedToAsync(NavRoute route, Task preload, NavigationMode mode)
{
// Page is now active
// You can await 'preload' to ensure data loading is finished
await preload;
}
public async Task OnNavigatedFromAsync()
{
// Page is no longer active, but may still be in the history stack.
// Suitable for pausing background tasks or resource suspension.
// For permanent cleanup, implement IDisposable.
}
}
6. Layout Body Changes (IBodyAware)
For Layouts, implement IBodyAware to be notified when the inner content (body) changes.
public partial class MainLayoutViewModel : ViewModelBase, IBodyAware
{
public async Task OnBodyChangedAsync(NavRoute route, object? body)
{
// Handle body change (e.g., update title based on route)
}
}
### Transitions and Stack Behavior
Control animations using `NavOptions`.
**Per-Navigation:**
```csharp
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)
Navigation methods (NavigateAsync, GoBackAsync, etc.) are queued and executed sequentially.
If you await a navigation method inside a lifecycle callback (e.g., OnNavigatingFromAsync, OnNavigatedFromAsync, OnBodyChangedAsync), you will cause a deadlock.
The queue is waiting for your callback to finish, but your callback is waiting for the queue to process the new navigation.
Rule: Only OnNavigatedToAsync is safe to await navigation methods.
Solution: If you need to navigate from other callbacks, use the Post method or Dispatcher.UIThread.Post:
// Safe: Posts the request to run after the current operation completes
navigator.Post(NavigationMode.GoTo, new LoginRoute());
// Or manually:
Dispatcher.UIThread.Post(() => navigator.GoToAsync(new LoginRoute()));
🛠️ 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 in [Route(ViewModel=...)]. |
| 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. Use Dispatcher.UIThread.Post instead. |
| 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. |
| 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 |