Skip to content

v2.3.0 AquariusRouter + AquariusValee (New)

Choose a tag to compare

@StHung StHung released this 01 Aug 16:39

v2.3.0

The family grows from two packages to four. Barbatos.Wpf.AquariusRouter brings Vue Router's
navigation model to WPF, Barbatos.Wpf.AquariusValee brings vee-validate's form-validation
model, and Barbatos.Wpf.Core picks up a full input/shortcut system, an agent layer on top of its
MCP module, and a push-notification pipeline that no longer shows you the same toast twice.

Everything that landed in the un-released 2.2.0 line ships here too.


✨ New package: Barbatos.Wpf.AquariusRouter

A Vue Router-style navigation system - for WPF. Built directly on Barbatos.Wpf.Aquarius, the
same way vue-router is built on vue.

dotnet add package Barbatos.Wpf.AquariusRouter
  • Route table - RouteRecord with nested Children, Name, Redirect, Alias, and
    per-record Meta.
  • Path matching - required, optional, repeatable (+/*) and custom-regex segments, ranked so
    a more specific route wins.
  • RouterView - the outlet. Named outlets and nested outlets both supported.
  • RouterLink - attached property on any Button/Hyperlink/element, with live
    IsActive/IsExactActive state you can style against.
  • {aqr:RouteTo} - builds a RouteLocationRaw straight from XAML, so a nav bar can point at a
    route by name ({aqr:RouteTo Name=user, Params='id=42'}) instead of hardcoding where that
    route currently lives.
  • Pairing a View with its ViewModel from the route table - RouteRecord.ViewModel /
    ViewModels assign the DataContext without either type knowing about the other. This is the
    option for when the View ships in one component library and the ViewModel in another, each under
    its own change control, and you can't edit either.
  • Navigation guards - global (Router.BeforeEach/BeforeResolve/AfterEach), per-route
    (RouteRecord.BeforeEnter), and in-component hooks a ViewModel simply implements
    (IOnBeforeRouteEnter/IOnBeforeRouteUpdate/IOnBeforeRouteLeave/IOnRouteEnter/
    IOnRouteUpdate), with a documented pipeline order across all three.
  • Navigation failures - a cancelled or redirected navigation returns a NavigationFailure
    rather than throwing.
  • In-app search - RouteSearchEntry + RouteSearchIndex: opt-in, per-route search entries so a
    user can type their way straight to a deeply-nested feature instead of walking the menu. Several
    entries may point at one route, so the same feature stays findable under whichever phrasing gets
    typed, and TextResolver lets the titles be resource keys in a localized app. This has no Vue
    Router counterpart - it's new surface. AquariusRouter deliberately ships the query engine only;
    the result list stays ordinary WPF so it can look like the rest of your app.
  • RouteLocationRaw.Parse - turns a "/users/42?tab=billing#top" string into the structured
    target.

✨ New package: Barbatos.Wpf.AquariusValee

A vee-validate-style form-validation system - for WPF. Also built directly on
Barbatos.Wpf.Aquarius.

dotnet add package Barbatos.Wpf.AquariusValee
  • Field<T> / Form - reactive validation state, with FieldMeta/FormMeta exposing
    Touched / Dirty / Pending / Valid (all computed on read, never cached).
  • System.ComponentModel.DataAnnotations is the schema mechanism - no new rule vocabulary to
    learn. Two ways to build a form:
    • Schema mode - Form.FromModel<T>(instance) reflects a plain model class's attributes into
      fields and validates against the live instance, which is what makes [Compare] and
      IValidatableObject resolve at all.
    • Ad-hoc mode - Form.DefineField(...) per field, no model class.
  • INotifyDataErrorInfo-native - errors reach WPF through the mechanism WPF already has, so
    the built-in validation adorner works with no extra plumbing (and Valee.Form suppresses it at
    container level, where it would otherwise light up the whole panel).
  • Three cross-field mechanisms - [Compare]-style attributes via schema mode,
    IValidatableObject for model-level rules, and Field<T>.DependsOn for hand-wired
    dependencies. PropertyRangeAttribute + IDependsOnProperties let an attribute declare its own
    dependencies so FromModel wires the re-validation up for you.
  • Async rules - AsyncValidationRule<T> with cancellation, surfaced through Meta.Pending;
    a superseded run is cancelled rather than allowed to land late.
  • XAML integration - Valee.Form, Valee.Field, Valee.For, Valee.PasswordField (because
    WPF deliberately never made PasswordBox.Password bindable), Valee.Mask for positional input
    masks, and Valee.Numeric + Valee.Minimum/Maximum for filtered numeric entry that clamps on
    blur rather than mid-keystroke. Plus FieldTemplateSelector (control-per-field from [UIHint]/
    [DataType]) and SafeMessageConverter (a localized message that fails to resolve falls back to
    the literal text instead of rendering as nothing).
  • The rules the BCL genuinely lacks - Alpha, AlphaNumeric, AlphaDash, AlphaSpaces,
    OneOf, NotOneOf, Digits, NumericString, IntegerString, Pattern (a
    RegularExpressionAttribute that can actually take RegexOptions), and PropertyRange.

🚀 Barbatos.Wpf.Core

New module: InputSystem

Keyboard shortcuts, conceptually ported from Unity's Input System rather than from WPF's raw
InputBinding: an InputActionMap holds InputActions, an action holds bindings, and the app
subscribes to the action - so rebinding a key never touches the code that reacts to it.

  • Three binding kinds under one action - KeyBinding (in-app, needs focus),
    GlobalKeyBinding (OS-level, via RegisterHotKey, fires even when the app isn't focused), and
    ChordBinding (multi-step, Visual-Studio-style Ctrl+K, Ctrl+C, with a per-step timeout).
  • Interactions - Tap, Hold, MultiTap, Press, so one key can mean different things
    depending on how it's pressed.
  • Runtime rebinding - change a binding while the app runs; global hotkeys additionally need
    IInputSystemService.RefreshBindings() to re-register with the OS.
  • Configuration - binds the Barbatos:InputSystem section, and picks up live edits through
    IOptionsMonitor.
builder.ConfigureInputSystem(options =>
{
    var shortcuts = new InputActionMap("AppShortcuts");
    shortcuts.AddAction("QuickEntry").AddGlobalBinding(Key.Space, ModifierKeys.Control | ModifierKeys.Alt);
    options.ActionMaps.Add(shortcuts);
});

MCP: the Microsoft Agent Framework layer

The MCP module previously gave you one-shot request/response calls. It now also gives you an
agent - registered alongside IAiChatService, not replacing it, because a "summarize this"
call and a conversational assistant genuinely want different things.

  • IAiAgentFactory builds an AIAgent (Microsoft's own type, not a wrapper) over the same
    bring-your-own-key provider and the same connected MCP servers, with logging/OpenTelemetry
    middleware attached.
  • Conversation memory - an AgentSession carries history across turns.
  • IAiSessionStore / FileAiSessionStore persist that conversation across app restarts.
  • Tool approval - under AiToolApprovalMode.Always (the default) a run pauses and hands the
    pending tool call back to the app, so a dialog can ask the end user before anything executes.
    See AgentApprovalExtensions.
  • AiAgentOptions - Name, Description, Instructions, ToolApproval, IncludeMcpTools,
    EnableLogging, EnableOpenTelemetry, bindable from Barbatos:Mcp:Agent.
  • ConfigureMcp takes a third optional configureAgent callback.

BYOK is unchanged and non-negotiable: no API key is ever baked into the app, and the publisher
never pays for the end user's usage.

Push notifications: deliveries you can trust

  • IPushNotification.ReceiptId - the server's own id for a notification.
  • IPushNotificationTransport.AcknowledgeAsync - sent automatically for every notification
    that carries a ReceiptId, so a server with an offline queue knows it can stop re-sending.
  • PushNotificationOptions.DeduplicationHistorySize (default 256) - a server that re-sends
    until acknowledged will occasionally deliver a duplicate when an acknowledgement is lost to a
    dropped connection. This is the window in which that stays invisible to the user. Deliberately
    per-process and never persisted: the only notification a restart can bring back is one the app
    died before acknowledging, and showing that twice beats losing it. 0 disables de-duplication.
  • SignalR transport - AppKey + AppKeyHeaderName (sent both as a header and as
    access_token, since which one reaches the server depends on the negotiated transport), a
    free-form Tags dictionary sent with the handshake so a server can target a subset of devices,
    and a configurable AcknowledgeMethodName (set it to null for a server that doesn't track
    delivery).

Both new interface members are default interface implementations - existing payload types and
custom transports keep compiling untouched.


🐛 Barbatos.Wpf.Aquarius

  • Lifecycle: OnCreated/OnBeforeMount are now tracked per DataContext, not by a one-shot
    flag.
    An element's DataContext is very often replaced between Initialized and Loaded -
    by a parent's DataContext="{Binding ...}" at the usage site, or by a router pairing a View
    with a ViewModel from its route table. With a bare flag, those early hooks were spent on the
    ViewModel that got discarded, and the one actually mounted received OnMounted having never
    seen OnCreated/OnBeforeMount - a half-initialized ViewModel. Remount still resets the pass
    entirely, so every mount stays a fresh mount rather than a resume.

🧰 Templates

templates/ is reorganised one-folder-per-package, and the starter app is now a module picker
rather than a fixed feature set.

  • barbatos-wpf-app exposes 15 boolean --Include* flags - the three Aquarius-family
    packages, Barbatos.i18n, and all 11 of Core's independently-optional modules. Visual Studio's
    "Create a new project" wizard renders them as real checkboxes (via ide.host.json); from Rider
    or the CLI you pass the same flags by hand. Running it with no flags reproduces the previous
    default exactly.
  • The generated app now demonstrates the router's two ViewModel-wiring routes side by side
    (aq:Setup.Enable convention on one route, RouteRecord.ViewModel pairing on the other), both
    RouterLink.To forms, an in-app route-search box, and - with --IncludeValee - a SignUpView
    covering all four Valee.* control bindings.
  • New item template aqv-form - scaffolds a schema-mode (Form.FromModel) View + ViewModel +
    model, with Valee.Form/Field/PasswordField already bound.
dotnet new barbatos-wpf-app -n MyApp --IncludeAquariusRouter true --IncludeValee true
dotnet new aqv-form -n Checkout --namespace MyApp.Features.Checkout

📦 Samples

  • Barbatos.Wpf.AquariusRouter.Sample and Barbatos.Wpf.AquariusValee.Sample - one runnable app
    per new package.
  • Barbatos.Wpf.Samples.Shared - shared components, so the sample apps stop re-implementing the
    same scaffolding.

📋 Upgrade notes

  • No public API was removed or renamed in this release.
  • Target frameworks are unchanged: net8.0-windows, net9.0-windows, net10.0-windows
    (Barbatos.Wpf.Core pins the 10.0.17763.0 platform version, as before).
  • Recompile rather than binary-swapping Barbatos.Wpf.Core. ConfigureMcp gained a third
    optional parameter; that is source-compatible but not binary-compatible, so an assembly compiled
    against 2.2.0 that calls it needs a rebuild.
  • Barbatos.Wpf.AquariusRouter and Barbatos.Wpf.AquariusValee each depend on
    Barbatos.Wpf.Aquarius - install it alongside either one, the same way vue-router and
    vee-validate each need vue.
  • The Aquarius lifecycle change is a behavioral fix. If you were relying on OnCreated firing
    exactly once per element even across a DataContext swap, it now fires once per DataContext.