Umbra.Router.Core
1.0.0
dotnet add package Umbra.Router.Core --version 1.0.0
NuGet\Install-Package Umbra.Router.Core -Version 1.0.0
<PackageReference Include="Umbra.Router.Core" Version="1.0.0" />
<PackageVersion Include="Umbra.Router.Core" Version="1.0.0" />
<PackageReference Include="Umbra.Router.Core" />
paket add Umbra.Router.Core --version 1.0.0
#r "nuget: Umbra.Router.Core, 1.0.0"
#:package Umbra.Router.Core@1.0.0
#addin nuget:?package=Umbra.Router.Core&version=1.0.0
#tool nuget:?package=Umbra.Router.Core&version=1.0.0
π¦ Umbra.Router.Core
A lightweight, framework-agnostic routing system for .NET UI applications.
Works with:
- Avalonia
- WPF
- WinUI / Uno
- MAUI
- WinForms (sim, atΓ© lΓ‘ se vocΓͺ quiser sofrer um pouco π)
β¨ Overview
Umbra Router was designed to provide a clean navigation pipeline with IoC-first resolution, guards, parameters, and view-model binding, without locking you into a specific UI framework.
β‘ Core Idea
The routing pipeline follows this strict order:
URL β Resolver β Guard β ViewModel β View β UI
Why this order?
Performance and predictability.
- Resolver is cheap (types only)
- Guards run BEFORE ViewModel creation
- ViewModel is only created if navigation is allowed
- View is only instantiated at the end
This avoids unnecessary allocations and initialization work.
π§ Key Concepts
1. Route Registration
Routes are registered via RoutesBuilder.
Simple registration
x.Register<HomePage, HomeViewModel>("home");
Angular-style nested routes
x.UseAngularStyleRoutes(new RoutesAngularStyle
{
new RouteAngularStyle
{
Path = "sub",
Children =
{
new RouteAngularStyle
{
Path = "first",
Component = typeof(FirstSubPage),
ViewModel = typeof(FirstSubViewModel)
}
}
}
});
2. Title System (Important)
Each route can define a title template:
SetTitle("Params {0}")
Then navigation replaces {0} dynamically:
_history.NavigateAsync(
url: "example/params?page=2",
title: "2"
);
Result:
Params 2
3. Navigation Flow
When you call:
_history.NavigateAsync("example/params?page=2");
This is what happens internally:
π Pipeline
- URL Parsing
- Route Resolver (IoC-based)
- Guard execution (CanMatch)
- Guard execution (CanDeactivate)
- ViewModel resolution
- View creation
- Binding (ConfigureTView)
- UI update event
π‘οΈ Guards System
Guards control navigation BEFORE ViewModel is created.
Base class
public abstract class NavigationGuardBase : IGuard
{
public async Task<GuardResult> ExecuteGuardAsync(NavigationContext context)
{
var result = await GuardAsync(context);
if (result.Decision == GuardDecision.Allow)
await OnGuardAllow(context);
else
await OnGuardDeny(context);
return result;
}
protected abstract Task<GuardResult> GuardAsync(NavigationContext context);
}
Example usage
x.Register<HomePage, HomeViewModel>("home")
.CanMatchGuard<AuthGuard>()
.CanDeactivateGuard<UnsavedChangesGuard>();
Guard lifecycle
CanMatchβ executed BEFORE ViewModel is createdCanDeactivateβ executed BEFORE leaving current page
If guard returns:
Allowβ navigation continuesDenyβ navigation stopsRedirectβ navigates to another route
π¦ ViewModel Context (IMPORTANT)
This is where many people get confused.
Inside a ViewModel:
public override async Task OnNavigatedToAsync(CancellationToken ctx)
{
Username = Context.Query["name"];
Page = Context.Query["page"];
if (Context.Body.TryGetValue(out ParamsBody body))
Date = body.Date.ToString("hh:mm:ss");
}
β Where does Context come from?
It is injected automatically by the router.
Each ViewModel that implements IRoutePage receives:
NavigationContext
It contains:
Queryβ URL query string (?page=1)Bodyβ navigation payload objectPathβ route pathTitleβ computed titleRouteSnapshotβ full resolved route state
So nothing is "magic" β it's injected during ViewModel resolution.
π§© View Binding (Framework Adapter)
Umbra Router does NOT assume how your UI binds.
Instead, you define it:
public class RouterHistory<TViewModel> : RouterHistoryBase<TViewModel, Control>
{
protected override void ConfigureTView(ref Control? view, TViewModel viewModel)
{
view.DataContext = viewModel;
}
}
Why this exists?
Because Umbra Router is framework-agnostic.
You decide:
- Avalonia β
DataContext - WPF β
DataContext - MAUI β
BindingContext - WinUI β
DataContext
The router does NOT enforce UI behavior.
π§ Router Registration Example
services.AddUmbraRouter<Control, PageViewModelBase>(x =>
{
x.Register<HomePage, HomeViewModel>("home");
x.Register<ParamsPage, ParamsModelView>("example/params");
x.Register<Error404Page, Error404ViewModel>("**");
});
π‘ IoC First Design
Everything is resolved through DI:
- ViewModels
- Views
- Guards
- Router services
No new outside the container.
π Why this router exists
- Avoid heavy navigation frameworks
- Full control over pipeline
- Fast guard-first filtering
- No unnecessary ViewModel instantiation
- Framework-independent design
π§ͺ Mental Model
Think of it like a conveyor belt:
URL
β
Route Resolver (cheap lookup)
β
Guards (can I even continue?)
β
ViewModel creation (DI)
β
View creation (UI layer)
β
Binding injection
β
UI update event
If anything fails early β everything after is skipped.
π§Ό Design Philosophy
- Minimal allocations
- Early rejection (guards first)
- IoC everywhere
- No UI coupling
- Explicit lifecycle hooks
π Final Note
If something looks like βmagicβ in this system, it's usually just:
dependency injection + structured context passing + strict pipeline order
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net6.0
- CommunityToolkit.Mvvm (>= 8.4.2)
- Microsoft.Extensions.DependencyInjection (>= 10.0.6)
-
net8.0
- CommunityToolkit.Mvvm (>= 8.4.2)
- Microsoft.Extensions.DependencyInjection (>= 10.0.6)
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 |
|---|---|---|
| 1.0.0 | 98 | 5/4/2026 |