PC.Framework
5.0.2
dotnet add package PC.Framework --version 5.0.2
NuGet\Install-Package PC.Framework -Version 5.0.2
<PackageReference Include="PC.Framework" Version="5.0.2" />
<PackageVersion Include="PC.Framework" Version="5.0.2" />
<PackageReference Include="PC.Framework" />
paket add PC.Framework --version 5.0.2
#r "nuget: PC.Framework, 5.0.2"
#:package PC.Framework@5.0.2
#addin nuget:?package=PC.Framework&version=5.0.2
#tool nuget:?package=PC.Framework&version=5.0.2
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.jsonpins10.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(),
TokenStorage = TokenStorageMode.SecureStorage, // or .Preferences (default)
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, IPreferencesService, IAuthStrategyResolver, BaseServices, and IMediator (MediatR) with LoggingBehavior<,> and ValidationBehavior<,> in its pipeline.
Replacing a framework service
Every framework service is registered with TryAdd, so registering your own before
UsePcFramework wins — the framework will not overwrite it:
builder.Services.AddSingleton<IDialogsService, MyDialogsService>(); // must come first
builder.UsePcFramework(new FrameworkConfiguration { ... });
The shipped implementations (NavigationService, DialogsService, PreferencesService,
SecureStorageService, AuthStrategyResolver, AuthStateService, GlobalExceptionHandler)
are all unsealed with virtual members, so you can subclass and override a single method
instead of reimplementing the interface:
public sealed class LoggingNavigationService(ILogger<LoggingNavigationService> log) : NavigationService
{
public override Task GoAsync(string route, INavigationParameters? p = null)
{
log.LogInformation("Navigating to {Route}", route);
return base.GoAsync(route, p);
}
}
Turning parts off, and adding your own
builder.UsePcFramework(new FrameworkConfiguration
{
UseMediatR = false, // app doesn't use CQRS, or wires MediatR itself
UseCommunityToolkit = false, // app calls UseMauiCommunityToolkit() itself
ConfigureMediatR = c => c.AddBehavior(typeof(IPipelineBehavior<,>), typeof(MyBehavior<,>)),
ConfigureServices = services => services.AddSingleton<IMyService, MyService>(),
});
ConfigureMediatR runs after the framework's own MediatR setup, so the built-in
LoggingBehavior and ValidationBehavior stay in place and yours are appended.
Resolving anything else: BaseServices.Services
BaseServices carries the app's IServiceProvider. Resolve extras from it rather than adding
constructor parameters to your ViewModel bases:
var theme = Services.GetRequiredService<IThemeService>(); // throws with a clear message if unregistered
var maybe = Services.GetService<IAnalytics>(); // null when unregistered
Custom View→ViewModel naming
The default convention is FooPage/FooView → FooViewModel, searching the View's own
assembly. Apps that name things differently, or split Views and ViewModels across projects,
can extend it instead of giving up auto-wiring:
builder.UsePcFramework(new FrameworkConfiguration
{
// ViewModels live in a separate assembly
ViewModelAssemblies = { typeof(LogInViewModel).Assembly },
// e.g. FooScreen -> FooViewModel
ViewModelConventions =
{
t => t.Name.EndsWith("Screen")
? t.Assembly.GetType($"{t.Namespace}.{t.Name[..^"Screen".Length]}ViewModel")
: null
},
});
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; }
}
}
BaseServices bundles Navigation, Dialogs, Dispatcher (MediatR), ErrorHandler, Connectivity, and Preferences (step 11).
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.
Lifecycle hooks
| Hook | When |
|---|---|
InitializeAsync |
Once, before the first OnNavigatedTo. Setup that shouldn't repeat on return. |
OnNavigatedTo |
Activated after a forward navigation. |
OnNavigatedBack |
Activated after a back navigation. |
OnNavigateToRefresh |
Activated after GoYourSelfAsync. |
OnNavigatedFrom |
Deactivated — navigated away from. Not awaited; keep it short. |
OnWhenActivated |
Every activation, for Rx subscriptions bound to the CompositeDisposable. |
Exceptions thrown by these hooks are routed to IGlobalExceptionHandler.
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 forFooViewModel(namespace*.Views→*.ViewModels, falling back to an assembly-wide scan by type name)FooView→ looks forFooViewModel, 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<SampleViewModel>(); // typed — no route string
await Services.Navigation.GoAsync(nameof(SamplePage)); // by route
await Services.Navigation.GoBackAsync(); // pop back
await Services.Navigation.GoToRootAsync(); // pop to Shell root
await Services.Navigation.GoYourSelfAsync(nameof(SamplePage)); // refresh current route
await Services.Navigation.GoToTabAsync("home"); // switch Shell tab
await Services.Navigation.GoModalAsync<FilterPage>(); // present modally
await Services.Navigation.PopModalAsync(); // dismiss modal
Typed navigation uses the route recorded by RegisterNavigation<TPage, TViewModel>() (step 4),
which also means you no longer write Routing.RegisterRoute in AppShell — routes are
pushed into Shell automatically before the first navigation.
Every method returns Task<bool>: false means the call was absorbed because a navigation was
already in flight. That gate is what stops a double-tap pushing the same page twice.
Passing data
Pass an INavigationParameters (a Dictionary<string, object>-like bag) as the second
argument — it arrives in OnNavigatedTo/OnNavigatedBack/OnNavigateToRefresh. Read values
with the typed accessors rather than casting object; they also cope with Shell handing you a
query-string value as text:
await Services.Navigation.GoAsync<DetailViewModel>(new NavigationParameters
{
{ NavParams.Id, 42 },
{ "mode", EditMode.ReadOnly },
});
protected override Task OnNavigatedTo(INavigationParameters parameters, CancellationToken token)
{
var id = parameters.GetValue<int>(NavParams.Id); // 0 when absent
var mode = parameters.GetValue("mode", EditMode.Editable); // enum, with a fallback
if (parameters.TryGetValue<Guid>("ref", out var reference)) { /* ... */ }
return Task.CompletedTask;
}
Returning a result
// caller
var picked = await Services.Navigation.GoForResultAsync<string>(nameof(PickerPage));
// on the picker, before going back
Services.Navigation.SetResult(selected);
await Services.Navigation.GoBackAsync();
Only back-navigations that go through GoBackAsync settle the result. A page dismissed by the
OS back gesture settles as default on the next navigation rather than hanging — call
SetResult explicitly rather than relying on that.
6. Dialogs, popups and busy UI
Services.Dialogs covers transient messages. Every method marshals to the main thread, so it
is safe to call from background work — these APIs throw if called off the UI thread.
await Services.Dialogs.ShowAsync("Saved", "Your changes were saved.");
var ok = await Services.Dialogs.ConfirmAsync("Delete?", "This cannot be undone.", "Delete", "Keep");
var name = await Services.Dialogs.PromptAsync("Name", "What should we call it?",
placeholder: "e.g. Weekly report", maxLength: 60);
var choice = await Services.Dialogs.ActionSheetAsync("Sort by", "Cancel", "Newest", "Oldest", "A–Z");
await Services.Dialogs.ShowToastAsync("Copied to clipboard");
await Services.Dialogs.ShowSnackbarAsync("Message archived", "Undo", () => Restore());
PromptAsync returns null when cancelled. Toast and snackbar come from CommunityToolkit.Maui,
so they need it initialized — the default unless you set UseCommunityToolkit = false.
Custom popups
Register a popup view with its ViewModel, then show it by view type:
services.RegisterPopup<FilterPopupView, FilterPopupViewModel>();
// returns default when dismissed by tapping outside
var filter = await Services.Popups.ShowAsync<FilterPopupView, FilterOptions>();
// from inside the popup's ViewModel
await Services.Popups.CloseAsync(selectedFilter);
The service is IPopupPresenter, deliberately not IPopupService — CommunityToolkit already
publishes a type by that name, and matching it would be ambiguous in any file importing the
toolkit.
Busy overlay
BusyOverlay dims its content and shows a spinner while IsBusy is set. It is a Grid, so
its children share one cell and the overlay is kept on top of them:
xmlns:pc="clr-namespace:PC.Framework.Controls;assembly=PC.Framework"
<pc:BusyOverlay IsBusy="{Binding IsBusy}" BusyText="Signing in…">
<ScrollView> ... </ScrollView>
</pc:BusyOverlay>
The scrim also swallows taps, so content underneath can't be interacted with while busy.
OverlayColor and IndicatorColor are overridable.
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. The framework
does not register it for you — add it to a ResourceDictionary your App.xaml merges:
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:PC.Framework.Converters;assembly=PC.Framework">
<converters:ErrorLookupConverter x:Key="ErrorLookup" />
</ResourceDictionary>
Then bind each field's message by property name:
<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
IApiClient reports outcomes as ApiResult<T> instead of throwing, so call sites do not need a
try/catch around every request:
services.AddPcHttpClient(new Uri("https://api.example.com"));
var result = await _api.GetAsync<Booking[]>("bookings");
if (result.IsSuccess) Bookings = result.Data!;
else if (result.HasValidationErrors) ShowFieldErrors(result.ValidationErrors);
else await Services.Dialogs.ShowAsync("Error", result.ErrorMessage!);
Non-2xx responses, transport failures and malformed payloads all come back as failed results
carrying StatusCode and Exception. Cancellation is the exception — an
OperationCanceledException propagates, because a cancelled call has no outcome to report.
ValidationProblemDetails responses are unpacked into ValidationErrors per field.
ApiResult<T> composes: Match, Map, GetValueOrDefault, and an implicit conversion from
T. There is a non-generic ApiResult for calls that return nothing.
Resilience
The framework ships no retry policy and takes no dependency on Polly. AddPcHttpClient
returns the IHttpClientBuilder, so add one if you want it:
// dotnet add package Microsoft.Extensions.Http.Resilience
services.AddPcHttpClient(new Uri("https://api.example.com"))
.AddStandardResilienceHandler();
Auth
Register an IAuthStrategy; AuthHeaderHandler applies it to every request and retries once
after a recovered 401.
services.AddAuthStrategy(sp => new BearerTokenAuthStrategy(
sp.GetRequiredService<ISecureStorageService>(),
refresh: async (current, ct) => await MyIdentityApi.RefreshAsync(current.RefreshToken, ct),
authState: sp.GetRequiredService<IAuthStateService>()));
BearerTokenAuthStrategy refreshes proactively once AuthToken.NeedsRefresh is true (five
minutes before expiry) and reactively on a 401, persisting the new token and signalling
IAuthStateService.SessionExpired when the session is genuinely over.
Concurrent 401s collapse into a single refresh. When a token expires, every in-flight request
fails at once; ITokenRefreshCoordinator (a singleton, so one refresh serves every named
HttpClient) ensures only one refresh runs per scheme. Without it, N requests meant N refreshes —
and with rotating refresh tokens, the later ones fail and sign the user out.
Strategies are tried in registration order, first CanHandle wins, so register the most specific
first. ApiKeyAuthStrategy is also provided for keys that never expire.
11. Commands, screen state and collections
Commands
CreateCommand gives a command the wiring nearly every command needs, so it stops being
written by hand each time:
SaveCommand = CreateCommand(
async ct => await Save(ct),
canExecute: this.WhenAnyValue(x => x.IsValid),
requiresInternet: true); // fails fast with NoConnectivityException when offline
RefreshCommand = CreateCommand(ct => Reload(ct), trackBusy: false);
It sets IsBusy for the duration (reference-counted, so a short command finishing doesn't clear
the flag while a longer one is still running) and — importantly — subscribes
ThrownExceptions to IGlobalExceptionHandler. A raw ReactiveCommand leaves that unobserved,
so a command that throws fails silently.
For non-command code paths there is RunBusyAsync(...).
Screen state
Bind ViewModelBase.State (a ContentState: Loading / Content / Empty / Error) to a
StateContainer instead of juggling IsBusy / IsEmpty / HasError flags:
<pc:StateContainer State="{Binding State}">
<pc:StateContainer.LoadingView><ActivityIndicator IsRunning="True" /></pc:StateContainer.LoadingView>
<pc:StateContainer.EmptyView><Label Text="Nothing here yet" /></pc:StateContainer.EmptyView>
<pc:StateContainer.ErrorView><Label Text="Something went wrong" /></pc:StateContainer.ErrorView>
<pc:StateContainer.ContentView><CollectionView ItemsSource="{Binding Items}" /></pc:StateContainer.ContentView>
</pc:StateContainer>
State and IsBusy are independent: use IsBusy with BusyOverlay for an overlay on existing
content, and State when the whole screen is a spinner, an empty message or an error. It is
ContentState, not ViewState, because MAUI already publishes Microsoft.Maui.Controls.ViewState.
Collections
ObservableRangeCollection<T> changes in bulk with a single notification — adding 50 items to a
plain ObservableCollection makes MAUI re-measure the list 50 times:
Items.AddRange(page); // one notification
Items.ReplaceRange(results); // one notification
ObservableDictionary<TKey, TValue> is a bindable dictionary. It now implements
IDictionary<TKey, TValue> over a single store; previously it derived from
ObservableCollection<KeyValuePair<,>> alongside a private dictionary, and the two could
silently disagree.
12. Local storage with IPreferencesService
For non-sensitive app data you want to survive restarts — last selected tab, onboarding
seen, a cached user profile. Available on every ViewModel as Services.Preferences, or
inject IPreferencesService anywhere.
Services.Preferences.Set("last_tab", 2);
Services.Preferences.Set("onboarding_seen", true);
Services.Preferences.Set("last_sync", DateTimeOffset.UtcNow);
Services.Preferences.Set("profile", new UserProfile("Ada", "ada@example.com"));
var tab = Services.Preferences.Get("last_tab", 0);
var seen = Services.Preferences.Get("onboarding_seen", false);
var profile = Services.Preferences.Get<UserProfile?>("profile");
Services.Preferences.Remove("profile");
string, bool, int, long, double, float and DateTime are stored natively by
MAUI Preferences. Guid, DateTimeOffset and enums are stored as strings, and any
other type is serialized to JSON — so records and POCOs round-trip without extra work.
Set(key, null)removes the key.Getreturns the supplied default when the key is missing or when the stored value can't be read back asT(e.g. it was written as a different type) — it never throws.Clear()wipes all preferences for the app, including auth tokens whenTokenStorageisPreferences. PreferRemoveunless you mean a full reset.
This is plain-text storage. Anything sensitive belongs in ISecureStorageService with
TokenStorageMode.SecureStorage.
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, Preferences |
IPreferencesService |
PC.Framework.Interfaces |
Typed key/value local storage over MAUI Preferences |
TokenStorageMode |
PC.Framework |
Selects Preferences vs SecureStorage for auth tokens |
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 | Versions 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. |
-
net10.0
- CommunityToolkit.Maui (>= 14.2.0)
- FluentValidation (>= 12.1.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.1.1)
- MediatR (>= 14.1.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Maui.Controls (>= 10.0.71)
- Microsoft.Maui.Controls.Compatibility (>= 10.0.71)
- PC.Framework.Core (>= 0.0.11)
- ReactiveUI.Maui (>= 23.2.28)
- ReactiveUI.Validation (>= 7.1.0)
- System.ComponentModel.Annotations (>= 5.0.0)
-
net10.0-android36.0
- CommunityToolkit.Maui (>= 14.2.0)
- FluentValidation (>= 12.1.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.1.1)
- MediatR (>= 14.1.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Maui.Controls (>= 10.0.71)
- Microsoft.Maui.Controls.Compatibility (>= 10.0.71)
- PC.Framework.Core (>= 0.0.11)
- ReactiveUI.Maui (>= 23.2.28)
- ReactiveUI.Validation (>= 7.1.0)
- System.ComponentModel.Annotations (>= 5.0.0)
-
net10.0-ios26.0
- CommunityToolkit.Maui (>= 14.2.0)
- FluentValidation (>= 12.1.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.1.1)
- MediatR (>= 14.1.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Maui.Controls (>= 10.0.71)
- Microsoft.Maui.Controls.Compatibility (>= 10.0.71)
- PC.Framework.Core (>= 0.0.11)
- ReactiveUI.Maui (>= 23.2.28)
- ReactiveUI.Validation (>= 7.1.0)
- System.ComponentModel.Annotations (>= 5.0.0)
-
net10.0-maccatalyst26.0
- CommunityToolkit.Maui (>= 14.2.0)
- FluentValidation (>= 12.1.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.1.1)
- MediatR (>= 14.1.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Maui.Controls (>= 10.0.71)
- Microsoft.Maui.Controls.Compatibility (>= 10.0.71)
- PC.Framework.Core (>= 0.0.11)
- ReactiveUI.Maui (>= 23.2.28)
- ReactiveUI.Validation (>= 7.1.0)
- System.ComponentModel.Annotations (>= 5.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 |
|---|---|---|
| 5.0.2 | 131 | 10/9/2026 |
| 4.5.16 | 250 | 8/10/2026 |
| 4.5.15 | 120 | 8/9/2026 |
| 4.5.14 | 136 | 7/29/2026 |
| 4.5.13 | 185 | 7/15/2026 |
| 4.5.12 | 119 | 7/13/2026 |
| 4.5.11 | 119 | 7/13/2026 |
| 4.5.10 | 146 | 7/13/2026 |
| 4.5.9 | 121 | 7/11/2026 |
| 4.5.8 | 127 | 7/10/2026 |
| 4.5.7 | 134 | 7/10/2026 |
| 4.5.6 | 122 | 7/10/2026 |
| 4.5.5 | 120 | 7/10/2026 |
| 4.5.4 | 130 | 7/10/2026 |
| 4.5.3 | 138 | 7/6/2026 |
| 4.5.2 | 127 | 7/1/2026 |
| 4.5.1 | 131 | 6/29/2026 |
| 4.5.0 | 130 | 6/23/2026 |
| 4.4.0 | 738 | 7/31/2025 |
| 4.3.9 | 280 | 6/16/2025 |