路由
Blazor使用名为Router
的专用组件来实现路由请求,以Blazor web app auto项目为例,Router组件配置如下:
-
Routers.razor
<Router AppAssembly="typeof(Program).Assembly"> <Found Context="routeData"> <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" /> <FocusOnNavigate RouteData="routeData" Selector="h1" /> </Found> </Router>
路由原理
当在组件中使用 @page
指令指定路由地址后,在编译时会为生成的组件类设置一个[RouteAttribute]
特性来指定组件的路由模板。
应用启动时,Blazor会检查Router
组件的AppAssembly
、AdditionalAssemblies
等属性,以了解它应扫描哪些程序集。然后通过扫描这些程序集来寻找具有[RouteAttribute]
特性的类型(组件),并使用[RouteAttribute]
的对应属性值来编译出RouteData
对象,该对象指定如何将请求路由到组件。
在进行导航时,当存在匹配的路由,使用<Found>
组件来展示路由到的路由视图组件,即RouteView
组件。RouteView
实例会从Route
上接收到RouteData
对象、路由参数或URL中的任何参数,然后渲染指定的组件与布局。
在Router
组件中,还可使用 <NotFound>
组件指定在不存在匹配路由时返回给用户的内容。
路由的布局
可以通过在RouteView
组件中使用DefaultLayout
设置默认的布局组件。
默认情况下,Blazor web项目中指定MainLayout.razor
作为应用的默认布局,如果希望在某个组件中使用指定的布局组件,可以在组件中使用@layout
指令。(后面有专门讲解布局的章节)
一、路由模板的使用
路由模板的使用很简单,只需要在组件上直接通过@page
指令来进行模板的定义即可,组件支持使用多个 @page
指令的多个路由模板。
@page "/blazor-route"
@page "/different-blazor-route"
<PageTitle>Routing</PageTitle>
<h1>Routing Example</h1>
二、路由参数
1、路由参数
可以在路由模板中通过使用{}
符号设置路由参数,路由器会将路由参数填充到具有相同名字且使用[Parameter]
特性定义的组件属性上。
-
路由参数是不区分大小写的
-
示例
@page "/route-parameter/{text}" <p>Blazor is @Text!</p> @code { [Parameter] public string? Text { get; set; } }
2、URL参数
上面说了路由参数的使用,但实际上在项目开发过程中很少这么去用,很多情况下浏览器进行访问时,URL中的参数是使用?
来与访问路径进行区分,用&
来拼接多个参数,并且以键值对的形式传递的,例如:localhost:5249/parameterTest?Name=Schuyler&Age=23
。很显然,想要获取这里的参数,使用路由参数明显是不合适的。
针对这种情况,.net core组件库提供了[SupplyParameterFromQuery]
特性,用于将Url中的参数填充到对应的属性上。默认情况下是相同名字(不区分大小写)来进行参数和属性之间的匹配的。也可以在使用[SupplyParameterFromQuery]
特性时为属性设置指定的参数名称[SupplyParameterFromQuery(Name="ParamName")]
-
示例
@page "/parameterTest" @rendermode InteractiveAuto <h3>ParameterTest</h3> <p>Name: @Name</p> <p>Age: @Age</p> <p>Score: @Score</p> @code { [SupplyParameterFromQuery] public string? Name { get; set; } [SupplyParameterFromQuery] public int? Age { get; set; } [SupplyParameterFromQuery(Name = "sco")] public int Score { get; set; } }
三、路由约束
1、可选参数
如果希望路由参数可选,直接在参数名后面加一个?就可以了,例如@page "/route-parameter/{text?}"
2、类型约束
路由约束强制在路由组件时进行参数的类型匹配,如果路径上的参数类型不符合,则不匹配。
语法:{param:type}
-
可选参数同样可以使用约束。
-
可用的约束类型
约束 示例 匹配项示例 bool {active:bool} true,FALSE datetime {dob:datetime} 2016-12-31,2016-12-31 7:32pm decimal {price:decimal} 49.99,-1,000.01 double {weight:double} 1.234,-1,001.01e8 float {weight:float} 1.234,-1,001.01e8 guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638,{CD2C1638-1638-72D5-1638-DEADBEEF1638} int {id:int} 123456789,-123456789 long {ticks:long} 123456789,-123456789 -
示例
@page "/user/{id:int}/{option:bool?}" @rendermode InteractiveAuto <p> Id: @Id </p> <p> Option: @Option </p> @code { [Parameter] public int Id { get; set; } [Parameter] public bool Option { get; set; } }
3、catch-all参数
catch-all
参数其实就是通配符参数,将获URL上对应位置往后的所有的路径内容作为参数值。
-
语法:
{*paramName}
-
位于URL的末尾
-
必须有对应的使用了
[Parameter]
特性的组件属性,且为string
类型。 -
示例
@page "/cat-chall-test/{*pageRoute}" <p> PageRoute: @PageRoute </p> @code { [Parameter] public string? PageRoute { get; set; } }
四、跨程序集路由
静态路由器使用终结点路由和 HTTP 请求路径来确定要渲染的组件。 当路由器变为交互式路由器时,它将使用文档的 URL(浏览器地址栏中的 URL)来确定要渲染的组件。 这意味着,如果文档的 URL 动态更改为另一个有效的内部 URL,交互式路由器可以动态更改渲染的组件,并且该过程中无需执行 HTTP 请求来提取新页面的内容。
静态路由
如果组件没有禁用预渲染,组件进行静态服务端渲染期间所使用的路由称为静态路由(Router
组件,即Routes.razor
中的 <Router>
)。
如果在其他项目中的组件,需要进行静态渲染,静态渲染是在服务端进行的,因此必须将其他程序集中的要进行静态渲染的可路由组件披露给服务端,此时需要通过在Program
中,使用AddAdditionalAssemblies
方法来实现。
例如Blazor web app Auto项目下,打开服务端项目的Program
类,可以看到如下语句:
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(BlazorAuto.Client._Imports).Assembly);
其中AddAdditionalAssemblies(typeof(BlazorAuto.Client._Imports).Assembly
就表示将客户端的程序集的可路由组件包含到服务端中,让服务端在进行静态路由时,可以路由到对应的可路由组件。
交互式路由
交互式组件在进行静态路由后,路由器将在服务器上的路由转变为交互式,此后所使用的路由称为“交互式路由”。
交互式路由的内部导航不涉及从服务器请求新页面内容。 因此,内部页面请求不会发生预渲染。
如果在服务器项目中定义了 Routes
组件,则 Router
组件的AdditionalAssemblies
属性应包括 .Client
项目的程序集。 这样,路由器就可以在以交互方式渲染时正常工作。
- 在以下示例中,
Routes
组件位于服务器项目中,BlazorSample.Client
项目的_Imports.razor
文件指示要搜索可路由组件的程序集:
<Router
AppAssembly="..."
AdditionalAssemblies="new[] { typeof(BlazorSample.Client._Imports).Assembly }">
...
</Router>
如果可路由组件仅存在于 .Client
项目中(全局路由模式设置的WebAssembly或Auto项目),Routes
组件则在 .Client
项目(而不是服务器项目)中进行定义。 在这种情况下,没有具有可路由组件的外部程序集,因此不需要为AdditionalAssemblies
指定值。
导航
一、NavigationManager
C# 代码中使用NavigationManager
类型来管理 URI 和导航。 如果在组件中使用,可以通过@inject NavigationManager Navigation
注入。
1、常用属性
Uri
:获取当前绝对 URI。
BaseUri
:获取可在相对 URI 路径之前添加用于生成绝对 URI 的基 URI(带有尾部反斜杠)。
- 一般情况下,
BaseUri
对应于文档的<base>
元素(App.razor中,<head>
内容的位置)上的href
属性。
2、常用事件
LocationChanged
:增强型导航位置更改时触发的事件。
-
注意这里的导航位置更改指的是发生增强型导航时,例如调用
NavigateTo
方法进行导航(且增强型导航可用时)、以及浏览器上前进后退(且增强型导航可用时)、选择内部链接时(例如标签、表单指向内部链接)等等。 -
此外,在使用中发现这个事件是针对整个应用而言的
-
事件触发时会传入两个参数,一个是触发者对象,另一个是
LocationChangedEventArgs
对象,其中LocationChangedEventArgs
对象提供了如下信息。Location
:新位置的 URL。IsNavigationIntercepted
:如果为true
,则表示 Blazor 拦截了浏览器中的导航。 如果为false
,则表示NavigationManager.NavigateTo
成功导航。
-
示例
@page "/navigate" @rendermode InteractiveAuto @implements IDisposable @inject ILogger<Navigate> Logger @inject NavigationManager Navigation <PageTitle>Navigate</PageTitle> <h1>Navigate Example</h1> <button class="btn btn-primary" @onclick="NavigateToCounterComponent"> Navigate to the Counter component </button> @code { private void NavigateToCounterComponent() { Navigation.NavigateTo("counter"); } protected override void OnInitialized() { Navigation.LocationChanged += HandleLocationChanged; } private void HandleLocationChanged(object? sender, LocationChangedEventArgs e) { Logger.LogInformation("URL of new location: {Location}", e.Location); } public void Dispose() { Navigation.LocationChanged -= HandleLocationChanged; } }
3、常用方法
导航
NavigateTo(string uri,bool forceLoad=false,bool replace=false)
:导航到指定URI。
uri
:目标组件的路径,前缀可以带/
,也可以不带。forceLoad
:如果为true
,则绕过客户端路由并强制浏览器从服务器加载新页面,无论增强型导航是否可用。为false
时,如果当前 URL 中可使用增强型导航,则使用增强型导航,否则对请求的 URL 执行整页重载。replace
:如果为true
,则替换浏览器历史记录中的当前 URI。如果为false
,则将新的URI追加到历史堆栈中。
NavigateTo(string uri, NavigationOptions options)
:导航到指定URI,用法跟上面的方法差不多的,不过是将参数封装到options
对象中。
-
options
:选项参数,可以设置ForceLoad
、ReplaceHistoryEntry
、HistoryEntryState
三个属性,使用这个方法主要是可以设置HistoryEntryState
,不然直接用上面那个方法就可以了。 -
示例
@page "/navigate" @rendermode InteractiveAuto @inject NavigationManager Navigation <h1>Navigate Example</h1> <button class="btn btn-primary" @onclick="NavigateToCounterComponent"> Navigate to the Counter component </button> @code { private void NavigateToCounterComponent() { Navigation.NavigateTo("counter"); } }
刷新
Refresh(bool forceReload = false)
:刷新当前页面。如果增强型导航可用,则执行增强型导航,否则执行整页重载。
forceReload
:如果为true
,则绕过客户端路由并强制浏览器从服务器加载新页面,无论增强型导航是否可用。- 如果执行的是增强型导航,则会保留当前页面的数据,否则,会全部重置。
导航URI处理
ToAbsoluteUri(string relativeUri)
:将相对 URI 转换为绝对 URI。
ToBaseRelativePath(string uri)
:根据应用的基URI,将绝对URI转换为相对URI。
- 例如,基URI为
https://localhost:8000
,绝对URI为https://localhost:8000/segment1/segment2
,则相对于基URI的相对URI为segment1/segment2
- 如果基URI不匹配会引发异常。
string GetUriWithQueryParameter(string name, string? value)
:给当前URI添加指定参数,生成新的URI并返回。
- 这个方法有很多个重载,用于传入不同类型的参数值。
- 对于URI中已经存在的参数可以进行替换,不存在的参数则添加,如果将参数的值设为
null
则表示删除URI中的参数。 - 其参数值支持的类型与约束所支持的类型是相同的。
string GetUriWithQueryParameter([string uri,] IReadOnlyDictionary<string, object?> parameters)
:给当前URI或指定的URI添加指定参数,生成新的URI并返回。
- 用法跟
GetUriWithQueryParameter
差不多,不过可以指定URI,且传入的参数名和值,是以字典对象的形式。
注册导航处理程序
IDisposable RegisterLocationChangingHandler(Func<LocationChangingContext, ValueTask> locationChangingHandler)
:注册一个处理程序来处理传入的内部导航事件。发生增强型导航时会调用处理程序。
-
LocationChangingContext
:导航上下文对象,其中提供了如下属性和方法。TargetLocation
:获取目标位置,调用NavigateTo
时传入了什么就是什么。HistoryEntryState
:获取与目标历史记录条目关联的状态。IsNavigationIntercepted
:获取是否从链接截获了导航。CancellationToken
:获取CancellationToken
以确定导航是否已取消,例如,确定用户是否触发了不同的导航。PreventNavigation()
:调用以阻止导航继续。
-
IDisposable
:返回对象,用于释放处理程序,取消注册,以允许组件进行垃圾回收。 -
示例
@page "/nav-handler" @rendermode InteractiveAuto @implements IDisposable @inject NavigationManager Navigation <p> <button @onclick="@(() => Navigation.NavigateTo("/"))"> Home (Allowed) </button> <button @onclick="@(() => Navigation.NavigateTo("/counter"))"> Counter (Prevented) </button> </p> @code { private IDisposable? registration; protected override void OnAfterRender(bool firstRender) { if (firstRender) { registration = Navigation.RegisterLocationChangingHandler(OnLocationChanging); } } private ValueTask OnLocationChanging(LocationChangingContext context) { if (context.TargetLocation == "/counter") { context.PreventNavigation(); } return ValueTask.CompletedTask; } public void Dispose() => registration?.Dispose(); }
除了上述例子中进行导航的拦截取消外,还可以通过NavigationLock
组件来实现,具体可以内置组件章节的内容。
二、锚点导航
锚点导航可以导航到指定组件上具有指定Id
属性的元素上,与<a>
标签的用法类似。
- 锚点路径语法:
"/routeName#id"
- 注意锚点路径使用
#
符号将组件路由路径与元素Id拼接一起,前缀的/
可有可无,但是routeName必须要写上,即使只是锚点到当前组件上的某个元素,也必须写上,否则会导航到基URI的描点上,例如"#id"
会导航到http://localhost:5242/#title
- 锚点在当前组件上时,不会触发增强型导航也不会刷新页面,只是在本页面上跳转。锚点在其他组件上,默认则触发增强型导航。
目前可以使用<a>
标签、NavLink
组件和NavigateTo
方法进行锚点导航:
-
<a href="/counter#targetElement">
-
<NavLink href="/counter#targetElement">
-
Navigation.NavigateTo("/counter#targetElement")
-
示例
@page "/test" <h1 id="title">Counter</h1> <a href="/counter#title">锚点</a>