Navs 2.0.1

dotnet add package Navs --version 2.0.1
                    
NuGet\Install-Package Navs -Version 2.0.1
                    
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.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Navs" Version="2.0.1" />
                    
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.1
                    
#r "nuget: Navs, 2.0.1"
                    
#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.1
                    
#: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.1
                    
Install as a Cake Addin
#tool nuget:?package=Navs&version=2.0.1
                    
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 27 10/18/2026
2.0.0 40 10/18/2026
1.0.1 58 10/17/2026
1.0.0 224 6/25/2026
1.0.0-rc-008 546 6/17/2025
1.0.0-rc-007 304 6/4/2025
1.0.0-rc-006 303 5/16/2025
1.0.0-rc-005 326 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

### Fixed

- **Navs (breaking behavioral):** `Async` route handlers and guards now start and resume on the caller's `SynchronizationContext`. Avalonia and FuncUI code can update UI after an await. Use the `Task` overloads or `Async.SwitchToThreadPool` for thread-pool work.
- **Samples:** The Avalonia samples no longer block the UI thread with `GetAwaiter().GetResult()`. Failures are logged, and Routerish shows sync, async, task and cancellable-task handlers with matching guards.
- **Docs:** The `UrlTemplates` documentation script references `2.*` instead of `1.*`.