过去的 ASP.NET Web 窗体和现代 Web 明显区别在于 Web 服务器入口是否存在路由组件。在 Web 窗体中,绝大多数 Web 终结点都是物理文件资源,直接通过其页面路径调用。
通过 ASP.NET MVC,只要请求的 URL 无法映射到物理服务器文件,路由组件就会启动。无论是 HTML 视图、JSON 有效负载、二进制流还是其他输出,路由器都会将请求的 URL 作为要执行的指令,让客户端响应作为其输出。URL 还可以包括可选参数,以帮助路由器确定要呈现的特定内容。
目前所有 Web 开发框架都具有路由组件,Blazor 也不例外。在本文中,我将探讨 Blazor 路由引擎的实现和编程接口。
路由引擎
Blazor 路由引擎是在客户端运行的组件。然而,它的实现是由在浏览器中下载的一个程序集中找到的 C# 代码组成的,并通过 WebAssembly 处理器运行。在 Blazor 应用程序中,路由器当前在 app.cshtml 文件中配置,如下所示:
代码语言:javascript复制<Router AppAssembly=typeof(Program).Assembly />
下面的代码演示 program.cs 文件的当前内容。正如你所看到的,目前它不包括与路由器引擎相关的任何内容,但某些内容预计会在以后产生。或者至少,这是在 Visual Studio 自动生成的 app.cshtml 文件中找到的注释所建议的:
代码语言:javascript复制public static void Main(string[] args)
{
BrowserHttpMessageHandler.DefaultCredentials =
FetchCredentialsOption.Include;
CreateHostBuilder(args).Build().Run();
}
public static IWebAssemblyHostBuilder CreateHostBuilder(
string[] args) =>
BlazorWebAssemblyHost
.CreateDefaultBuilder()
.UseBlazorStartup<Startup>();
路由器类获取提供的程序集名称,并将其与所有引用的程序集一起搜索匹配当前请求 URL 的 Blazor 组件。请注意,路由器类行为的这一特定方面可能会在未来发展为一种模型,必须在该模型中明确指定路由器要考虑的程序集。这样就可以得到所需的终结点。
在内部,路由器生成路由表并按给定顺序对它们进行排序。候选路由列表产生自实现 IComponent 接口的已探索程序集中的类列表,更重要的是,使用 Route 属性进行修饰。收集的所有路由都存储在一个字典中并按从最具体到最不具体的顺序进行排序。
此评估算法基于 URL 中发现的段及其在字符串中的位置。例如,文本段比参数段更具体,因此具有更多路由约束的参数段被视为比存在更少约束的其他段更具体。此外,正如在 ASP.NET MVC 中发生的那样,解析 URL 时,表中的路由将从最具体到最不具体进行评估,并且搜索在首次匹配时停止。
在客户端上,路由器参与多种情况,最常见的情况是用户单击链接、表单上的提交按钮或下拉列表中触发服务器调用的项。路由器绑定到内部位置更改事件,并从客户端处理导航到新请求路径的整个过程。毋庸置疑,当应用程序的位置以编程方式更改时,路由器也会启动。最后一点也非常重要,路由器在浏览器历史记录中记录任何它负责的位置更改,因此后退和前进按钮可以按用户的期望工作。
路由器之战:Blazor 与Angular
很长一段时间,路由逻辑的实现都隐藏在 Web 服务器或服务器端框架(如 ASP.NET)的折叠中。路由器的实现是通过 SPA 框架(其中 Angular 最为出色)移动到客户端的。让我们花点时间对合并的 Angular 路由器和仍在使用的 Blazor 路由器中的功能进行简要比较。
最后的结果就是,Blazor 路由器目前仅提供作为客户端路由器的基本功能。例如,它不具备检查路由上的授权和创建在位置更改时执行视图转换的链接的功能。与 Angular 路由器不同,它在获取路由参数后无法异步运行解析步骤。最后,Blazor 路由器不支持条件重定向到备用路由 - 这也是 Angular 路由器可以做到的。
可以合理地预计,当 Blazor 作为版本 1.0 附带提供时,该增量的一部分将会减少。
路由模板
路由是将 URL 与已知 URL 模式列表绑定在一起的过程。在 Blazor 中,URL 模式或路由模板被收集在路由表中。该表通过查看使用 Route 属性修饰的 Blazor 应用程序的组件进行填充。每个组件的路径都将成为受支持的路由模板。
目前,开发人员只有一种方法可以控制可访问的组件的路由路径:@page 指令。例如,在 ASP.NET Core 中,开发人员可以通过以编程方式将路由添加到表中来显式定义路由,让系统使用默认路由约定或使用控制器方法上的属性来确定候选项。如果在 ASP.NET Core 应用程序中使用 Razor 页面,那么将获得与 Blazor 开发人员完全相同的体验 - @page 指令。
总之,每个 Blazor 组件都必须通过 @page 指令指定其路由模板才能访问。Blazor 组件由 .cshtml 文件组成,该文件被编译为实现 IComponent 接口的 C# 类。如果 Razor 源包含 @page 指令,则使用 Route 属性修饰相同的动态编译类。
值得注意的是,Blazor 在同一视图中支持多个路由指令。换而言之,以下代码得到了很好的支持:
代码语言:javascript复制@page “/”
@page “/home”
<h1>My Home Page</h1>
发现的所有路由都放在同一个路由表容器中,并根据上述规则进行排序。在上一示例中,两个路由指令都由文本组成,因此它们都进入最终容器的顶部区域,并按(相对)外观的顺序排序。
路由确实支持参数,并且在最终表中以比文本路由更低的优先级识别参数路由,因为它被视为不太具体。下面是参数路线的示例:
代码语言:javascript复制@page “/user/view/{Id}”
当 URL 包含后跟 /user/view/ 的服务器名称时,URL 模式匹配算法会触发此路由。URL 中跟踪 /user/view/ 的任何内容都与命名参数 {Id} 相关联。
如果熟悉 ASP.NET MVC(在很大程度上甚至是 Web 窗体),这种模型绑定模式应是老生常谈。在 ASP.NET 中,路由参数被分配给匹配的控制器方法的形参。在 Blazor 中,情况略有不同但具有可比性。
在 Blazor 中,路由器参数会自动分配给使用 [Parameter] 属性注释的组件的属性。根据参数和属性的名称进行匹配。下面是一个快速示例:
代码语言:javascript复制@page “/user/view/{Id}”
<h1>Hello @Id!</h1>
@functions {
[Parameter]
public string Id { get; set; }
protected override void OnInit()
{
// Some code here
}
}
目前,Blazor 不支持可选参数,因此如果示例 URL 中缺少 {Id},则整个 URL 不匹配。为了避免这种情况,目前最好的解决方法是使用两个 @page 指令,包含和不包含参数,如下面的代码所示:
代码语言:javascript复制@page “/user/view/{Id}”
@page “/user/view/”
<h1>Hello @Id!</h1>
@functions {
[Parameter]
public string Id { get; set; } = “0”;
protected override void OnInit()
{
// Some code here
}
}
同时,还建议为绑定页参数提供一个默认值,如果通过 URL 传递值,则该默认值将被覆盖。
类型匹配是参数路由和自动绑定到变量的常见问题。如果 URL 的段包含文本字符串,但绑定变量声明类型为 int,会发生什么情况?在正常情况下,如果没有任何预防措施,它可能会产生异常,因为文本值被填充到整数容器中。如果需要确保在应有参数的位置仅指定给定类型的值,则应选择路由约束。
如果熟悉任何风格的 ASP.NET MVC,那么路由约束并不是什么新鲜事。它包括向每个 URL 参数添加类型属性,如下所示:
代码语言:javascript复制@page “/user/view/{Id:int}”
参数的名称后跟冒号和表示 .NET 类型的文本。支持的文本与以下大多数 .NET 基元类型一对一匹配:int、bool、double、float、datetime、long 和 decimal。对于具有约束的路由,任何无法成功转换为指定类型的参数值都会使匹配失效,并且无法识别该路由。
更智能的链接和编程 URL 导航
在 Blazor 应用程序中,欢迎你使用定位标记来创建指向外部内容的链接。但是,当定位标记用于呈现菜单或导航栏时,可能需要一些额外的工作来调整 CSS 样式以反映链接的状态。
内置的 Blazor NavLink 组件可以用于任何需要定位点元素的地方,尤其是在菜单中。当前地址与链接匹配时,规范 HTML 定位点元素和 NavLink 组件之间的区别在于“活动”样式的自动分配。如果当前页面 URL 与引用的 URL 匹配,则“活动”CSS 类将自动添加到由 NavLink 组件呈现的定位标记中。“活动”CSS 类的实现仍然是页面开发人员的责任。该组件还包含用于控制匹配方式的属性。你可以执行严格匹配或前缀匹配。
此外还可以通过编程方式触发 Blazor 路由器。若要通过 Blazor 页面中的代码进行导航,应首先为 IUriHelper 抽象类型注入已配置的依赖项。@inject 指令就可执行这项工作,如下所示:
代码语言:javascript复制@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper Navigator
可以通过 NavigateTo 方法命令注入的对象。该方法采用 URL 作为参数:
代码语言:javascript复制Navigator.NavigateTo(“/user/view/1”);
该方法在概念上等同于在纯 JavaScript 中设置 DOM 位置对象的 href 属性。但是,在 Blazor 中,路由器可以在不离开客户端的情况下进行导航,无需从服务器完全重新加载内容。
缺少的功能
Blazor 框架是一个极具吸引力的软件,但很多功能仍然在开发中。有许多缺失的路由功能(例如将角色或用户身份附加到路由的功能),身份验证和授权仍然不完整。有关路由中与安全性相关的设备的任何考虑必须等到这些 API 最终确定。
路由谜题的另一个重要缺失部分:完全自定义决定目标 URL 的路由器逻辑的功能。此功能有助于开发人员控制无效链接请求。虽然 Blazor 路由器还远未完成,但仍在继续向成熟的传送框架发展。可以在 bit.ly/2TtY0DP 查看团队跟踪的 Blazor 路由系统的增强功能。
Dino Esposito 在他 25 年的职业生涯中撰写了超过 20 本的书籍和 1000 篇的文章。Esposito 不仅是舞台剧《事业中断》的作者,还是 BaxEnergy 的数字策略分析师,正忙于编写有助于建设环保世界的软件。可以在 Twitter 上关注他 (@despos)。
衷心感谢以下 Microsoft 技术专家对本文的审阅:Daniel Roth