从之前的.NET5到现在的.NET7,Blazor还是获得了长足的进步,需要重新审视下了。
- ASP.NET Core Blazor
- 开头(略)
- Components
- 组件是编译成.NET装配件的C#标类
- 可以作为https://learn.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-7.0或者https://learn.microsoft.com/en-us/nuget/what-is-nuget分发
- 组件标类使用Razor标记语言编写,文件扩展名为
.razor
,所以也叫Razor组件- MVC也使用Razor页面,但是方式不同于Blazor
- Blazor Server
- 组件侯宿在服务端,通过https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-7.0控制客户端
- 文中对此机制进行了描述
- Blazor WebAssembly
- 用于SPA应用
- 不需要服务端支持,客户端承担更多功能,易于于JS互操作
- Blazor Hybrid
- 服务端在本地,而不是通过远程链接
- 在.NET MAUI、WPF以及WinForms中支持
- JavaScript interop
- 可与第三方JS料库交互
- Code sharing and .NET Standard
- Blazor实现了https://learn.microsoft.com/en-us/dotnet/standard/net-standard,同标准下的库可以混用,例如Blazor, .NET Framework, .NET Core, Xamarin, Mono, and Unity
- 不适合在浏览器中使用的API会抛出PlatformNotSupportedException.
- Blazor实现了https://learn.microsoft.com/en-us/dotnet/standard/net-standard,同标准下的库可以混用,例如Blazor, .NET Framework, .NET Core, Xamarin, Mono, and Unity
- ASP.NET Core Blazor supported platforms
- 主流的都支持,hybrid的在最新的Edge WebView2、Android的Chrome以及iOS和macOS的Safari上测过
- Tooling for ASP.NET Core Blazor
- 讲述怎么使用VS、VS Code以及donet命令行来搭建Blazor工程
- ASP.NET Core Blazor tutorials
- 包含一些起步用的教程,不展开了
- Learn modules包含更深入一些的内容
- Use ASP.NET Core SignalR with Blazor
- ASP.NET Core Blazor hosting models
- 又复述了一遍Blazor Server、Blazor WebAssembly、Blazor Hybrid三种侯宿模式
- Which Blazor hosting model should I choose?
- 以表格形式列举了各种侯宿模式的优缺点
- ASP.NET Core Blazor Hybrid
- Overview
- 开头
- 采用本地WebView,服务端也在本地,采用本地互操作通道
- Blazor Hybrid apps with .NET MAUI
- Blazor Hybrid apps with WPF and Windows Forms
- 开头
- ASP.NET Core Blazor Hybrid tutorials
- 针对MAUI、WinForms和WPF的起步教程
- ASP.NET Core Blazor Hybrid routing and navigation
- 开头
- 默认的URI请求的路由行为:
- 区分内部链接和外部链接
- 内部链接在BlazorWebView中打开
- 外部链接在系统指定的浏览器中打开
- 如果内部链接请求了一个文件,但是不在静态内容中
- WPF和WinForms,返回的是侯宿页面的内容
- MAUI,返回404
- UrlLoading可以用于改变
target="blank"
以外的链接打开形式,可改成OpenExternally
、OpenInWebView
以及CancelLoad
。
- 默认的URI请求的路由行为:
- Namespace
- 需要用到
Microsoft.AspNetCore.Components.WebView;
- 需要用到
- Internal navigation(略)
- External navigation(略,内容有所重复)
- 开头
- ASP.NET Core Blazor Hybrid static files
- .NET MAUI
- 本生的物资https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/single-project#raw-assets使用MauiAsset构建动作,将物资放入
Resources/Raw
,例如Resources/Raw/Data.txt
。 - 通过
Microsoft.Maui.Storage.FileSystem.OpenAppPackageFileAsync
可以获取到该物资中资源的数据流。参考https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/storage/file-system-helpers
- 本生的物资https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/single-project#raw-assets使用MauiAsset构建动作,将物资放入
- WPF(略)
- Windows Forms(略)
- Static assets limited to Razor components
- 静态物资放在
wwwroot
目录即可,然后VS中设置Copy to Output Directory to Copy if newer
- 可以通过
Microsoft.Maui.Storage.FileSystem.OpenAppPackageFileAsync
读取 - 或者直接在img元素的src指定,如:
<img alt="1991 Jeep YJ" src="/jeep-yj.png" />
- 可以通过
- 静态物资放在
- .NET MAUI
- Use browser developer tools with ASP.NET Core Blazor Hybrid
- Browser developer tools with .NET MAUI Blazor
- 通过配置
AddBlazorWebViewDeveloperTools
来支持开发工具 - 在BlazorWebView中使用
Ctrl+Shift+I
开启开发工具
- 通过配置
- Browser developer tools with .NET MAUI Blazor
- Reuse Razor components in ASP.NET Core Blazor Hybrid
- 开头部分
- 不同侯宿平台具有不同的能力
- WASM支持同步JS互操作,Server和Hybrid只支持异步JS互操作
- Server中的组件能够访问只存在于服务端的服务,比如Entity Framework数据库上下文
- BlazorWebView 中的组件可以直接访问原生桌面于移动设备的特性
- 不同侯宿平台具有不同的能力
- Design principles
- 下列是设计能跨平台工作的组件的一些原则
- 将UI代码放置在Razor标类料库(RCL)
- 特定功能的实现不放在RCL,RCL应只存放抽象层(接口和基类)
- 只在特定侯宿平台加入平台特定功能
- 通常,为组件中的HTML使用CSS样式,并且在CSS样式中表达不同平台的差异
- 如果UI某部需要针对特定平台有额外的处理逻辑,则在RCL中使用https://learn.microsoft.com/en-us/aspnet/core/blazor/components/dynamiccomponent?view=aspnetcore-7.0封装之。也可以使用RenderFragment实例。
- 下列是设计能跨平台工作的组件的一些原则
- Project code organization
- 开头部分
- Share assets across web and native clients using a Razor class library (RCL)
- 文首
- 如何在Server和MAUI应用中共享静态物资
- Sample app
- Share web UI Razor components, code, and static assets
- 重复复述了一些其他文章中的内容
index.html
通常不共享- 根部的Razor组件,比如
App.razor
或Main.razor
倒是可以共享,不过通常这些含有跟侯宿方式相关的代码
- Provide code and services independent of hosting model
- 侯宿方式相关的代码,应抽象成接口,后通过服务的方式注入
- 文中举了weather forecast的例子
- Security and Identity
- (暂略)
- 文首
- Overview
- ASP.NET Core Blazor project structure
- Blazor Server
- 相关的模板blazorserver, blazorserver-empty(没有bootstrap CSS和示例代码)
- Project structure
- Data目录
- Pages目录,盛放
.razor
文件 Properties/launchSettings.json
,开发环境相关的设置- Shared目录
- MainLayout.razor,应用的布局管理
- MainLayout.razor.css
- NavMenu ,侧边栏导航
- NavMenu.razor.css
- SurveyPrompt.razor,survey组件
- wwwroot,Web根目录
- _Imports.razor,公共Razor指示
- App.razor,应用根组件,路由在此设置
- appsettings.json,提供https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/configuration?view=aspnetcore-7.0
- Program.cs,应用的入口点
- Blazor WebAssembly (略)
- Location of
<head>
content- Server应用在
Pages/_Host.cshtml
,WASM应用中在wwwroot/index.html
- Server应用在
- Dual Blazor Server/Blazor WebAssembly app
- 推荐使用RCL来共享代码和逻辑
- Blazor Server
- Fundamentals
- Routing and navigation
- Route to components from multiple assemblies
- 在Router组件上使用AdditionalAssemblies,示例
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(Component1).Assembly }"> @* ... Router component elements ... *@ </Router>
- 在Router组件上使用AdditionalAssemblies,示例
- Route to components from multiple assemblies
- Configuration
- 本文只适用于Blazor WebAssembly
- Dependency injection
- DI 是这样的技术,组件依赖由框架来注入。于是乎,组件只要声明自己的依赖,具体注入的时机由框架给出
-
Default services
- HttpClient(Scoped)
- IJSRuntime(BW:Singleton,BS:Scoped)
- NavigationManager(BW:Singleton,BS:Scoped)
- 此外还有配置和录记等服务可以按需注入
-
Add services to a Blazor WebAssembly app
- 具体的代码在Program.cs中
- 注入的服务由Blazor的builder创建,创建之后可以调用方法来初始化,blazor提供配置信息(例如
host.Configuration["WeatherServiceUrl"]
)可以用于初始化。
- 注入的服务由Blazor的builder创建,创建之后可以调用方法来初始化,blazor提供配置信息(例如
- 具体的代码在Program.cs中
-
Add services to a Blazor Server app
- Microsoft.AspNetCore.Builder.WebApplicationBuilder的IServieCollection用于管理服务描述器,添加服务描述器:
builder.Services.AddSingleton<IDataAccess, DataAccess>();
- Microsoft.AspNetCore.Builder.WebApplicationBuilder的IServieCollection用于管理服务描述器,添加服务描述器:
-
Register common services in a hosted Blazor WebAssembly solution
- 可以在客户端提供可以用于客户端和服务端的服务注册功能
-
Service lifetime
- Scoped,可清除的
- Singleton,单现例的
- Transient,一次性的
-
Request a service in a component
- 使用
@inject
Razor指示来注入服务,需要两个参数- Type:服务的类型
- Property:用于接收服务的辖属,不需要手动声明,会由编译器创建
- 如果是在组件对应的标类中,使用
[Inject]
属性添加 - 不要将待注入的服务设置成nullable,而是设置成:
private IExampleService ExampleService { get; set; } = default!;
- 文中列出了许多参考
- 使用
-
Use DI in services
- 如果一个服务需要注入另一个服务,需要在构造算函中指明所需服务
- 注意事项具体见文中
- 必须有一个参数其参量可以由DI满足,其他参数若无法满足,但有默认值也可以
- 可用的构造算函必须是公开的
- 如果存在多个可用的构造算函,则
-
Utility base component classes to manage a DI scope (略)
-
Use of an Entity Framework Core (EF Core) DbContext from DI(略)
-
Detect transient disposables in Blazor WebAssembly apps(略)
-
Detect transient disposables in Blazor Server apps(略)
- Logging
- Configuration
- 默认只在Development情况下,录记LogLevel.Inofrmation级别信息
- 工程文件配置
<ImplicitUsings>enable</ImplicitUsings>
的话,可以不使用using
指明录记相关的标类
- Log levels(过)
- Razor component logging
ILogger<Counter>)
或LoggerFactory.CreateLogger<Counter>()
- Logging in Blazor Server apps(略)
- Logging in Blazor WebAssembly apps(略)
- (剩余略)
- Configuration
- Routing and navigation
- Components
- JavaScript intertop
- Overview
- Interaction with the Document Object Model (DOM)
- 如果项件归Blazor管理,就不要用JS去变更DOM。否则会让Blazor和DOM的状态不匹配,出现未定义的行为
- Asynchronous JavaScript calls
- JS互操作默认是异步的,这是为了保证在不同侯宿模型中能有一致表现
- 只有才WASM中,同步的JS互操作才是可能的
- JS互操作默认是异步的,这是为了保证在不同侯宿模型中能有一致表现
- Object serialization
- 项件序列化使用的是System.Text.Json,有以下规则
- 类型必须有默认建构器,get/set访问器必须公开,域属不会被序列化
- 全局的默认序列化无法定制
- .NET成员名会被转成小写的JSON项件名
- JSON逆序列化成JsonElement实例
- JsonConverter用编口可用于自定义序列化。辖属序列化可以用
[Jsonconverter]
来自定义。 - 文中引用了其他.NET文档
- .NET 7.0计划支持System.DateOnly和System.TimeOnly
- Blazor支持字节阵列,而不用将其转为Base64
- 项件序列化使用的是System.Text.Json,有以下规则
- JavaScript initializers
- 可以在Blazor应用加载之前和之后执行JS初始化
- JS初始化用到文件
{NAME}.lib.module.js
,此文件应放在wwwroot
目录下{NAME}
为装配件名
- 此模块应到处下列算函
beforeStart(options, extensions)
afterStarted
- 文中举了一个小例子
BlazorSample.lib.module.js
RazorClassLibrary1.lib.module.js
- MVC和Razor Pages应用不会自动加载JS初始化,需要代码来触发。
- 提到一些资源
- Location of JavaScript
- 有几种办法
- 在
<head>
加载,通常不推荐 - 在
<body>
架子 - 从与组件并存的外部js加载
- 从外部js加载
- 在Blazor启动时插入一个脚本
- 在
- 不要在Razor组件中放置
<script>
,因为无法被Blazor动态更新 - Load a script in
<head>
markup(过) - Load a script in
<body>
markup(过) - Load a script from an external JavaScript file (.js) collocated with a component(过)
- Load a script from an external JavaScript file (.js)(过)
- Inject a script after Blazor starts(过)
- 有几种办法
- JavaScript isolation in JavaScript modules(略)
- Cached JavaScript files
- JS文件在开发环境下不设置缓存
- 如果要清楚浏览器缓存
- 在开发者工具中设置(文中有各大浏览器开发工具的链接)
- 手动刷新页面
- Interaction with the Document Object Model (DOM)
- Call JS from .NET
- 文首
- 注入IJSRuntime 服务,即可调用JS
- IJSRuntime.InvokeAsync
- JSRuntimeExtensions.InvokeAsync
- JSRuntimeExtensions.InvokeVoidAsync
- 前面几个算函
- JS作用域是全局,其他作用域需要显式指明,例如
someScope.someFunction
- 参数是JSON序列化后的
Object[]
CancellationToken
通知取消操作TimeSpan
知名操作时限- 返回值
TValue
必须是可JSON序列化的 - InvokeAsync处理返回JS Promise
- JS作用域是全局,其他作用域需要显式指明,例如
- prerendering的时候无法调用JS
- 举了一个JS TextDecoder的例子
- 注入IJSRuntime 服务,即可调用JS
- JavaScript API restricted to user gestures
- (仅限于Server)
- 一些用户手势相关的操作(比如Fullscreen API)必须同步操作
- 也就是用onclick,而不是@onclick
- Invoke JavaScript functions without reading a returned value (InvokeVoidAsync)
- 没有或者不需要返回值的话,可以用InvokeVoidAsync
- Component (.razor) example (InvokeVoidAsync)
- (过)
- Class (.cs) example (InvokeVoidAsync)
- (过)
- Invoke JavaScript functions and read a returned value (InvokeAsync)
- 若需要返回值,则使用InvokeAsync
- Component (.razor) example (InvokeAsync)
- (过)
- Class (.cs) example (InvokeAsync)
- (过)
- Dynamic content generation scenarios
- 使用 BuildRenderTree生成的动态内容,使用
[Inject]
属性
- 使用 BuildRenderTree生成的动态内容,使用
- Prerendering
- (过)
- Synchronous JS interop in Blazor WebAssembly apps
- (过)
- Location of JavaScript
- (略)
- JavaScript isolation in JavaScript modules
- (停)
- 文首
- Call .NET from JS (过)
- JSImport/JSExport interop
- 解释如何在WASM中使用.NET 7的
[JSImport]
以及[JSExport]
交互用编口。 - (剩余过)
- 解释如何在WASM中使用.NET 7的
- Overview
- State management
- Server和WASM有各自适用的情形
- (略)
- Performance
-
Optimize rendering speed
-
Avoid unnecessary rendering of component subtrees
- 事件发生时,下面的流程决定哪些组件需要重绘
- 事件被派发到事件处理器所在之组件,执行完事件处理后会重绘此组件
- 重绘的组件会提供一份新的参数拷贝给它的子组件
- 每个子组件根据接收到的参数量值决定是否要重绘,默认情况下,参数变了之后就会重绘
- 下面的手段可以避免递归重绘子组件树体
- 确保子组件参数是初始非变更类型,例如string, int, bool, DateTime
- 覆写ShouldRender
- 用于处理非初始类型,比如自定义模型类型、事件回调、或RenderFragment量值
- 对于UI-only的组件,首次呈现后不变,则忽略参数改动
- 举了一个例子,如何在OnParametersSet决定是否要shouldRender
- 事件处理器也会将shouldRender设置为true
- 事件发生时,下面的流程决定哪些组件需要重绘
-
Virtualization
- (略)
-
Create lightweight, optimized components
- (略)
-
Avoid thousands of component instances
-
Don’t receive too many parameters
-
Ensure cascading parameters are fixed
-
Avoid attribute splatting with CaptureUnmatchedValues
-
Implement SetParametersAsync manually
-
Don’t trigger events too rapidly
-
Avoid rerendering after handling events without state changes
-
-
- Test components
- Test approaches
- E2E以及Unit testing
- bUnit
- Playwright for .NET
- E2E以及Unit testing
- Choose the most appropriate test approach
- Component without JS interop logic
- Unit testing
- Component with simple JS interop logic
- Unit testing
- Component that depends on complex JS code
- Unit testing and separate JS testing
- Component with logic that depends on JS manipulation of the browser DOM
- E2E testing
- Component that depends on 3rd party class library with hard-to-mock dependencies
- E2E testing
- Component without JS interop logic
- Test components with bUnit
- bUnit可以跟MSTest, NUnit以及xUnit一起协同工作
- Additional resources
- Test approaches
Components
- Overview
- Component classes
- Razor syntax https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-7.0
- Razor语法中的directive和directive attributes在Blazor中常用
- Directives: 作用于组件,例如
@page
给组件指定一个URL - Directive attributes: 作用于组件元素,例如input元素上的
@bind
- Directives: 作用于组件,例如
- Razor语法中的directive和directive attributes在Blazor中常用
- Names
- 文件名必须大写字母开头
- 其他规则
- 组件采用驼峰命名,例如
ProductDetail
- 路由采用火车命名,例如
/product-detail
- 组件采用驼峰命名,例如
- Routing
- 通过
@page
指令或者RouteAttribute
- 通过
- Markup
- 一个.razor文件被编译成一个C#标类
- c#代码可以在一到多个
@code
中指定 - C#属域可以用@方式输出,比如
<h1 style="font-style:@headingFontStyle">@headingText</h1>
- 组件在Blazor的内部表现形式是一棵渲染树体,整合了DOM和CSSDOM。此树体的改动会被反映到Web页面。
- 组件可以放置到工程的任意位置,常见的位置是:页面放Pages,非页面放Shared
- Razor措辞中的控制结构采用小写形式,比如
@if
,@code
,@code
。辖属为大写,比如@Body
(LayoutComponentBase.Body)。
- Asynchronous methods (async) don’t support returning void
- 异步方法不要返回void,要返回Task
- Nested components(过)
- Namespaces
- 默认的名字空间来自工程名和目录名的结合,可以用
@namespace
改写 @using
可以用于打开命名空间_Imports.razor
中的指令对于所有.razor
文件是公共的- 以下不受支持:
global::
修饰符- 别名:
@using Foo = Bar
- 部分:
@using BlazorSample
,然后使用Shared.NavMenu
- 默认的名字空间来自工程名和目录名的结合,可以用
- Partial class support
- 组件编译生成的是C# partial class,所以用一个标类代码可以放在不同的文件中,比如:
Pages/CounterPartialClass.razor
Pages/CounterPartialClass.razor.cs
_Imports.razor
中的指示对cs文件不适用
- 组件独有的css,可以在组件名+
.css
结尾的文件中指定
- 组件编译生成的是C# partial class,所以用一个标类代码可以放在不同的文件中,比如:
- Specify a base class
@inherits
用于指定组件的基础标类,默认是ComponentBase
- Razor syntax https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-7.0
- Component parameters
- 组件参数通过公开的C#辖属来指定,并需要标有
[Parameter]
属性 - 传参方式示例:
<ParameterChild Title="Set by Parent" Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" /
- 一些方针
- The @ prefix is required for string parameters. Otherwise, the framework assumes that a string literal is set.
- Outside of string parameters, we recommend use the use of the @ prefix for nonliterals, even when they aren’t strictly required.
- Quotes around parameter attribute values are optional in most cases per the HTML5 specification.
- Blazor无法在Razor表达式中执行异步动作,不支持
<ParameterChild Title="@await ..." />
- 异步动作可以在https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-7.0#component-initialization-oninitializedasync中执行
- 有些语法在RazorPage(.cshtml)中支持,但是在Razor Component中不支持
-
<ParameterChild Title="Set by @(panelData.Title)" />
-
- 组件参数应是
auto-properties
,也就是没有自定义的set、get逻辑 - 组件初始化之后,不要修改参数的值
- 如果有需求对设置的参数进行转换,可以覆盖OnParametersSetAsync
- 如果一个参数是必须的,则通过指定
[EditorRequired]
来提示 - 参数支持Tuple类型,比如
Tuple<int, string, bool>? Data { get; set; }
- 目前只支持C# 7.0或以后的unnamed tuples
- 组件参数通过公开的C#辖属来指定,并需要标有
- Route parameters
- 路由参数直接指定在
@page
指示中,比如@page "/route-parameter/{text?}"
- 可以通过参数来接收
[Parameter] public string? Text { get; set; }
- catch-all路由参数可以通过
{*pageRoute}
指定,参考https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-7.0#catch-all-route-parameters
- 路由参数直接指定在
- Child content render fragments
- 组件可以通过RenderFragment来内嵌其他组件的内容,例如:
[Parameter] public RenderFragment? ChildContent { get; set; }
- 在循环中渲染子组件的时候,需要设一个本地变量做索引
- 使用
Enumerable.Range(0,3)
则没有问题 - 组件内容参数一般用
ChildContent
标出
- Render fragments for reusable rendering logic
- 就算不嵌套,也可以直接提取公共的RenderFragment
- Overwritten parameters
- 参数从父到子瀑布式传递
- 参数不会被以外改写
- 最小化副作用
- 不要再子组件修改输入参数
- 举了一个Expander例子
- 总之,如果需要修改参数,则声明一个私有的,并在OnInitialized事件中初始化之,否则会有问题,因为这跟组件的更新逻辑有关。
- 参数从父到子瀑布式传递
- Attribute splatting and arbitrary parameters
- 形如
<input id="useAttributesDict" @attributes="InputAttributes"/>
- 为了捕获这样的属性,需要指定:
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? InputAttributes { get; set; }
- 此情形下,也可以使用
IEnumerable<KeyValuePair<string, object>>
或IReadOnlyDictionary<string, object>
- 此情形下,也可以使用
- 需要注意的是,Attribute splatting的顺序是从右到左,所以会被右边的覆盖
- 形如
- Capture references to components
- 如果要添加到组件的引用
- 添加
@ref
属性到子组件 - 定义一个和子组件相同类型的属域
- 例如
<ReferenceChild @ref="childComponent" />
- 可以调用子组件的方法,但是注意要在
OnAfterRender
中判断在firstRender
的时候,设置好调用子组件的动作
- 添加
- 如果要添加到组件的引用
- Synchronization context
- SynchronizationContext用于保证单逻辑线程执行,组件的生命周期方法和事件回调在此context执行
- 这是为了和WASM的运行模式对齐
- Avoid thread-blocking calls
- 不要使用下列API
- Result
- Wait
- WaitAny
- WaitAll
- Sleep
- GetResult
- 不要使用下列API
- Invoke component methods externally to update state
- 通过InvokeAsync可以将任务分派到SynchronizationContext
- 文中举了一个例子(略)
- Use @key to control the preservation of elements and components
- (暂略)
- Apply an attribute
- 例如
@attribute [Authorize]
- 例如
- Conditional HTML element attributes
- Component classes
- Cascading values and parameters
- 让数据从上面节点流到下面的节点,是跨级传达,不需要逐级传达,可以允许组件跨层级
-
CascadingValue component
- CascadingValue可以给下居节点树设置一个共享的量值
-
[CascadingParameter] attribute
- 组件通过
[CascadingParameter]
来按照类型捕获共享的量值 - 和组件参数相同,递进参数改变时也会重新呈现组件
CascadingValue<TValue>.IsFixed
可以用于说明某递进参数是静态的
- 组件通过
-
Cascade multiple values
- 多个递进参数,如果类型相同,则必须指定不同的名字
- 接收方必须指定
[CascadingParameter(Name = "CascadeParam1")]
-
Pass data across a component hierarchy
- 举了一个Tabs的例子
- 简单地说,把一个例现作为递进参数,其方法可以供众下级调用。
- Data binding
- 文首
- 数据绑定通过
@bind
Razor指示提供,目标既可以是域属,也可以是辖属- 只有UI更新的时候,这个更新的值才会呈现
- 但是组件会在事件处理器之后触发呈现
- 可以指定绑定到DOM事件,形如
@bind:event="{EVENT}"
- 形如
<input @bind="InputValue" @bind:event="oninput" />
- 上述在事件oninput更新InputValue
- 形如
- 执行异步逻辑的话,使用
@bind:after="{EVENT}"
- 形如
<input @bind="searchText" @bind:after="PerformSearch" />
- 意为在绑定获得更新时执行PerformSearch
- 形如
- 支持双向绑定的组件可以使用(.NET7支持的,貌似有bug)
@bind:get
,指定用于绑定的量值@bind:set
,当绑定发生变化的回调- 例子:
<input @bind:get="Value" @bind:set="ValueChanged" />
- 数据绑定通过
- Multiple option selection with
<select>
elements- 支持多个选项的元素,之上的绑定可以获取一个阵列,也就是ChangeEventArgs 的Value辖属返回一个阵列
- Binding
<select>
element options to C# object null values- 一些
<select>
提供不了null值的解释
- 一些
- Unparsable values
- 如果遇到无法识别的量值,会转回前值
- (剩余略)
- Format strings(过)
- Custom binding formats
- C#辖属的get、set访问器可以用来自定义绑定的格式
- (剩余略)
- Binding with component parameters
- 绑定到子组件的参数,是一个常见的场景,也叫链锁绑定
- 措辞形如
@bind-{PROPERTY}
- 在子组件中,无法使用连锁绑定来更新上级组件的值,因为需要量值、和更新量值的事件处理要分开
- 此事件处理器的命名约定为辖属名加上后缀
Changed
,类型为EventCallback,可用调用其InvokeAsync来更i性能值 - 组件参数绑定可以有
@bind:after
、@bind-{PROPERTY}:event
- 事实上,下面两种写法等同
<ChildBind @bind-Year="year" />
<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />
- 单向绑定大概是指从子组件到上级组件
- Bind across more than two components
- 可以上下任意嵌套,但是注意
- 新参数值从上向下流动
- 变更通知从下向上流动
- 一个常见的实践是让上级保持最新值
- 可以上下任意嵌套,但是注意
- Additional resources
- 文首
- Event handling
- 文首
- 描述事件处理的措辞:
@on{DOM EVENT}="{DELEGATE}"
- 对于事件处理委托
- 异步的话支持返回Task
- 自动触发UI重绘,无须呼叫StateHasChanged
- 异常会被录记
- 描述事件处理的措辞:
- Event arguments
- Built-in event arguments
- 直接在事件委托的参数处指定
- 支持的事件类型https://learn.microsoft.com/en-us/dotnet/api/system.eventargs在文中有列表
- Custom event arguments
- General configuration
- 在JS中,定义一个算函来构建自定义事件参量
- 在
wwwroot/index.html
或Pages/_Host.cshtml
中注册之 - 为事件参量定义一个C#标类
- 通过EventHandlerAttribute来注记自定义的事件
- 确保此EventHandler标类整体可见
- 在HTML元素上使用自定义事件
- 触发自定义事件的时候,bubbles必须设置为true
- Custom clipboard paste event example
- 一个示例
- General configuration
- Built-in event arguments
- Lambda expressions
- 可以用Lambda作为事件处理器
- (其他略)
- EventCallback
- 用EventCallback来跨组件展露EventCallback,父组件可以设置一个回调方法到子组件的EventCallback,示例如下:
[Parameter] public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
- 父组件中处理此事件的回调无须调用StateHasChanged,因为会自动触发此调用
- EventCallback以及
EventCallback<TValue>
支持异步委托- 示例:
await OnClickCallback.InvokeAsync(arg);
- 示例:
- 用EventCallback来跨组件展露EventCallback,父组件可以设置一个回调方法到子组件的EventCallback,示例如下:
- Prevent default actions
- 使用形式
@on{DOM EVENT}:preventDefault
(等同于@on{DOM EVENT}:preventDefault="true"
)- 值也可以从属域来:
<input @onkeydown:preventDefault="shouldPreventDefault" />
- 值也可以从属域来:
- 示例:
<input value="@count" @onkeydown="KeyHandler" @onkeydown:preventDefault />
- 使用形式
- Stop event propagation
- 使用形式
@on{DOM EVENT}:stopPropagation
- 此指示的范围限定在Blazor的作用域,不扩展到HTML DOM。
- 为了阻止HTML DOM事件扩散,可以:
- 获取事件路径:
Event.composedPath()
- 根据事件标的过滤事件
- 获取事件路径:
- 使用形式
- Focus an element
- 在元素引用上调用FocusAsync方法
- 文首
- CCS Isolation
- 将CSS样式隔绝在单独的页面、视图中,以解决会避免
- 维护全局样式
- 嵌套内容的样式冲突
-
Enable CSS isolation
- 为.razor文件创建一个对应的.razor.css文件
- 确定是不支持在Razor代码块中导入CSS
- 为.razor文件创建一个对应的.razor.css文件
-
CSS isolation bundling
- 隔绝是在建构时完成的
- 会生成类似
{ASSEMBLY NAME}.styles.css
的css文件 - 目标元素上会添加
b-{STRING}
属性,比如<h1 b-3xxtam6d07>
- 对应的CSS标类会对应于上述属性
h1[b-3xxtam6d07]
- 所有生成的CSS会合成到
obj/{CONFIGURATION}/{TARGET FRAMEWORK}/scopedcss/projectbundle/{ASSEMBLY NAME}.bundle.scp.css
- 会生成类似
- 隔绝是在建构时完成的
-
Child component support
- 用
::deep
CSS选择器可以将样式应用到子组件- 例如
::deep h1
- 例如
- 建构时,只会改写选择器最右边的元素,例如
div > a
成div > a[b-{STRING}]
- 但是指定
div ::deep > a
可以改写成div[b-{STRING}] > a
- 用
-
CSS preprocessor support
- 对于sass/less这种css预处理器需要额外的配置
- 有些NuGet料包,比如Delegate.SassBuilder可以帮助达成此目的
- 对于sass/less这种css预处理器需要额外的配置
-
CSS isolation configuration
-
Customize scope identifier format
- 可以自定义scope,比如在csproj的ItemGroup添加以下内容
<None Update="Pages/Example.razor.css" CssScope="custom-scope-identifier" />
- 多个
*.razor.css
可以共享同一个custom-scope-identifier
- 可以自定义scope,比如在csproj的ItemGroup添加以下内容
-
Change base path for static web assets
- csproj中PropertyGroup设置
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
- csproj中PropertyGroup设置
-
Disable automatic bundling
- 让第三方工具来处理发布和加载
<DisableScopedCssBundling>true</DisableScopedCssBundling>
- 让第三方工具来处理发布和加载
-
Disable CSS isolation
<ScopedCssEnabled>false</ScopedCssEnabled>
-
-
Razor class library (RCL) support
- 需要导入RCL中的域受限CSS:
@import '_content/ClassLib/ClassLib.bundle.scp.css';
- 需要导入RCL中的域受限CSS:
-
Additional resources
- 将CSS样式隔绝在单独的页面、视图中,以解决会避免
- Lifecycle
- 文首
- Razor组件给出了一系列和生命周期相关的事件,籍此可以执行一些操作。
-
Lifecycle events
- 组件命循事件
- 如果组件是第一次呈现给一个请求
- 创建组件现例
- 注入辖属,执行SetParametersAsync
- 调用OnInitialized{Async},如果返回的Task没有完成了,会等待之
- 调用OnParametersSet{Async},如果返回的Task没有完成了,会等待之。完成后呈现组件
- 对于所有的同步工作以及完成了Task都会进行呈现
- 注意点
- 反正就是有异步操作的话先呈现,等异步操作结束后再呈现
- SetParameterAsync/OnInitialized{Async}这些只在初次呈现的时候才进行
- 如果组件是第一次呈现给一个请求
- 会先呈现父属组件,然后呈现子属组件,因为哪些子属组件需要呈现决定于父属组件的呈现
- 如果父属组件的初始化是同步的,那么会确保优先完成;起步初始化的情况下,完成顺序无法确定
- DOM事件处理
- 跑事件处理器
- 如果返回未完成的Task,会等待此Task完成后再呈现组件
- 对于所有的同步工作以及完成了Task都会进行呈现
- Render命循
- 初次呈现后,如果
ShouldRender()
是彳值,则避免呈现 - 建构呈现树的差异,呈现组件
- 等待DOM的更新
- 调用OnAfterRender{Async}.
- 初次呈现后,如果
- 可以用StateHasChanged显式触发呈现
- 代码给出更详细信息:https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs
- 组件命循事件
-
When parameters are set (SetParametersAsync)
- SetParametersAsync将组件参数从父属传递到子属,组件参数包含在其ParameterView参数中
- 驳改这个方法,可以与ParameterView中的组件参数交互
- 默认的实现会设置
[Parameter]
和[CascadingParameter]
属性的参数 - 如果不调用base.SetParametersAsync的话,可以任意处置传入的组件参数
- (举了一个 ParameterView.TryGetValue的例子)
- SetParametersAsync将组件参数从父属传递到子属,组件参数包含在其ParameterView参数中
-
Component initialization (OnInitialized{Async})
- OnInitialized{Async}在SetParametersAsync之后
- 如果在Server端预呈现内容的话,会调用OnInitializedAsync两次
- 当组件随页面首先静态呈现的时候,在服务端执行一次
- 当浏览器呈现组件的时候,在浏览器执行一次
- 预呈现的时候,无法调用进JS
-
After parameters are set (OnParametersSet{Async})
- OnParametersSet{Async}在以下情况下被调用:
- 组件被OnInitialized{Async}初始化之后
- 当父属组件呈现并提供
- 某个改动过的,已知或者朴然的,非变更类型
- 复合类型的情况,因为框架无法得知其是否被更新,于是就当作其被更新了
- (举了一个设置时间的例子)
- OnParametersSet{Async}在以下情况下被调用:
-
After component render (OnAfterRender{Async})
- 在组件呈现完毕后调用,带一个firstRender参数,说明是否首次呈现
- 适合在需要根据呈现内容来进行一些初始化的场景,比如JS操作
- OnAfterRenderAsync如果返回未完成的任务,框架也不会等任务完成时再呈现
- 这是为了避免陷入死循环
- OnAfterRender和OnAfterRenderAsync在预呈现阶段不会被调用
- 在组件呈现完毕后调用,带一个firstRender参数,说明是否首次呈现
-
State changes (StateHasChanged)
- StateHasChanged告知框架某个组件需要再呈现
- 会针对EventCallck方法自动执行
-
Handle incomplete async actions at render
- 显示占位符
-
Handle errors
- (有专门的章节)
-
Stateful reconnection after prerendering
- Server应用其RenderMode为ServerPrerendered,此模式下组件会被作为页面的一部分,预呈现为静态内容
- 在浏览器建立SignalR连接时,会重新呈现组件,使其可交互
- OnInitialized{Async}会被调用两次,一种对冲的办法是在两次呈现时缓存状态
- (举了使用Microsoft.Extensions.Caching.Memory的例子)
- Server应用其RenderMode为ServerPrerendered,此模式下组件会被作为页面的一部分,预呈现为静态内容
-
Prerendering with JavaScript interop(过)
-
Component disposal with IDisposable and IAsyncDisposable
- 实现上述接口的组件,框架会在组件移出UI的时候执行之
- Dispoal可以在任意时刻执行,甚至在初始化的时候
- 如果既有同步版本又有异步版本,只会执行异步版本
-
Disposal of JavaScript interop object references(过)
-
Document Object Model (DOM) cleanup tasks during component disposal
- 使用MutationObserver模式
-
Synchronous IDisposable
-
Asynchronous IAsyncDisposable
-
Assignment of null to disposed objects
- 总的来说避免赋值null,但是有些情形避免不了
-
StateHasChanged
- dispose的时候不适用
-
Event handlers
- 取消订阅.NET事件的处理
-
Anonymous functions, methods, and expressions
- when the object exposing the event outlives the lifetime of the component registering the delegate
- 虽说匿名函数不太需要取消订阅,但是还有一些情况需要注意的
- 实现上述接口的组件,框架会在组件移出UI的时候执行之
-
Cancelable background work
- 举了一些需要中止任务执行的情形
- 要中止组件的后台任务
- 使用CancellationTokenSource以及CancellationToken
- 在disposal的时候,调用CancellationTokenSource.Cancel
- 异步调用返回时,调用ThrowIfCancellationRequested
- (举了一个例子)
-
Blazor Server reconnection events
- 断开SignalR连接后,只有UI更新会被打断。
- 文首
- Rendering
-
Rendering conventions for ComponentBase
- 继承自ComponentBase的Razor组件,包含下面的再呈现逻辑:
- 来自父组件参数集合的更新过后
- 来自叠进参数的更新过后
- 调用事件处理器响应事件之后
- 自己调用StateHasChanged之后
- 下列情况下会跳过再呈现
- 所有的参数为已知类型或者元初类型,且这些参数没有被更新
- 组件的ShouldRender方法返回false
-
Control the rendering flow
-
Suppress UI refreshing (ShouldRender)
- 返回亍值可以强制刷新(不影响初始呈现)
-
When to call StateHasChanged
- 下面情况无需嗲用StateHasChanged
- 对事件的处理,不管是同步的还是异步的,因为ComponentBase已经为大多数事件处理触发了再呈现
- 典型的命周逻辑,比如OnInitalized或者OnParametersSetAsync, 因为ComponentBase已经为此触发了再呈现
- 需要显示调用StateHasChanged的场合介绍如下
-
An asynchronous handler involves multiple asynchronous phases
-
Receiving a call from something external to the Blazor rendering and event handling system
- 比如定时器超时的情况,需要手动指定StateHasChanged
-
To render a component outside the subtree that’s rerendered by a particular event
- 举了一些例子
- 下面情况无需嗲用StateHasChanged
- 继承自ComponentBase的Razor组件,包含下面的再呈现逻辑:
-
- Class libraries
- 文首
- RCL可以用以下方式引用
- Sln中的另一个项目
- .NET料库
- NuGet料包
- RCL可以用以下方式引用
- Create an RCL
- 选择
Support pages and views
需要自行添加_Imports.razor
等文件。 - 对于SupportedPlatform ,参考https://learn.microsoft.com/en-us/aspnet/core/blazor/components/class-libraries?view=aspnetcore-7.0&tabs=visual-studio#browser-compatibility-analyzer-for-blazor-webassembly
- 选择
- Consume a Razor component from an RCL
- 可以使用全名,比如
<ComponentLibrary.Component1 />
来引用RCL中的组件,或者使用@using
提前声明组件的命名空间 - 对于CSS isolation有特别的注意项(此处略过)
- 注意RCL的wwwroot内容的位置,比如:
<link href="_content/ComponentLibrary/additionalStyles.css" rel="stylesheet" />
- 可以使用全名,比如
- Create an RCL with static assets in the wwwroot folder
- 前面已经示例过了,RCL的wwwroot会在侯宿主中按照
_content/{PACKAGE ID}/{PATH AND FILE NAME}
引用
- 前面已经示例过了,RCL的wwwroot会在侯宿主中按照
- Create an RCL with JavaScript files collocated with components
- JS文件可以并存之,并在发布时自动一道wwwroot目录下
- 默认并存的文件命名;
- MVC应用中以
.cshtml.js
方式 - Razor组件以
.razor.js
方式
- MVC应用中以
- 引用的话,按照
{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js
的方式 - (剩余略)
- Supply components and static assets to multiple hosted Blazor apps
- Browser compatibility analyzer for Blazor WebAssembly
- 跟WebAssembly相关(略)
- JavaScript isolation in JavaScript modules
- Build, pack, and ship to NuGet
- 用
dotnet pack
打包,用dotnet nuget push
上传
- 用
- 文首
- Bulit-in Components
- Blazor自带的组件看这里。
其他文章
Prevent refreshing the UI after an event in Blazor
- 默认情况下,Blazor会在事件处理器之后调用StateHasChanged,如果事件处理器是异步的,那么可能会调用俩次
- 此行为出现在ComponentBase标类中source
- 解决方案是通过实现IHandleEvent提供一个回调,那么Blazor就会使用自定义的回调,相关实现在sourc
- 例子
private record SimpleCallback(Action Callback) : IHandleEvent { public static Action Create(Action callback) => new SimpleCallback(callback).Invoke; public static Func<Task> Create(Func<Task> callback) => new SimpleAsyncCallback(callback).Invoke; public void Invoke() => Callback(); public Task HandleEventAsync(EventCallbackWorkItem item, object arg) => item.InvokeAsync(arg); } private record SimpleAsyncCallback(Func<Task> Callback) : IHandleEvent { public Task Invoke() => Callback(); public Task HandleEventAsync(EventCallbackWorkItem item, object arg) => item.InvokeAsync(arg); }
- 例子
其他参考
资源
(本篇完)