Compass.Wpf
0.0.15
dotnet add package Compass.Wpf --version 0.0.15
NuGet\Install-Package Compass.Wpf -Version 0.0.15
<PackageReference Include="Compass.Wpf" Version="0.0.15" />
<PackageVersion Include="Compass.Wpf" Version="0.0.15" />
<PackageReference Include="Compass.Wpf" />
paket add Compass.Wpf --version 0.0.15
#r "nuget: Compass.Wpf, 0.0.15"
#:package Compass.Wpf@0.0.15
#addin nuget:?package=Compass.Wpf&version=0.0.15
#tool nuget:?package=Compass.Wpf&version=0.0.15
Compass
The Source Generator Powered UI Infrastructure for Avalonia & WPF
Compass is a comprehensive Application Infrastructure designed to streamline the development of modern, type-safe desktop applications. By leveraging C# Source Generators, Compass eliminates runtime reflection overhead while providing a developer experience similar to ASP.NET Core or Blazor.
It is not just a routerβit is a complete solution for Navigation, Dialogs, Notifications, Authorization, and Dependency Injection.
β¨ Key Features
- π Type-Safe Navigation: Zero runtime reflection. Routes are strongly-typed C# records with constructor injection for parameters.
- π¬ Unified Interaction Service: Built-in support for Dialogs, Toasts, and Snackbars, managed via a clean, testable service API.
- π‘οΈ Integrated Security: Policy-based authorization (Roles, Policies, Claims) deeply integrated into navigation and UI components (
AuthorizeView). - π§© Advanced Layouts: Declarative, nested layouts (e.g.,
MainLayout -> SettingsLayout -> Page) defined directly in Route attributes. - π§ Intelligent Auto-Wiring: Automatically locates and injects ViewModels based on conventions or explicit configuration.
- β‘ Async Lifecycle: Robust lifecycle management (
IPreloadable,INavigatingFrom) for async data loading and navigation guards. - π οΈ Dependency Injection: Seamless integration with
Microsoft.Extensions.DependencyInjection. - π Cross-Platform: Identical API for Avalonia and WPF.
π Getting Started
1. Installation & Resources
First, add the Compass package to your project. Then, you MUST register the theme resources in your App file.
Avalonia (App.axaml)
<Application ...>
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Compass.Avalonia/Themes/CompassTheme.axaml" />
</Application.Styles>
</Application>
WPF (App.xaml)
<Application ...>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.xaml" />
<ResourceDictionary Source="pack://application:,,,/Compass.Wpf;component/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
2. Service Registration
Register Compass in your App.axaml.cs (Avalonia) or App.xaml.cs (WPF):
public void ConfigureServices(IServiceCollection services)
{
// Register Compass and its source-generated services
services.AddCompassCore<ViewFactory>(options =>
{
// 1. Configure Navigation
options.Navigation.StartRoute = new LoginRoute();
// 2. Configure Authorization (Optional)
options.Auth.AddPolicy("AdminOnly", p => p.RequireRole("Admin"));
// 3. Configure Interaction Defaults (Optional)
options.Snackbars.DefaultDuration = TimeSpan.FromSeconds(3);
});
}
3. Define the View Factory
Create a partial class marked with [ViewFactory]. The source generator will implement the resolution logic here. This file can be empty.
using Compass;
namespace MyApp.Services;
[ViewFactory]
public partial class ViewFactory;
4. Setup the Shell (UI)
In your MainWindow, use the CompassShell control. This acts as the host for pages, dialogs, and notifications.
Avalonia (MainWindow.axaml):
<Window xmlns:nav="using:Compass" ...>
<nav:CompassShell ShellContent="{Binding ShellContent}" />
</Window>
ViewModel (MainViewModel.cs):
public class MainViewModel(IShellContent shellContent) : ViewModelBase
{
public IShellContent ShellContent { get; } = shellContent;
}
π§ Core Concepts
1. Navigation & Routes
Routes are defined as partial records. Compass uses these records to match Views and ViewModels.
// Simple Route (Maps to HomePage + HomePageViewModel)
[Route<HomePage>]
public partial record HomeRoute;
// Route with Parameters & Layout
[Route<ProductPage, MainLayout>]
public partial record ProductRoute(int ProductId, string Mode);
Using INavigationService:
| Method | Description |
|---|---|
NavigateAsync(route) |
Pushes a new instance of the page onto the stack. |
GoToAsync(route) |
Smart navigation. If an equivalent route (by record equality) exists in the stack, it brings it to the top. Otherwise, behaves like NavigateAsync. |
GoBackAsync() |
Pops the current page. |
Post(mode, route) |
Safely schedules navigation on the UI thread. Use this inside lifecycle methods to avoid deadlocks. |
public class HomeViewModel(INavigationService nav) : ViewModelBase
{
public async Task OpenProduct()
{
// Type-safe parameters!
await nav.GoToAsync(new ProductRoute(42, "Edit"));
}
}
2. Interaction (Dialogs, Toasts, Snackbars)
Compass provides a unified API for UI interactions, decoupling your ViewModels from View implementation details.
Dialogs (IDialogService):
// 1. Define Args
public record ConfirmArgs(string Title, string Message) : IDialogArgs<bool>;
// 2. Call Service
public class MyViewModel(IDialogService dialogs)
{
public async Task DeleteItem()
{
// Simple usage
var confirmed = await dialogs.ShowDialogAsync(new ConfirmArgs("Delete?", "Are you sure?"));
// Advanced usage (Builder)
var result = await dialogs.Make(new ConfirmArgs("Delete?", "Sure?"))
.SetCloseOnOverlayClick(false)
.SetModal(true)
.ShowAsync();
}
}
Snackbars & Toasts:
public class MyViewModel(ISnackbarService snackbars, IToastService toasts)
{
public void Save()
{
// Snackbar with Action
snackbars.Show("File Saved", "Undo", () => UndoSave());
// Simple Toast
toasts.Show("Operation Successful", type: ToastType.Success);
}
}
3. Lifecycle Management
Implement interfaces on your ViewModel to hook into navigation events.
| Interface | Method | Purpose |
|---|---|---|
IPreloadable |
PreloadAsync |
Load data on a background thread before navigation completes. Runs parallel to exit animation. |
INavigatedTo<T> |
OnNavigatedToAsync |
Main entry point. Access the typed Route instance here. Runs on UI thread. |
INavigatingFrom |
OnNavigatingFromAsync |
Guard navigation (e.g., "Unsaved Changes"). Return false to cancel. |
IReleaseAware |
OnReleased |
Cleanup resources when the ViewModel is removed from memory. |
Async Data Loading Pattern:
public partial class ProductViewModel : ViewModelBase,
IPreloadable,
INavigatedTo<ProductRoute>
{
private ProductData _data;
// 1. Runs in background
public async Task PreloadAsync(IRoute route, NavigationMode mode, CancellationToken token)
{
var id = ((ProductRoute)route).ProductId;
_data = await _service.LoadProductAsync(id, token);
}
// 2. Runs on UI Thread after Preload completes
public Task OnNavigatedToAsync(ProductRoute route, Task preload, NavigationMode mode)
{
// Update Properties (No flickering!)
Title = _data.Name;
return Task.CompletedTask;
}
}
4. Authorization
Protect your routes and UI elements using the [Authorize] attribute and policies.
Configuration:
options.Auth.AddPolicy("CanDelete", p => p.RequireClaim("Permission", "Delete"));
Protecting a Route:
[Route<AdminPage>]
[Authorize(Policy = "CanDelete")]
public partial record AdminRoute;
Protecting UI (AuthorizeView):
<nav:AuthorizeView Policy="CanDelete">
<nav:AuthorizeView.Authorized>
<Button Command="{Binding DeleteCommand}">Delete</Button>
</nav:AuthorizeView.Authorized>
<nav:AuthorizeView.NotAuthorized>
<TextBlock Text="You do not have permission to delete."/>
</nav:AuthorizeView.NotAuthorized>
</nav:AuthorizeView>
5. Layouts
Layouts are wrapper controls (like MasterPage) that persist across page navigations. They must implement IMountableControl.
public partial class MainLayout : UserControl, IMountableControl
{
// Tell Compass where to inject the child view
public Control GetMountPoint() => this.FindControl<ContentControl>("Body");
}
Nested Layouts:
// Layout stack: MainLayout -> SettingsLayout -> AccountPage
[Route<AccountPage, MainLayout, SettingsLayout>]
public partial record AccountRoute;
π Best Practices
- Use Records for Routes: Always use
partial recordfor routes. They provide value equality by default, which Compass uses for smart navigation (GoToAsync). - Keep ViewModels Agnostic: Never reference UI controls (like
WindoworMessageBox) in your ViewModels. UseIDialogServiceandINavigationService. - Async All The Way: Prefer
NavigateAsyncandPreloadAsyncfor data fetching. Avoid blocking the UI thread in constructors. - Feature Folders: Organize your code by feature (e.g.,
Features/Products/) keeping Views, ViewModels, and Routes together. - Centralized Layouts: Use Layouts (
IMountableControl) for shared UI structure (NavBars, Headers) instead of repeating XAML in every page.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0-windows7.0 is compatible. net9.0-windows was computed. net9.0-windows7.0 is compatible. net10.0-windows was computed. net10.0-windows7.0 is compatible. |
-
net10.0-windows7.0
- CommunityToolkit.Mvvm (>= 8.4.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
-
net8.0-windows7.0
- CommunityToolkit.Mvvm (>= 8.4.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
-
net9.0-windows7.0
- CommunityToolkit.Mvvm (>= 8.4.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 |
|---|