PC.Framework 4.5.14

dotnet add package PC.Framework --version 4.5.14
                    
NuGet\Install-Package PC.Framework -Version 4.5.14
                    
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="PC.Framework" Version="4.5.14" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PC.Framework" Version="4.5.14" />
                    
Directory.Packages.props
<PackageReference Include="PC.Framework" />
                    
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 PC.Framework --version 4.5.14
                    
#r "nuget: PC.Framework, 4.5.14"
                    
#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 PC.Framework@4.5.14
                    
#: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=PC.Framework&version=4.5.14
                    
Install as a Cake Addin
#tool nuget:?package=PC.Framework&version=4.5.14
                    
Install as a Cake Tool

PC.Framework

An opinionated toolkit for building .NET MAUI apps with ReactiveUI, MVVM, MediatR (CQRS), and FluentValidation — wired together so you don't have to.

PC.Framework gives you base ViewModel/Page classes with automatic ViewModel wiring, a Shell-based navigation service, a dialogs abstraction, FluentValidation-backed edit forms with dirty-tracking, a global exception handler, and pluggable HTTP auth strategies. The goal is to remove the boilerplate every MAUI + ReactiveUI project ends up writing by hand.

Packages in this repo

Project What it is
src/PC.Framework The main MAUI library: base views/viewmodels, navigation, dialogs, validation, error handling, DI registration (UsePcFramework)

Requirements

  • .NET 10 SDK (src/global.json pins 10.0.201, rolling forward to the latest minor)
  • MAUI workload (dotnet workload install maui)
  • Visual Studio 2022 or JetBrains Rider with MAUI support

Installation

dotnet add package PC.Framework

Step-by-step guide

1. Register the framework in MauiProgram.cs

UsePcFramework is the single entry point. It registers navigation, dialogs, auth, storage, the global exception handler, and MediatR (with logging + validation pipeline behaviors) into the MAUI DI container.

using PC.Framework;
using ReactiveUI.Builder;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        builder
            .UseMauiApp<App>()
            .UseMauiCommunityToolkit()
            .UsePcFramework(new FrameworkConfiguration
            {
                FontRegistration = new Dictionary<string, string>
                {
                    { "OpenSansRegular", "OpenSans-Regular.ttf" },
                    { "OpenSansSemibold", "OpenSans-Semibold.ttf" }
                },
                RegisterServicesFromAssembly = typeof(App), // assembly MediatR scans for handlers
                ReactiveBuilder = rxBuilder => rxBuilder.WithMaui().BuildApp(),
                ExceptionHandlerConfig = new GlobalExceptionHandlerConfig(
                    AlertType: ErrorAlertType.Localize,
                    IgnoreTokenCancellations: true,
                    LogError: true)
            });

        builder.Services.AddViewsAndViewModels(); // your own registrations, see step 4

        return builder.Build();
    }
}

UsePcFramework registers, among others: INavigationService, IDialogsService, IGlobalExceptionHandler, IAuthStateService, ISecureStorageService, IAuthStrategyResolver, BaseServices, and IMediator (MediatR) with LoggingBehavior<,> and ValidationBehavior<,> in its pipeline.

2. Create a ViewModel

Derive from ViewModelBase and inject BaseServices — the framework's bundle of Navigation, Dialogs, Dispatcher (MediatR), ErrorHandler, and Connectivity.

using PC.Framework;
using PC.Framework.Bases.ViewModels;
using PC.Framework.Interfaces;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using Unit = System.Reactive.Unit;

public sealed partial class SampleViewModel : ViewModelBase
{
    [Reactive] private string _displayName = string.Empty;

    public ReactiveCommand<Unit, Unit> RefreshCommand { get; }

    public SampleViewModel(BaseServices services) : base(services)
    {
        RefreshCommand = ReactiveCommand.CreateFromTask(Load);
    }

    // Called automatically when the page is navigated to (forward navigation)
    protected override async Task OnNavigatedTo(INavigationParameters parameters, CancellationToken token)
    {
        await Load();
    }

    private async Task Load()
    {
        IsBusy = true;
        try { /* fetch data via Services.Dispatcher.Send(...) */ }
        finally { IsBusy = false; }
    }
}

ViewModelBase implements IActivatableViewModel + IQueryAttributable. Based on how the page was navigated to, one lifecycle hook fires automatically:

  • OnNavigatedTo — forward navigation (Navigation.GoAsync)
  • OnNavigatedBack — back navigation (Navigation.GoBackAsync)
  • OnNavigateToRefresh — self-navigation/refresh (Navigation.GoYourSelfAsync)

Override OnWhenActivated(CompositeDisposable disposables) for any additional WhenActivated subscriptions.

3. Create the View

Use BaseReactivePage<TViewModel> for a normal page, or BaseReactiveEditablePage<TViewModel> when the ViewModel needs "discard unsaved changes?" back-button behavior (pairs with EditViewModelBase<T>, step 6).

<views:BaseReactivePage x:TypeArguments="vm:SampleViewModel"
                         xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                         xmlns:views="clr-namespace:PC.Framework.Bases.Views;assembly=PC.Framework"
                         xmlns:vm="clr-namespace:YourApp.ViewModels"
                         x:Class="YourApp.Views.SamplePage">
    <Label Text="{Binding DisplayName}" />
</views:BaseReactivePage>

The page auto-wires its BindingContext from DI as soon as it's constructed/parented — no code-behind needed. Resolution uses a naming convention (ViewModelLocator):

  • FooPage → looks for FooViewModel (namespace *.Views*.ViewModels, falling back to an assembly-wide scan by type name)
  • FooView → looks for FooViewModel, same rules

Set AutoWire="False" on the page if you want to assign BindingContext yourself. If a match can't be resolved or isn't registered in DI, the framework throws loudly in DEBUG and logs quietly in RELEASE.

4. Register the View + ViewModel and (if needed) a Shell route

public static class ServiceExtensions
{
    public static IServiceCollection AddViewsAndViewModels(this IServiceCollection services)
    {
        // Full pages (navigable via Shell)
        services.RegisterNavigation<SamplePage, SampleViewModel>();

        // Child/embedded views (not directly routable)
        services.RegisterView<SampleHeaderView, SampleHeaderViewModel>();

        return services;
    }
}

Pages referenced from ShellContent in AppShell.xaml are picked up automatically; anything reached with GoAsync("SomeRoute") needs an explicit route in AppShell.xaml.cs:

public partial class AppShell
{
    public AppShell()
    {
        InitializeComponent();
        Routing.RegisterRoute(nameof(SamplePage), typeof(SamplePage));
    }
}

5. Navigate with INavigationService

Available on every ViewModel via Services.Navigation:

await Services.Navigation.GoAsync(nameof(SamplePage));              // push forward
await Services.Navigation.GoBackAsync();                             // pop back
await Services.Navigation.GoYourSelfAsync(nameof(SamplePage));      // refresh current route

Pass an INavigationParameters (a Dictionary<string, object>-like bag) as the second argument to pass data — it shows up in OnNavigatedTo/OnNavigatedBack/OnNavigateToRefresh.

6. Show dialogs with IDialogsService

await Services.Dialogs.ShowAsync("Saved", "Your changes were saved.", "OK");
var confirmed = await Services.Dialogs.ConfirmAsync("Discard?", "You have unsaved changes.", "Discard", "Keep editing");
var name = await Services.Dialogs.PromptAsync("Rename", "Enter a new name", "OK");

7. Edit forms with validation + dirty-tracking (EditViewModelBase<T>)

For forms, derive from EditViewModelBase<T> and inject a FluentValidation validator for the ViewModel itself. It gives you Errors, IsValid, and IsDirty as reactive properties, kept in sync as the user types.

public sealed partial class LogInViewModel : EditViewModelBase<LogInViewModel>
{
    [Reactive] private string _email = string.Empty;
    [Reactive] private string _password = string.Empty;

    public ReactiveCommand<Unit, Unit> LoginCommand { get; }

    public LogInViewModel(BaseServices services, IValidator<LogInViewModel> validator) : base(services, validator)
    {
        var canLogin = this.WhenAnyValue(x => x.IsValid, x => x.IsBusy, (valid, busy) => valid && !busy);
        LoginCommand = ReactiveCommand.CreateFromTask(Login, canLogin);
    }

    protected override IReadOnlySet<string> ValidatedProperties { get; } =
        new HashSet<string> { nameof(Email), nameof(Password) };

    private async Task Login() { /* ... */ }
}

public class LogInViewModelValidator : AbstractValidator<LogInViewModel>
{
    public LogInViewModelValidator()
    {
        RuleFor(x => x.Email).NotEmpty().EmailAddress();
        RuleFor(x => x.Password).NotEmpty().MinimumLength(8);
    }
}

Register validators once, scanning the assembly(ies) they live in:

builder.Services.AddValidatorsFromAssemblies([typeof(App).Assembly]);

In XAML, bind a field's error through the built-in ErrorLookupConverter (registered as {StaticResource ErrorLookup}):

<Entry Text="{Binding Email, Mode=TwoWay}" />
<Label Text="{Binding Errors, Converter={StaticResource ErrorLookup}, ConverterParameter=Email}" />

Use BaseReactiveEditablePage<TViewModel> for the page so a back-button press while IsDirty prompts the user via ConfirmLeaveIfDirtyAsync() before leaving.

8. Global exception handling

GlobalExceptionHandlerConfig (passed in step 1) controls behavior:

new GlobalExceptionHandlerConfig(
    AlertType: ErrorAlertType.Localize,   // None | NoLocalize | FullError | Localize
    IgnoreTokenCancellations: true,       // swallow TaskCanceledException
    LogError: true);

To also catch unhandled exceptions at the AppDomain/platform level (native iOS/Android crashes, unobserved task exceptions), call this once during app startup (e.g. in App.xaml.cs):

GlobalExceptionHandlerRegistration.RegisterAppDomainUnhandledExceptionHandlers(
    app.Services.GetRequiredService<IGlobalExceptionHandler>());

9. CQRS with MediatR (Services.Dispatcher)

UsePcFramework wires up MediatR with LoggingBehavior<,> and ValidationBehavior<,> already in the pipeline, scanning RegisterServicesFromAssembly (from step 1) for handlers. Define requests/handlers as usual and call them from a ViewModel via Services.Dispatcher:

var result = await Services.Dispatcher.Send(new LoginRequest(Email, Password), ct);

Handlers whose request implements FluentValidation's IValidator<TRequest> get validated automatically before execution by ValidationBehavior<,>.

10. HTTP + auth (optional)

PC.Framework.Core defines transport-agnostic building blocks so you're not tied to a specific HTTP client:

  • ApiResult<T> / ApiException — typed success/failure/validation-failure results for API calls
  • IAuthStrategy — pluggable "how do I authenticate this request" strategy (apply headers, recover from 401), resolved per-request by IAuthStrategyResolver
  • AuthHeaderHandler (PC.Framework) — a DelegatingHandler you attach to an HttpClient/Refit client that applies the resolved IAuthStrategy and retries once after a successful 401 recovery
  • IAuthStateService — raises SessionExpired so the rest of the app (e.g. a navigation-to-login redirect) can react
  • ISecureStorageService — abstraction over MAUI SecureStorage for tokens/credentials
services.AddSingleton<IAuthStrategy, MyBearerTokenStrategy>();
services.AddRefitClient<IMyApi>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri(baseUrl))
    .AddHttpMessageHandler<AuthHeaderHandler>();

Reference

Type Namespace Purpose
ViewModelBase PC.Framework.Bases.ViewModels Base ReactiveUI ViewModel with navigation lifecycle hooks
EditViewModelBase<T> PC.Framework.Bases.ViewModels Adds FluentValidation Errors/IsValid + dirty-tracking IsDirty
BaseReactivePage<TViewModel> PC.Framework.Bases.Views Auto-wiring ReactiveContentPage<TViewModel>
BaseReactiveEditablePage<TViewModel> PC.Framework.Bases.Views BaseReactivePage + "discard changes?" back-button guard
BaseServices PC.Framework DI bundle: Navigation, Dialogs, Dispatcher, ErrorHandler, Connectivity
INavigationService PC.Framework.Interfaces GoAsync / GoBackAsync / GoYourSelfAsync over Shell navigation
IDialogsService PC.Framework.Interfaces ConfirmAsync / ShowAsync / PromptAsync
IGlobalExceptionHandler PC.Framework.Interfaces Central exception processing + alerting
ApiResult<T> / ApiException PC.Framework.Core.Http Typed API call outcomes
IAuthStrategy / IAuthStrategyResolver PC.Framework.Core.Contracts Pluggable request authentication
ViewModelLocator PC.Framework Naming-convention ViewModel resolution used by BaseReactivePage

License

MIT © Pirate Chicken

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-android36.0 is compatible.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-ios26.0 is compatible.  net10.0-maccatalyst was computed.  net10.0-maccatalyst26.0 is compatible.  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.

Version Downloads Last Updated
4.5.14 95 7/29/2026
4.5.13 149 7/15/2026
4.5.12 94 7/13/2026
4.5.11 84 7/13/2026
4.5.10 114 7/13/2026
4.5.9 91 7/11/2026
4.5.8 104 7/10/2026
4.5.7 105 7/10/2026
4.5.6 101 7/10/2026
4.5.5 95 7/10/2026
4.5.4 98 7/10/2026
4.5.3 110 7/6/2026
4.5.2 104 7/1/2026
4.5.1 110 6/29/2026
4.5.0 110 6/23/2026
4.4.0 667 7/31/2025
4.3.9 260 6/16/2025
4.3.8 255 6/16/2025
4.3.7 412 5/5/2025
4.3.6 273 3/18/2025
Loading failed