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

πŸ“¦ 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

  1. URL Parsing
  2. Route Resolver (IoC-based)
  3. Guard execution (CanMatch)
  4. Guard execution (CanDeactivate)
  5. ViewModel resolution
  6. View creation
  7. Binding (ConfigureTView)
  8. 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 created
  • CanDeactivate β†’ executed BEFORE leaving current page

If guard returns:

  • Allow β†’ navigation continues
  • Deny β†’ navigation stops
  • Redirect β†’ 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:

It contains:

  • Query β†’ URL query string (?page=1)
  • Body β†’ navigation payload object
  • Path β†’ route path
  • Title β†’ computed title
  • RouteSnapshot β†’ 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 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. 
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
1.0.0 98 5/4/2026