NavigationFrame.Avalonia 2.0.0

There is a newer version of this package available.
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
                    
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="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NavigationFrame.Avalonia" Version="2.0.0" />
                    
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 2.0.0
                    
#r "nuget: NavigationFrame.Avalonia, 2.0.0"
                    
#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@2.0.0
                    
#: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=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=NavigationFrame.Avalonia&version=2.0.0
                    
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 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:

  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).

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 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.