v2.3.0 AquariusRouter + AquariusValee (New)
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 -
RouteRecordwith nestedChildren,Name,Redirect,Alias, and
per-recordMeta. - 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 anyButton/Hyperlink/element, with live
IsActive/IsExactActivestate you can style against.{aqr:RouteTo}- builds aRouteLocationRawstraight 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/
ViewModelsassign theDataContextwithout 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, andTextResolverlets 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.AquariusValeeField<T>/Form- reactive validation state, withFieldMeta/FormMetaexposing
Touched/Dirty/Pending/Valid(all computed on read, never cached).System.ComponentModel.DataAnnotationsis 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
IValidatableObjectresolve at all. - Ad-hoc mode -
Form.DefineField(...)per field, no model class.
- Schema mode -
INotifyDataErrorInfo-native - errors reach WPF through the mechanism WPF already has, so
the built-in validation adorner works with no extra plumbing (andValee.Formsuppresses it at
container level, where it would otherwise light up the whole panel).- Three cross-field mechanisms -
[Compare]-style attributes via schema mode,
IValidatableObjectfor model-level rules, andField<T>.DependsOnfor hand-wired
dependencies.PropertyRangeAttribute+IDependsOnPropertieslet an attribute declare its own
dependencies soFromModelwires the re-validation up for you. - Async rules -
AsyncValidationRule<T>with cancellation, surfaced throughMeta.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 madePasswordBox.Passwordbindable),Valee.Maskfor positional input
masks, andValee.Numeric+Valee.Minimum/Maximumfor filtered numeric entry that clamps on
blur rather than mid-keystroke. PlusFieldTemplateSelector(control-per-field from[UIHint]/
[DataType]) andSafeMessageConverter(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
RegularExpressionAttributethat can actually takeRegexOptions), andPropertyRange.
🚀 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, viaRegisterHotKey, fires even when the app isn't focused), and
ChordBinding(multi-step, Visual-Studio-styleCtrl+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:InputSystemsection, 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.
IAiAgentFactorybuilds anAIAgent(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
AgentSessioncarries history across turns. IAiSessionStore/FileAiSessionStorepersist 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.
SeeAgentApprovalExtensions. AiAgentOptions-Name,Description,Instructions,ToolApproval,IncludeMcpTools,
EnableLogging,EnableOpenTelemetry, bindable fromBarbatos:Mcp:Agent.ConfigureMcptakes a third optionalconfigureAgentcallback.
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 aReceiptId, so a server with an offline queue knows it can stop re-sending.PushNotificationOptions.DeduplicationHistorySize(default256) - 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.0disables 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-formTagsdictionary sent with the handshake so a server can target a subset of devices,
and a configurableAcknowledgeMethodName(set it tonullfor 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/OnBeforeMountare now tracked perDataContext, not by a one-shot
flag. An element'sDataContextis very often replaced betweenInitializedandLoaded-
by a parent'sDataContext="{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 receivedOnMountedhaving never
seenOnCreated/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-appexposes 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 (viaide.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.Enableconvention on one route,RouteRecord.ViewModelpairing on the other), both
RouterLink.Toforms, an in-app route-search box, and - with--IncludeValee- aSignUpView
covering all fourValee.*control bindings. - New item template
aqv-form- scaffolds a schema-mode (Form.FromModel) View + ViewModel +
model, withValee.Form/Field/PasswordFieldalready 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.SampleandBarbatos.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.Corepins the10.0.17763.0platform version, as before). - Recompile rather than binary-swapping
Barbatos.Wpf.Core.ConfigureMcpgained 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.AquariusRouterandBarbatos.Wpf.AquariusValeeeach depend on
Barbatos.Wpf.Aquarius- install it alongside either one, the same wayvue-routerand
vee-validateeach needvue.- The Aquarius lifecycle change is a behavioral fix. If you were relying on
OnCreatedfiring
exactly once per element even across aDataContextswap, it now fires once perDataContext.