NavigationFrame.Avalonia
2.0.6
See the version list below for details.
dotnet add package NavigationFrame.Avalonia --version 2.0.6
NuGet\Install-Package NavigationFrame.Avalonia -Version 2.0.6
<PackageReference Include="NavigationFrame.Avalonia" Version="2.0.6" />
<PackageVersion Include="NavigationFrame.Avalonia" Version="2.0.6" />
<PackageReference Include="NavigationFrame.Avalonia" />
paket add NavigationFrame.Avalonia --version 2.0.6
#r "nuget: NavigationFrame.Avalonia, 2.0.6"
#:package NavigationFrame.Avalonia@2.0.6
#addin nuget:?package=NavigationFrame.Avalonia&version=2.0.6
#tool nuget:?package=NavigationFrame.Avalonia&version=2.0.6
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).
- Extensible Authorization: Built-in support for route-level and UI-Element-level authorization using policies and roles.
🚀 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;
If the page should use inherited DataContext:
// Turn off inference and "ViewModel not found" warning
[Route<MyPage, MyLayout>(InheritContext = true)]
public partial record MyPageRoute;
Authorization
The framework provides a flexible authorization system that works at both the route level and the UI level.
1. Configure Authorization Delegate
Assign the Authorizer property on your INavigationService instance (usually in your MainViewModel or App startup).
// DI example
// register IAuthenticationStateProvider
// You need to implement AppAuthenticationStateProvider(IAuthenticationStateProvider)
services.AddSingleton<AppAuthenticationStateProvider>();
services.AddSingleton<IAuthenticationStateProvider>(sp => sp.GetRequiredService<AppAuthenticationStateProvider>());
// register IRoleAuthorizationPolicy, framework provides the DefaultRoleAuthorizationPolicy
services.AddSingleton<IRoleAuthorizationPolicy, DefaultRoleAuthorizationPolicy>();
// register IPolicyProvider, framework provides the DefaultPolicyProvider
services.AddSingleton<IPolicyProvider>(sp =>
{
var provider = new DefaultPolicyProvider();
provider.RegisterHandlers(
new CanEditDataPolicyHandler(),
new CanDeleteUserPolicyHandler(),
new CanExportDataPolicyHandler(),
);
return provider;
});
services.AddSingleton(new AuthorizationOptions
{
EnableCache = true,
CacheExpiration = TimeSpan.FromMinutes(5)
});
// register AuthorizationService, framework provides the AuthorizationService
services.AddSingleton<AuthorizationService>();
// Example in MainViewModel
public MainViewModel(INavigationService navService, AuthorizationService authService)
{
NavigationService = navService;
NavigationService.Authorizer = authService.CreateAuthorizer();
}
2. Route-Level Authorization
Add the [Authorize] attribute to your routes to protect pages:
[Route<AdminPage>]
[Authorize(Roles = "Admin", Policy = "RequireSuperUser")]
public partial record AdminPageRoute;
When navigating to this route, the NavigationService will call your authorization delegate. If it returns false,
navigation is cancelled.
3. UI-Level Authorization (Attached Properties)
Use Authorize attached properties to show/hide UI elements based on authorization. The Visible and Enable properties accept a flexible string format:
<UserControl xmlns:nav="using:NavigationFrame.Avalonia" nav:Authorize.Authorizer="{Binding Authorizer}">
<StackPanel>
<TextBox nav:Authorize.Enable="" />
<TextBox nav:Authorize.Enable="CanEditData" />
<TextBox nav:Authorize.Enable="CanEditData; Admin,Manager" />
<TextBox nav:Authorize.Enable="Policy=CanEditData" />
<TextBox nav:Authorize.Enable="Roles=Admin,Manager" />
<TextBox nav:Authorize.Enable="Policy=CanEditData; Roles=Admin" />
<TextBox nav:Authorize.Enable="P=CanEditData; R=Admin" />
</StackPanel>
</UserControl>
Tips:
Authorizer: Set this property on yourINavigationService's content holder control to enable global authorization. ** You only set once **.
<ContentControl Content="{Binding NavigationService.Content}"
nav:Authorize.Authorizer="{Binding NavigationService.Authorizer}"/>
4. Manual Authorization
Access the Authorizer delegate via INavigationService to perform manual checks:
public class SettingsViewModel(INavigationService nav) : ViewModelBase
{
public async Task DeleteUserAsync()
{
if (nav.Authorizer != null)
{
bool canDelete = await nav.Authorizer(null, "Admin", "CanDelete");
if (!canDelete) return;
}
// ...
}
}
5. Authorization Tips
- The navigation framework cannot detect whether the current user has switched accounts. If the user has switched accounts, it's best to clear the navigation stack and return to the home page.
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. Tips: if your component implements IDisposable, and it's created by DI, disposing should be
done by DI to keep consistency.
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
IViewFactory/PageViewModel(s) /LayoutViewModel(s) as scoped/transient 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 | 116 | 12/31/2025 | |
| 2.0.5 | 121 | 12/30/2025 | |
| 2.0.4 | 114 | 12/29/2025 | |
| 2.0.3 | 116 | 12/28/2025 | |
| 2.0.2 | 119 | 12/28/2025 | |
| 2.0.0 | 131 | 12/27/2025 | |
| 1.9.9 | 205 | 12/24/2025 | |
| 1.9.8.1 | 191 | 12/22/2025 | |
| 1.9.8 | 270 | 12/22/2025 | |
| 1.9.6 | 151 | 12/20/2025 | |
| 1.9.0 | 246 | 12/14/2025 | |
| 1.7.0 | 229 | 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 | 565 | 12/7/2025 |