Navigation model
INavigator is host-agnostic. UseFrameNavigation is the only Avalonia host: login → replace-root → push on a Plugin.Avalonia.MVVMExpress.Controls.Frame named NavigationHost. AvaloniaFrameNavigator hops to IMainThread before constructing a view. There is no UseShell.
- URI stack on the Frame host: Current, Stack, ModalStack, CanGoBack, History, GoBackAsync, PopToRootAsync, ReplaceAsync, ResetAsync / ReplaceRootAsync, PushModalAsync / PopModalAsync.
- ResetAsync clears the Frame journal so Back cannot return to login. It does not keep the old journal and Push a new root.
- Modal views open as owned Window instances — not a second Frame overlay.
- UseAuth<TChallenge>() wraps GuardedNavigator. Register IAuthState yourself (Playground uses DemoAuthState). There is no UseSecureSessionAuth() host helper in 1.0.
- One INavigator per IWindowContext. A second Window gets its own navigator.
- ViewModels never call Frame.Navigate or show a dialog statically. IMainThread is the only marshal API.
Frame navigation
Add Plugin.Avalonia.MVVMExpress.Navigation and call UseFrameNavigation(). Typed args use a record and IAcceptNavArgs<T>.Accept. URI / dictionary args use IAcceptNavQuery.
public sealed record ProductDetailsArgs(int ProductId);
public sealed class ProductDetailsViewModel : PageViewModel,
IAcceptNavArgs<ProductDetailsArgs>, IAcceptNavQuery
{
private int _productId;
public void Accept(ProductDetailsArgs args) => _productId = args.ProductId;
public void Accept(IReadOnlyDictionary<string, object> query)
{
if (query.TryGetValue(nameof(ProductDetailsArgs.ProductId), out var raw)
&& int.TryParse(Convert.ToString(raw), out var id))
{
_productId = id;
}
}
}
await Navigator.NavigateToAsync<ProductDetailsViewModel, ProductDetailsArgs>(
new ProductDetailsArgs(product.Id), ct);
await Navigator.NavigateToAsync(
"details",
new Dictionary<string, object> { ["ProductId"] = product.Id },
cancellationToken: ct);builder.Services.UseAvaloniaMvvmExpress(o => o
.UseFrameNavigation((nav, _) => nav
.Map<LoginViewModel, LoginPage>("login")
.Map<HomeViewModel, HomePage>("home")
.Map<DetailsViewModel, DetailsPage>("details"))
.UseDialogs()
.UseAuth<LoginViewModel>());
await Navigator.ResetAsync<HomeViewModel>(); // replace-root after login
await Navigator.NavigateToAsync<DetailsViewModel, DetailsArgs>(new(id));
if (pages.CanGoBack)
await pages.GoBackAsync();
await pages.PopToRootAsync();
await pages.ReplaceRootAsync<HomeViewModel>();