ObservableModel
ObservableModel is the INPC / INPChanging base. SetProperty compares with EqualityComparer<T>.Default and exits without raising events when the value is unchanged. PropertyChangedEventArgs are cached by property name so a hot bind path does not allocate a new args object on every raise.
NotifyDependsOn raises a named set of dependents. Prefer that over PropertyChanged(null), which forces every binding to refresh. Hand-write the field and the property, or mark a partial class with [Notify] / [NotifyAlso] from the SourceGenerators package.
public sealed class CounterViewModel : ViewModel
{
private int _count;
public int Count
{
get => _count;
set
{
if (SetProperty(ref _count, value))
{
NotifyDependsOn(nameof(Count), nameof(Label));
}
}
}
public string Label => $"Count {Count}";
}ViewModel lifecycle
ViewModel adds Status, IsBusy, ViewModelCancellationToken, InitializeAsync, OnAppearingAsync, OnDisappearingAsync, and ExecuteAsync. The token is created in the constructor and cancelled on Dispose. After dispose the token stays readable — IsCancellationRequested is true — so late continuations can still observe cancel.
Construct (DI)
→ Accept(args) / Accept(query) IAcceptNavArgs / IAcceptNavQuery
→ InitializeAsync(token) once
→ OnNavigatedToAsync(token)
→ OnAppearingAsync(token)
→ OnDisappearingAsync(token)
→ OnNavigatedFromAsync(token)
→ Dispose cancels ViewModelCancellationTokenPageViewModel implements INavigable and optionally holds INavigator and IDialogs. The page owns BindingContext. The ViewModel never holds Page. ViewModelLifecycleBehavior in the host package calls appear / disappear and unsubscribes on Unloaded.
AsyncState and Outcome
AsyncState<T> is the bindable UI status object: Status, Data, Error, Exception, Timestamp, plus IsLoading, IsRefreshing, IsEmpty, HasError, and IsSuccess. ViewModelStatus values are Idle, Loading, Refreshing, Saving, Success, Empty, Error, Offline, Unauthorized, and Cancelled. LoadAsync and RefreshAsync return Outcome<T>. The host package ships AsyncStateView to swap loading / empty / error / success templates.
Outcome / Outcome<T> is the library result type — success or failure with code, message, exception, validation, and metadata. The name is Outcome so it does not collide with FluentResults, LanguageExt, or an app-level Result<T>.
public AsyncState<IReadOnlyList<Product>> Products { get; } = new();
await Products.LoadAsync(token => catalog.ListAsync(token), ct);
// bind ItemsSource to Products.Data
// bind IsRefreshing to Products.IsRefreshing