Navs.CSharp 2.0.0

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

  • net8.0

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
2.0.1 75 10/18/2026
2.0.0 77 10/18/2026

### 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.