最近在学习 Blazor ,在B站上找了一个国外的课程边看边学习。嗯,原价¥1503的课程,大概200多美元,课程链接如下:
B站(大章节分P-适合初学):.NET 8 Blazor 从入门到精通
B站(小章节分P-适合复习):Blazor从入门到精通(中文字幕)
官网课程:Blazor From Start to Finish
Blazor 的关键概念
本文主要介绍Blazor 的关键概念,每个知识点都附上了学习过程中查到的参考资料。文中删除了一些常识性或表述不清的内容,如热重载、组件与页面等。
项目模板
项目开发的常用模板配置项如下,其它配置也可以都试一下,观察一下区别:
Auto 交互方式:最初使用 Blazor Server,并在随后访问时使用 WebAssembly 自动进行交互式客户端呈现,详细内容参考.NET8 Blazor的Auto渲染模式的初体验。
Razor 语法
参考ASP.NET Core 的 Razor 语法参考,前期主要理解下面几个重点语法即可:
- 隐式 Razor 表达式:以 @ 开头,后跟 C# 代码
<p>@DateTime.Now</p><p>@DateTime.IsLeapYear(2016)</p>
- 显式 Razor 表达式:由 @ 符号和圆括号组成
<p>Last week this time: @(DateTime.Now - TimeSpan.FromDays(7))</p>
- @code 块:允许 Razor 组件将 C# 成员(字段、属性和方法)添加到组件
@code { // C# members (fields, properties, and methods)}
- 循环语句和条件语句:如@for、@if等,直接写在页面中
@for (var i = 0; i < people.Length; i++){ var person = people[i]; <p>Name: @person.Name</p> <p>Age: @person.Age</p>}@if (value % 2 == 0){ <p>The value was even.</p>}
依赖注入
参考将依赖项注入 Blazor 组件在Program.cs(项目引导程序)中注册依赖项:
builder.Services.AddSingleton<DemoDependency>();//用于注册依赖项的其他模式...
对于 Blazor 组件,有两种方法可以指示我们的组件使用哪些依赖项:
//1.在 Razor 标记中@inject IToDoApi ToDoApi@inject ISomeServiceType AnotherService//2.在 C# 代码中@code{ [Inject] private IYetAnotherServiceType PropertyInjectedDependency { get; set; }}
注入配置
参考ASP.NET Core Blazor 配置,其中配置的优先级别:用户机密 > appsettings.{Environment}.json > appsettings.json。
在 appsettings.json 中配置连接字符串:
{"Logging": {"LogLevel": {"Default":"Information","Microsoft.AspNetCore":"Warning" } },"AllowedHosts":"*","ConnectionStrings": {"Default":"连接字符串来自appsettings.json" }}
在组件中引入配置依赖:
@page"/"@inject IConfiguration config<PageTitle>Home</PageTitle><h1>Hello, world!</h1><h2>@config.GetConnectionString("Default")</h2>
IConfiguration 是默认注册的,不需要另外写代码注册,可以直接使用。
HeadOutlet 组件
切换页面时不是整个页面被重新加载,实际上只有根组件App.razor的<Routes />被重新渲染。这种渲染方式不利于SEO,可以使用HeadOutlet 组件来控制 <head> 元素的内容来进行优化。
<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link rel="stylesheet" href="bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="KeyConcepts.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /></head><body> <Routes /> <script src="_framework/blazor.web.js"></script></body></html>
参考在 ASP.NET Core Blazor 应用中控制 <head> 内容,指定一个页面的标题和描述:
@page"/control-head-content"<PageTitle>@title</PageTitle><p>Title: @title</p><p>Description: @description</p><HeadContent> <meta name="description" content="@description"></HeadContent>@code { private string description ="This description is set by the component."; private string title ="Control <head> Content";}
- 使用PageTitle 组件指定页面标题,这样可以将 HTML <title> 元素呈现给 HeadOutlet 组件。
- 使用HeadContent 组件指定 <head> 元素内容,该组件为 HeadOutlet 组件提供内容。
需要注意,如果在A页面用了B页面,那么B页面的 PageTitle 会覆盖掉A页面的 PageTitle。所以,组件不需要作为页面使用时就不要放 PageTitle了。
@code 分离
Blazor可以支持在razor文件里面添加cs代码,但是代码一旦复杂了之后就会变得特别的麻烦。其实,这部分代码在编译时实际是被分离出来的,我们也可以在编译前手动将它们分离出来。
右键code,选择快速操作和重构,然后如下图所示选择将块提取到代码隐藏中:
结果如下,其中①只是②的一个快捷方式:
上面是使用VS的自动分离功能,也可以使用手动的方式进行分离。参考C# Blazor 学习笔记(4):blazor代码分离,注意以下几点:
- 直接右键razor组件的上级目录,添加一个partial局部类
- 新建类的类名是xxx.razor.cs,这样才能挂到组件上面
xxx.razorxxx.razor.cs:代码xxx.razor.css:css样式
代码分离后,依赖项也需要改成属性注入:
using Microsoft.AspNetCore.Components;namespace KeyConcepts.Client.Pages;public partial class Demo{ // 在razor组件中是这样的 @inject IConfiguration config [Inject] protected IConfiguration config { get; set; }=default!; private string? GetConnectionString() { return config.GetConnectionString("Default"); }}
Blazor 调试
调试没什么好说的,就在VS中正常打断点、单步运行、监控变量值就行了,具体参考调试 ASP.NET Core 应用。
CSS 隔离
CSS 隔离可以将 CSS 范围限定到 Razor 组件,以简化 CSS 并避免与其他组件或库发生冲突,但过多的使用也会导致 CSS 追踪困难。
参考ASP.NET Core Blazor CSS 隔离,在与组件相同文件夹中创建一个.razor.css文件,该文件与组件的.razor文件的名称相匹配。例如为Counter.razor组件创建一个Counter.razor.css文件:
h1 { color:red;}
生成时 Blazor 会重写 CSS 选择器以匹配组件呈现的标记, 重写的 CSS 样式被作为静态资产捆绑和生成, 默认情况下在 <head> 标记中引用表样式:
<!-- {ASSEMBLY NAME} 占位符是项目的程序集名称 !--><link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet">
在捆绑的文件中,每个组件都与范围标识符关联。 对于每个具有样式的组件,HTML 属性追加有格式 b-{STRING},其中 {STRING} 占位符是框架生成的十个字符的字符串。 标识符对每个应用都是唯一的。
在呈现的 Counter 组件中,Blazor 将范围标识符追加到 h1 元素:
<h1 b-zdeg3nv67a="">Counter</h1>
注:如果CSS不生效,需要清理一下浏览器的缓存。
调用JavaScript
js文件可以放到wwwroot目录下,也可以关联到特定组件,参考
从与组件并置的外部 JavaScript 文件 (.js) 加载脚本 为 Counter 组件添加并置js文件:
//Counter.razor.jsexport function displayCount(count) { alert('The count is' + count);}export function createMessage(count) { return 'The count is' + count;}
Blazor 应用的 Razor 组件使用 .razor.js 扩展名并置 JS 文件(参考 CSS 隔离部分),并且可通过项目中文件的路径公开寻址 {PATH}/{COMPONENT}.razor.js:
- 占位符 {PATH} 是指向组件的路径
- 占位符 {COMPONENT} 是组件
修改 Counter 组件的代码,调用js函数:
@page"/counter"@rendermode InteractiveAuto@inject IJSRuntime JSRuntime<PageTitle>Counter</PageTitle><h1>Counter</h1><h2>@subMessage</h2><p role="status">Current count: @currentCount</p><button @onclick="IncrementCount">Click me</button>@code { private int currentCount = 0; private string subMessage =""; private IJSObjectReference? jsModule; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import","./Pages/Counter.razor.js"); } } private async Task IncrementCount() { currentCount++; await jsModule.InvokeVoidAsync("displayCount", currentCount); subMessage = await jsModule.InvokeAsync<string>("createMessage", currentCount); }}
- @inject IJSRuntime JSRuntime:注入 IJSRuntime 接口,用于与客户端 JavaScript 交互
- IJSObjectReference? jsModule:保存对 JavaScript 模块的引用
- JSRuntime.InvokeAsync
<IJSObjectReference>:加载 JavaScript 模块并保存其引用
实际项目中,尽量不要使用js控制DOM,而是使用Blazor组件,因为两者可能起冲突。