NavigationFrame.Avalonia 1.9.9

Additional Details

** Breaking Changes **

There is a newer version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NavigationFrame.Avalonia" Version="1.9.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NavigationFrame.Avalonia" Version="1.9.9" />
                    
Directory.Packages.props
<PackageReference Include="NavigationFrame.Avalonia" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NavigationFrame.Avalonia --version 1.9.9
                    
#r "nuget: NavigationFrame.Avalonia, 1.9.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NavigationFrame.Avalonia@1.9.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NavigationFrame.Avalonia&version=1.9.9
                    
Install as a Cake Addin
#tool nuget:?package=NavigationFrame.Avalonia&version=1.9.9
                    
Install as a Cake Tool

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 *Route objects (e.g., navigator.NavigateAsync(new ProductRoute(123))).
  • Smart ViewModel Inference: Automatically locates and wires up ViewModels based on naming conventions (e.g., HomePageHomeViewModel).
  • Advanced Layout System: Support for nested layouts (Master → Auth → Page) using the [Layout] attribute.
  • Lifecycle Management: Rich events (OnRouteChanged, OnNavigatedTo, CanNavigateFrom) via IPageContext to 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:

  1. Namespace.HomePageNamespace.HomeViewModel
  2. Namespace.Views.HomePageNamespace.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:

  1. 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;
        }
    }
    
  2. Dispatcher.Post Schedule the navigation to run after the current cycle.

    Dispatcher.UIThread.Post(() => _navigator.NavigateAsync(new LoginRoute()));
    
  3. OnNavigatedTo is Safe OnNavigatedTo runs asynchronously after the navigation commit. It is safe to await NavigateAsync here.

🛠️ 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 = true assignment (Info).
  • NAV006: Redundant ViewModel assignment (Info).
  • NAV007: Potential Deadlock in Navigation Callback.
  • NAV008: Redundant Unsafe CancellationToken Reuse.
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.