MFToolkit.Routing 1.0.6

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

MFToolkit.Routing 使用指南

框架无关的 .NET 路由导航库,支持 Avalonia、WPF、MAUI 等 UI 框架


目录


快速开始

1. 安装

dotnet add package MFToolkit.Routing

2. 定义页面

// 定义一个支持生命周期钩子的页面
public class HomePage : INavigationAware
{
    public void OnNavigated(Dictionary<string, object?>? parameters)
    {
        Console.WriteLine("HomePage 已激活");
    }

    public void OnNavigatingFrom()
    {
        Console.WriteLine("即将离开 HomePage");
    }

    public void OnNavigatedFrom()
    {
        Console.WriteLine("已离开 HomePage");
    }
}

public class SettingsPage : INavigationAware
{
    public void OnNavigated(Dictionary<string, object?>? parameters)
    {
        var userId = parameters?["userId"];
        Console.WriteLine($"SettingsPage 已激活,参数: {userId}");
    }

    public void OnNavigatingFrom() { }
    public void OnNavigatedFrom() { }
}

3. 配置 DI 并注册路由

// Program.cs 或 Startup.cs
var builder = WebApplication.CreateBuilder(args);

// 添加路由服务
builder.Services.AddRouting();

// 注册路由
builder.Services.AddRoutes(routes =>
{
    routes.Register(new RouteEntity
    {
        RoutePath = "/home",
        RouteType = typeof(HomePage),
        IsTop = true
    });

    routes.Register(new RouteEntity
    {
        RoutePath = "/settings",
        RouteType = typeof(SettingsPage)
    });
});

var app = builder.Build();

4. 在代码中使用导航

public class MyService
{
    private readonly IRouter _router;

    public MyService(IRouter router)
    {
        _router = router;
    }

    public async Task NavigateToSettings()
    {
        // 方式1:通过路由路径导航
        var result = await _router.NavigateAsync("/settings");

        // 方式2:通过页面类型导航
        var result2 = await _router.NavigateAsync<SettingsPage>();

        // 方式3:带参数导航
        var result3 = await _router.NavigateAsync("/settings", new Dictionary<string, object?>
        {
            ["userId"] = 123,
            ["returnUrl"] = "/home"
        });

        if (result.IsSuccess)
        {
            Console.WriteLine("导航成功");
        }
        else if (result.IsBlocked)
        {
            Console.WriteLine("导航被守卫阻止");
        }
        else if (result.IsNotFound)
        {
            Console.WriteLine("路由未找到");
        }
    }

    public async Task GoBack()
    {
        if (_router.CanGoBack)
        {
            await _router.GoBackAsync();
        }
    }
}

核心概念

RouteEntity(路由实体)

表示一个路由的配置信息:

public class RouteEntity
{
    public Guid Id { get; set; }              // 唯一ID,自动生成
    public string? RoutePath { get; set; }    // 路由路径,如 "/home/settings"
    public string? RouteName { get; set; }   // 显示名称
    public required Type RouteType { get; set; }  // 页面类型(必须)
    public Type? ViewModelType { get; set; } // 视图模型类型(可选)
    public Dictionary<string, object?>? DefaultParameters { get; set; }  // 默认参数
    public bool IsKeepalive { get; set; }     // 是否保持活跃
    public bool IsTop { get; set; }           // 是否为顶级路由
    public bool IsLazy { get; set; }          // 是否懒加载
    public int SortOrder { get; set; }        // 排序权重
    public string RouteKey => RoutePath ?? RouteType.Name;  // 唯一键
}

RouteEntry(路由条目)

表示栈中的一个实际导航记录:

public class RouteEntry
{
    public RouteEntity Entity { get; }        // 关联的路由实体
    public Dictionary<string, object?>? Parameters { get; }  // 当前导航参数
    public DateTime NavigatedAt { get; }      // 导航时间
    public object? PageInstance { get; set; } // 页面实例(框架侧填充)
    public object? ViewModelInstance { get; set; } // 视图模型实例(Router 创建)
    public bool IsActivated { get; set; }     // 是否已激活
}

RouteStack(路由栈)

每个顶级路由拥有独立的栈:

顶级路由 A 的栈:  [PageA1] → [PageA2] → [PageA3] (当前)
顶级路由 B 的栈:  [PageB1] → [PageB2] (当前)

父子关系由栈历史自动确定:

// 获取当前路由的父路由(栈中上一个条目)
var parent = router.CurrentStack.Parent;

// 获取完整祖先链(从根到父)
var ancestors = router.CurrentStack.GetAncestors();

路由注册

方式一:fluent API

builder.Services.AddRoutes(routes =>
{
    routes.Register(new RouteEntity
    {
        RoutePath = "/home",
        RouteType = typeof(HomePage),
        IsTop = true
    });

    routes.Register(new RouteEntity
    {
        RoutePath = "/settings",
        RouteType = typeof(SettingsPage),
        IsKeepalive = true  // 保持活跃
    });
});

方式二:直接传入路由集合

var routes = new[]
{
    new RouteEntity
    {
        RoutePath = "/home",
        RouteType = typeof(HomePage),
        IsTop = true
    },
    new RouteEntity
    {
        RoutePath = "/about",
        RouteType = typeof(AboutPage)
    }
};

builder.Services.AddRoutes(routes);

注意AddRoutes 会自动将 RouteTypeViewModelType 注册到 DI 容器,无需手动注册。


导航操作

基本导航

// 通过路由路径
await router.NavigateAsync("/home");

// 通过页面类型
await router.NavigateAsync<HomePage>();
await router.NavigateAsync(typeof(HomePage));

// 带字典参数
await router.NavigateAsync("/settings", new Dictionary<string, object?>
{
    ["userId"] = 123,
    ["mode"] = "edit"
});

// 指定导航动作类型(UI 框架根据 action 决定如何显示页面)
await router.NavigateAsync("/home", action: "Present");   // 以模态方式呈现
await router.NavigateAsync("/home", action: "Push");      // 默认推入

// 带参数 + 动作
await router.NavigateAsync("/settings", new Dictionary<string, object?> { ["id"] = 1 }, action: "Modal");

路径参数导航

路由路径支持 :paramName 格式的参数占位符:

// 注册带参数的路由
builder.Services.AddRoutes(routes =>
{
    routes.Register(new RouteEntity
    {
        RoutePath = "/user/:userId",
        RouteType = typeof(UserDetailPage)
    });

    routes.Register(new RouteEntity
    {
        RoutePath = "/user/:userId/profile/:tab",
        RouteType = typeof(UserProfilePage)
    });
});

// 方式一:路径参数(自动解析)
await router.NavigateAsync("/user/123");
// 参数自动提取: { "userId": "123" }

// 方式二:字典参数
await router.NavigateAsync("/user/:userId", new Dictionary<string, object?>
{
    ["userId"] = 456
});

// 方式三:混合使用(参数合并)
await router.NavigateAsync("/user/123/profile", new Dictionary<string, object?>
{
    ["tab"] = "settings"  // 额外参数
});
// 最终参数: { "userId": "123", "tab": "settings" }

返回操作

// 返回上一页(默认 action = NavigationActions.Pop)
if (router.CanGoBack)
{
    await router.GoBackAsync();
}

// 自定义 action
await router.GoBackAsync(action: "PopWithAnimation");

// 返回到栈顶(清空当前栈,默认 action = NavigationActions.PopToRoot)
await router.GoBackToRootAsync();

// 返回到指定页面(默认 action = NavigationActions.PopToPage)
await router.GoBackToAsync("/home");
await router.GoBackToAsync<HomePage>();

切换顶级路由

// 获取所有顶级路由
var topRoutes = router.RegisteredTopRoutes;

// 切换到指定顶级路由
router.SwitchTopRoute(topRoutes[0].Id);

路由守卫

定义守卫

public class AuthGuard : IRouteGuard
{
    private readonly IAuthService _authService;

    public AuthGuard(IAuthService authService)
    {
        _authService = authService;
    }

    public async Task<bool> CanNavigateAsync(RouteEntity targetRoute, Dictionary<string, object?>? parameters)
    {
        // 检查是否需要登录
        if (RequiresAuth(targetRoute))
        {
            return await _authService.IsLoggedInAsync();
        }
        return true;
    }

    public async Task OnNavigationBlockedAsync(RouteEntity targetRoute, Dictionary<string, object?>? parameters)
    {
        // 导航被阻止时的处理
        Console.WriteLine($"访问 {targetRoute.RoutePath} 被阻止");
        // 例如:跳转到登录页
        // await _router.NavigateAsync("/login");
    }

    private bool RequiresAuth(RouteEntity route)
    {
        // 根据路由路径判断是否需要认证
        return route.RoutePath?.StartsWith("/settings") == true
            || route.RoutePath?.StartsWith("/profile") == true;
    }
}

注册守卫

// 方式一:泛型快捷注册(单个守卫)
builder.Services.AddRouting<AuthGuard>();

// 方式二:配置回调注册多个守卫
builder.Services.AddRouting(options =>
{
    options.GuardTypes.Add(typeof(AuthGuard));
    options.GuardTypes.Add(typeof(RoleGuard));  // 按添加顺序执行
});

// 方式三:直接添加(框架层自行管理)
builder.Services.AddSingleton<IRouteGuard, AuthGuard>();
builder.Services.AddSingleton<IRouteGuard, CustomGuard>();

守卫链

多个守卫按注册顺序执行,任一返回 false 即阻止导航:

// 执行顺序:AuthGuard → RoleGuard → CustomGuard
// 如果 AuthGuard 返回 false,后续守卫不会执行

生命周期钩子

INavigationAware 接口

public class MyPage : INavigationAware
{
    /// <summary>
    /// 页面激活时调用
    /// </summary>
    public void OnNavigated(Dictionary<string, object?>? parameters)
    {
        // 接收导航参数
        var id = parameters?["id"];
        Console.WriteLine($"页面激活,参数: {id}");
    }

    /// <summary>
    /// 即将离开页面时调用
    /// </summary>
    public void OnNavigatingFrom()
    {
        // 保存状态、取消订阅等
        Console.WriteLine("即将离开页面");
    }

    /// <summary>
    /// 已离开页面后调用
    /// </summary>
    public void OnNavigatedFrom()
    {
        // 清理资源
        Console.WriteLine("已离开页面");
    }
}

基类方式

// 使用 NavigationAware 基类,只需重写关心的方法
public class MyPage : NavigationAware
{
    public override void OnNavigated(Dictionary<string, object?>? parameters)
    {
        // 处理导航参数
    }
}

生命周期时序

导航时:

OnNavigatingFrom (当前页) → 入栈 → OnNavigated (目标页)

返回时(非 KeepAlive):

OnNavigatingFrom (当前页) → OnNavigatedFrom → 销毁实例 → 出栈 →
    (新栈顶实例存在?) → OnNavigated : 重建实例 → OnNavigated

ViewModel 支持

注册路由时指定 ViewModel

builder.Services.AddRoutes(routes =>
{
    routes.Register(new RouteEntity
    {
        RoutePath = "/home",
        RouteType = typeof(HomePage),
        ViewModelType = typeof(HomeViewModel),
        IsTop = true
    });

    routes.Register(new RouteEntity
    {
        RoutePath = "/user",
        RouteType = typeof(UserPage),
        ViewModelType = typeof(UserViewModel)
    });
});

ViewModel 实现 IQueryAttributable

public class UserViewModel : IQueryAttributable
{
    public string? UserId { get; set; }
    public string? Tab { get; set; }

    public void ApplyQueryAttributes(IDictionary<string, object?> parameters)
    {
        if (parameters.TryGetValue("userId", out var userId))
            UserId = userId as string;

        if (parameters.TryGetValue("tab", out var tab))
            Tab = tab as string;
    }
}

生命周期推断规则

路由类型 Page 生命周期 ViewModel 生命周期
顶级路由 (IsTop = true) Singleton Singleton
KeepAlive Transient + 缓存 Transient + 缓存
普通页面 Transient Transient

注意:通过 AddRoutes 注册路由时,PageType 和 ViewModelType 会自动注册到 DI 容器,生命周期根据 IsTop 推断。

框架层集成

Router 会自动从 DI 获取 Page 和 ViewModel 实例,框架层只需处理 UI 切换:

// 框架订阅 Navigated 事件
router.Navigated += (sender, args) =>
{
    // Router 已经创建了 PageInstance 和 ViewModelInstance
    // 框架层只需绑定 ViewModel 并切换 UI

    if (args.To?.PageInstance != null)
    {
        // 绑定 ViewModel(框架做的事)
        if (args.To.ViewModelInstance != null)
        {
            // WPF/Avalonia: page.BindingContext = args.To.ViewModelInstance;
            // MAUI: page.BindingContext = args.To.ViewModelInstance;
        }

        // 切换 UI
        Frame.Navigate(args.To.PageInstance);
    }
};

职责划分

谁做 做什么
Router 从 DI 获取 Page 和 ViewModel,触发 INavigationAware,触发 IQueryAttributable
框架层 绑定 ViewModel 到 BindingContext,处理 UI 切换

事件通知

Router 提供事件供 UI 框架订阅:

// 从 DI 获取 Router
var router = _serviceProvider.GetRequiredService<IRouter>();

// 导航开始
router.NavigationStarting += (sender, args) =>
{
    Console.WriteLine($"开始导航: {args.From?.Entity.RouteKey} → {args.To?.Entity.RouteKey}");
};

// 导航完成
router.Navigated += (sender, args) =>
{
    Console.WriteLine($"导航完成: {args.From?.Entity.RouteKey} → {args.To?.Entity.RouteKey}");
};

// 导航失败
router.NavigationFailed += (sender, args) =>
{
    Console.WriteLine($"导航失败: {args.Status} - {args.Message}");
};
public class NavigationEventArgs : EventArgs
{
    public string Action { get; }         // 导航动作类型
    public RouteEntry? From { get; }      // 来源路由
    public RouteEntry? To { get; }        // 目标路由
    public NavigationStatus Status { get; }  // 状态
    public string? Message { get; }       // 消息
    public Dictionary<string, object?>? Parameters { get; }  // 参数
}
public static class NavigationActions
{
    public const string Push = "Push";       // 推入新页面
    public const string Pop = "Pop";         // 弹出当前页面
    public const string PopToRoot = "PopToRoot";  // 弹出到根页面
    public const string PopToPage = "PopToPage"; // 弹出到指定页面
    public const string Replace = "Replace"; // 替换当前页面
    public const string SwitchTop = "SwitchTop"; // 切换顶级路由
}

UI 框架集成示例

// 订阅导航事件,框架层根据 Action 类型处理 UI
router.Navigated += (sender, args) =>
{
    switch (args.Action)
    {
        case NavigationActions.Push:
            // 推入新页面
            var page = _sp.GetRequiredService(args.To!.Entity.RouteType);
            args.To.PageInstance = page;
            Frame.Push(page);
            break;

        case NavigationActions.Pop:
            // 弹出页面
            Frame.Pop(args.From!.PageInstance);
            break;

        case NavigationActions.PopToRoot:
            // 弹出到根页面
            Frame.PopToRoot();
            break;

        case NavigationActions.PopToPage:
            // 弹出到指定页面(框架自己处理具体逻辑)
            Frame.PopToPage(args.To);
            break;

        case NavigationActions.Replace:
            // 替换当前页面
            Frame.Replace(args.To!);
            break;

        case NavigationActions.SwitchTop:
            // 切换顶级路由
            Frame.SwitchTo(args.To!);
            break;

        default:
            // 自定义动作类型
            break;
    }
};

高级用法

KeepAlive 页面缓存

// 注册 KeepAlive 页面
builder.Services.AddRoutes(routes =>
{
    routes.Register(new RouteEntity
    {
        RoutePath = "/dashboard",
        RouteType = typeof(DashboardPage),
        IsKeepalive = true  // 离开时保留实例
    });
});

// 行为:
// 1. 首次访问:创建实例
// 2. 导航离开:保留实例(不销毁)
// 3. 返回访问:复用已有实例
// 4. 显式返回根路由:销毁实例

默认顶级路由

当没有注册任何顶级路由时,系统自动创建一个默认顶级路由作为兜底:

// 通过 AddRoutes 注册路由
builder.Services.AddRoutes(routes =>
{
    // 这种情况会使用默认顶级路由
    routes.Register(new RouteEntity
    {
        RoutePath = "/home",
        RouteType = typeof(HomePage)
        // 没有设置 IsTop = true
    });

    // 注册首个 IsTop = true 的路由时,自动切换到它
    routes.Register(new RouteEntity
    {
        RoutePath = "/admin",
        RouteType = typeof(AdminPage),
        IsTop = true  // 切换到 admin 栈
    });
});

嵌套路由

父子关系由栈历史自动确定,无需预配置:

// 注册路由(无需配置父子关系)
builder.Services.AddRoutes(routes =>
{
    routes.Register(new RouteEntity { RoutePath = "/home", RouteType = typeof(HomePage), IsTop = true });
    routes.Register(new RouteEntity { RoutePath = "/home/settings", RouteType = typeof(SettingsPage) });
    routes.Register(new RouteEntity { RoutePath = "/home/profile", RouteType = typeof(ProfilePage) });
});

// 导航时自动建立父子关系
await router.NavigateAsync("/home");           // 栈: [HomePage]
await router.NavigateAsync("/home/settings");  // 栈: [HomePage, SettingsPage]
                                              // SettingsPage.Parent = HomePage

// 从栈历史获取父路由
var parent = router.CurrentStack.Parent;  // HomePage
var ancestors = router.CurrentStack.GetAncestors();  // [HomePage]

AOT 兼容性

本库完整支持 AOT 编译:

// ✅ 正确:使用 typeof
routes.Register(new RouteEntity { RouteType = typeof(HomePage) });

// ❌ 禁止:字符串类型名
routes.Register(new RouteEntity { RouteType = "HomePage" });  // 不支持!

常见问题

Q: 如何在 UI 框架中使用?

A: Router 本身不操作 UI,但会从 DI 获取 Page 和 ViewModel 实例。你需要:

  1. 在框架层订阅 Navigated 事件
  2. 在事件处理中绑定 ViewModel 并执行框架特定的页面切换代码

示例(WPF 伪代码):

router.Navigated += async (sender, args) =>
{
    if (args.To?.PageInstance != null)
    {
        // 绑定 ViewModel
        if (args.To.ViewModelInstance != null)
        {
            args.To.PageInstance.BindingContext = args.To.ViewModelInstance;
        }
        // 切换 UI
        await Dispatcher.InvokeAsync(() => MainFrame.Navigate(args.To.PageInstance));
    }
};

Q: 如何处理导航参数?

A: 在 OnNavigated 方法中接收:

public class MyPage : INavigationAware
{
    public void OnNavigated(Dictionary<string, object?>? parameters)
    {
        if (parameters != null && parameters.TryGetValue("id", out var id))
        {
            // 使用参数
            LoadData((int)id!);
        }
    }
}

API 参考

IRouter

方法/属性 说明
NavigateAsync(routeKey, parameters, action) 通过路由键导航(默认 Push)
NavigateAsync<T>(parameters, action) 通过类型导航(泛型,默认 Push)
GoBackAsync(action) 返回上一页(默认 Pop)
GoBackToRootAsync(action) 返回栈顶(默认 PopToRoot)
GoBackToAsync(routeKey, action) 返回到指定路由(默认 PopToPage)
GoBackToAsync<T>(action) 返回到指定类型页面(默认 PopToPage)
ReplaceAsync(routeKey, parameters, action) 替换当前页面(默认 Replace)
ReplaceAsync<T>(parameters, action) 替换当前页面(泛型,默认 Replace)
SwitchTopRoute(topRouteId) 切换顶级路由
CurrentRoute 当前路由条目
CurrentStack 当前栈
StackDepth 栈深度
CanGoBack 是否可以返回
属性 说明
IsSuccess 是否成功
IsBlocked 是否被阻止
IsNotFound 是否未找到
IsCancelled 是否取消
Status 状态枚举
TargetRoute 目标路由
Message 消息

文档版本:v1.8 最后更新:2026-04-25

变更记录

版本 日期 变更内容
v1.0.0 2026-04-23 初始版本
v1.0.1 2026-04-23 新增 IQueryAttributable 接口支持
v1.0.2 2026-04-23 删除 ParentId,改为栈历史动态获取父子关系
v1.0.3 2026-04-23 新增 ViewModelType/ViewModelInstance 支持,Router 创建 ViewModel
v1.0.4 2026-04-23 AddRoutes 自动注册 PageType/ViewModelType 到 DI 容器
v1.0.5 2026-04-23 新增 NavigationActions 枚举和 NavigationEventArgs.Action,提供导航动作类型给 UI 框架
v1.0.6 2026-04-24 RouterOptions.GuardType 改为 GuardTypes 列表,支持注册多个守卫
v1.0.7 2026-04-24 NavigateAsync/ReplaceAsync 增加可选 action 参数,支持自定义导航动作类型
v1.0.8 2026-04-25 GoBackAsync/GoBackToRootAsync/GoBackToAsync 统一补齐 action 参数,所有导航方法默认值均使用 NavigationActions 常量
Product Compatible and additional computed target framework versions.
.NET 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

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.1.1 119 5/5/2026
1.1.0 90 5/3/2026
1.0.9 110 5/3/2026
1.0.8 110 4/28/2026
1.0.7 110 4/25/2026
1.0.6 103 4/25/2026
1.0.5 112 4/23/2026