Navs 2.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Navs --version 2.0.0
                    
NuGet\Install-Package Navs -Version 2.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="Navs" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Navs" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Navs" />
                    
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 Navs --version 2.0.0
                    
#r "nuget: Navs, 2.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 Navs@2.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=Navs&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Navs&version=2.0.0
                    
Install as a Cake Tool

The Navs Family

Navs is a router family for .NET applications. The core library is UI-agnostic; the adapters bind it to Avalonia, FuncUI and Terminal.Gui.

  • Navs — core router, route table, guards, history and cache.
  • UrlTemplates — URL and route-pattern parsing.
  • Navs.Avalonia — Avalonia controls.
  • Navs.FuncUI — Avalonia.FuncUI components.
  • Navs.Terminal.Gui — Terminal.Gui controls.
  • Navs.CSharp — C#-friendly facade.
  • UrlTemplates.CSharp — C#-friendly facade for URL templates.
open System
open System.Threading
open Navs
open Navs.Router

let routes = [
    Route.define<string>("home", "/", fun _ _ -> "Home")
    Route.define<string>("about", "/about", fun _ _ -> "About")
    Route.define<string>(
        "guid",
        "/:id<guid>",
        fun context _ -> async {
            do! Async.Sleep 90
            return
                match context.GetParam<Guid>("id") with
                | ValueSome id -> $"Guid {id}"
                | ValueNone -> "No GUID"
        }
    )
]

let router =
    match Router.createDefault(routes) with
    | Ok router -> router
    | Error errors -> invalidOp $"Invalid routes: %A{errors}"

let result =
    router.Navigate("/about") CancellationToken.None
    |> fun task -> task.GetAwaiter().GetResult()

match result with
| Ok _ -> printfn "Current view: %A" router.Snapshot.Content
| Error error -> printfn "Navigation failed: %A" error

Navigate returns a CancellableTask<Result<...>>. Pass the token when you invoke it. The router exposes its current state through router.Snapshot and publishes changes through router.Changed.

using Navs.CSharp;

var routes = new[]
{
    Route.Define<string>("home", "/", (context, navigable) => "Home"),
    Route.Define<string>("about", "/about", (context, navigable) => "About")
};

var router = Router.Create(routes);
var outcome = await router.NavigateAsync("/about");

if (outcome.IsSuccess)
{
    Console.WriteLine(router.Snapshot.Content);
}
else
{
    Console.WriteLine(outcome.Error?.Code);
}

C# receives NavigationOutcome; it does not need to handle F# Result or ValueOption. TryBuildLink and IsActive generate links and answer active-route questions, and TryGetParam<T>/GetParamValues<T> read typed parameters from the route context or the snapshot.

open System.Threading
open Avalonia.Controls
open Navs
open Navs.Router
open Navs.Avalonia

let block text =
    let value = TextBlock()
    value.Text <- text
    value :> Control

let routes = [
    Route.define<Control>("home", "/", fun _ _ -> block "Home")
    Route.define<Control>("about", "/about", fun _ _ -> block "About")
]

let router = AvaloniaRouter(routes) :> IRouter<Control>

let outlet = RouterOutlet()
outlet.Router <- Some router

router.Navigate("/") CancellationToken.None |> ignore
open Avalonia.FuncUI.DSL
open Avalonia.FuncUI.Types
open Navs
open Navs.Router
open Navs.FuncUI

let routes = [
    Route.define<IView>("books", "/books", fun _ _ -> TextBlock.create [])
    Route.define<IView>("about", "/about", fun _ _ -> TextBlock.create [])
]

let router = FuncUIRouter(routes) :> IRouter<IView>
let outlet = RouterOutlet.create(router)
open Terminal.Gui.Views
open Navs
open Navs.Router
open Navs.Terminal.Gui

let routes = [
    Route.define<Window>("home", "/", fun _ _ -> Window())
    Route.define<Window>("about", "/about", fun _ _ -> Window())
]

let router = TerminalGuiRouter(routes) :> IRouter<Window>
let outlet = RouterOutlet(router)

UrlTemplates

UrlTemplates parses URL templates and URLs. Navs compiles route patterns at router creation, so matching does not reparse templates on every navigation.

open UrlTemplates.RouteMatcher

let template = "/api/v1/profiles/:id<int>?optionalKey<guid>&requiredKey!#hash"
let url = "/api/v1/profiles/2345?requiredKey=2#hash"

match RouteMatcher.matchStrings template url with
| Ok (parsedTemplate, urlInfo, urlMatch) ->
    printfn "%A" parsedTemplate
    printfn "%A" urlInfo
    printfn "%A" urlMatch
| Error errors -> printfn "%A" errors

UrlTemplates (C#)

UrlTemplates.CSharp exposes the same operations through Try... methods, typed parameters and structured errors, with no F# types in the signatures.

using UrlTemplates.CSharp;

if (UrlTemplate.TryParse("/api/v1/profiles/:id<int>", out var template, out var templateError))
{
    if (RouteMatcher.TryMatch(template!, "/api/v1/profiles/2345", out var match, out var errors))
    {
        match!.TryGetPathParam<int>("id", out var id);
        Console.WriteLine(id); // 2345
    }
}
Product Compatible and additional computed target framework versions.
.NET 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 is compatible.  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 (4)

Showing the top 4 NuGet packages that depend on Navs:

Package Downloads
Navs.Avalonia

Package Description

Navs.FuncUI

Package Description

Navs.Terminal.Gui

Package Description

Navs.CSharp

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.1 47 10/18/2026
2.0.0 52 10/18/2026
1.0.1 60 10/17/2026
1.0.0 224 6/25/2026
1.0.0-rc-008 547 6/17/2025
1.0.0-rc-007 305 6/4/2025
1.0.0-rc-006 303 5/16/2025
1.0.0-rc-005 330 11/30/2024
1.0.0-rc-004 234 11/27/2024
1.0.0-rc-003 237 11/14/2024
1.0.0-rc-002 223 11/13/2024
1.0.0-rc-001 357 3/28/2024
1.0.0-beta-008 250 3/17/2024
1.0.0-beta-007 251 3/15/2024
1.0.0-beta-006 235 3/13/2024
1.0.0-beta-005 251 3/10/2024
1.0.0-beta-004 256 3/9/2024
1.0.0-beta-003 257 3/7/2024
1.0.0-beta-002 241 3/6/2024
1.0.0-beta-001 253 3/6/2024

### Added

- **UrlTemplates.CSharp:** C# facade with `UrlTemplate.TryParse`, `UrlTemplate.TryBuild`, `RouteMatcher.TryParseUrl` and three `TryMatch` overloads. Parsed templates, URLs and matches are exposed through `Template`, `Url` and `RouteMatch`, with typed parameter access and structured `MatchError` values instead of F# results, options and lists.
- **Navs.CSharp:** C# facade with `NavigationOutcome`, `NavigationErrorCode`, `Route.Define` and `Router.Create`; C# callers no longer handle F# `Result`, `Option` or `CancellableTask`. `ICSharpRouter<TView>` is disposable and releases its changed-event subscription. Routes expose `CanActivate`/`CanDeactivate` through `GuardResult` and `Guard`. `TryBuildLink` and `IsActive` generate links and answer active-route questions, and `RouteContext`/`RouteSnapshot` offer `TryGetParam<T>` and `GetParamValues<T>` instead of `FSharpValueOption`.
- **Navs:** Compiled route table with deterministic static/parameter ranking, duplicate and invalid-pattern validation, and ambiguous-pattern rejection at router creation. Declarative redirects resolve through a bounded chain.
- **Navs:** `IRouteLinks` URL generation with path and query encoding, plus active-route checks.
- **Navs:** `Snapshot` and `Changed` expose state, active route, content and history as one consistent value.
- **Navs:** `IHistory.Current` and C# `HistorySnapshot.TryGetCurrent` expose the current history entry.
- **Navs:** Per-route cache policies with TTL expiry, bounded eviction and explicit `ClearCache`; `NoCache` remains the default. Cache hits still run guards, and expired or evicted entries are not reused.
- **Navs.Terminal.Gui:** `TerminalGuiRouter` and render-only `RouterOutlet` for Terminal.Gui `Window` values.
- **Project:** C# consumer tests, adapter tests and v2 core tests covering navigation, guards, history, cache, links, public surfaces and an FsCheck URL round-trip property.

### Changed

- **Navs (breaking):** `Navigate`, `NavigateByName`, `Back` and `Forward` return `CancellableTask<Result<NavigationResult, NavigationError>>`; callers pass the token when invoking them.
- **Navs (breaking):** `IRouter` exposes `Snapshot`, `Changed`, `History`, `Links` and `ClearCache`; the old `State`, `Content` and `GetView` members are removed.
- **Navs (breaking):** `FSharp.Data.Adaptive` is no longer a dependency, so adaptive composition over router state is gone. Read `Snapshot` or subscribe to `Changed` instead.
- **Navs (breaking):** `HistorySnapshot.Entries` is an `IReadOnlyList<HistoryEntry>` instead of an F# list.
- **Navs (breaking):** Router construction now validates and compiles the route table once through `Router.create`; the old `Router.build` and adapter-specific route wrappers are removed.
- **Navs (breaking):** Guard order is registration order; deactivation guards run before activation guards. Redirects are bounded by `MaxRedirects`.
- **Navs (breaking):** `ConcurrencyPolicy` supports `LatestWins`, `Serialize` and `Reject`; the default remains `LatestWins`. A superseded navigation cannot commit history.
- **Navs (breaking):** URL and query parameter values are encoded; missing optional query parameters are omitted.
- **UrlTemplates (breaking):** Parsed URLs now percent-decode path, query and hash values and treat `+` as a space in query values.
- **UrlTemplates (breaking):** Malformed templates and URLs report the offending character and its index instead of the parser's expectation list; the message no longer depends on the parsing engine. The package depends on XParsec instead of FParsec.
- **Navs.Avalonia (breaking):** `AvaloniaRouter` and `RouterOutlet` are render-only; the `Routes` control and adapter `Route` wrappers are removed.
- **Navs.FuncUI (breaking):** `FuncUIRouter` and `RouterOutlet.create` use the core `RouteDefinition<IView>`; `useRouter` remains the component hook.
- **Navs.Terminal.Gui (breaking):** `TerminalGuiRouter` and `RouterOutlet` use the core `RouteDefinition<Window>`.
- **Docs:** README, FsDocs guides and all samples were migrated to the v2 API.

### Fixed

- **Navs:** A cancelled navigation now leaves the router `Idle` instead of stuck in `Navigating`.
- **Navs:** A stopped deactivation guard reports the active route and leaves the previous content in place.