什么是Blazor
Blazor
是一個使用.NET生成交互式客戶端WebUI的框架:
- 使用C#代替JavaScript來創建信息豐富的交互式UI。
- 共享使用.NET編寫的服務器端和客戶端應用邏輯。
- 將UI呈現為HTML和CSS,以支持眾多瀏覽器,其中包括移動瀏覽器。
- 與新式托管平台(如Docker)集成。
基於.Net Core提供客戶端Web開發的優勢
使用.NET進行客戶端Web開發可提供以下優勢:
- 使用C#代替JavaScript來編寫代碼。
- 利用現有的.NET庫生態系統。
- 在服務器和客戶端之間共享應用邏輯。
- 受益於.NET的性能、可靠性和安全性。
- 在Windows、Linux和macOS上使用VisualStudio保持高效工作。
- 以一組穩定、功能豐富且易用的通用語言、框架和工具為基礎來進行生成。
Blazor組件
Blazor
應用基於組件。Blazor
中的組件是指UI元素,例如頁面、對話框或數據輸入窗體。
組件是內置到.NET
程序集的.NET
C#
類,它們用於:
- 定義靈活的UI呈現邏輯。
- 處理用戶事件。
- 可以嵌套和重用。
- 可作為Razor類庫或NuGet包共享和分發。
組件類通常以Razor
標記頁(文件擴展名為.razor
)的形式編寫。Blazor
中的組件有時被稱為Razor
組件。Razor
是一種語法,用於將HTML標記與專為提高開發人員工作效率而設計的C#代碼結合在一起。借助Razor
,可使用Visual Studio
中的IntelliSense
編程支持在同一文件中的HTML標記與C#之間切換。Razor Pages
和MVC也使用Razor
。與基於請求/響應模型生成的Razor Pages
和MVC
不同,組件專門用於處理客戶端UI邏輯和構成。
Blazor使用UI構成的自然HTML標記。下面的Razor標記演示了一個組件(Dialog.razor
),它顯示一個對話框,並處理在用戶選擇按鈕時發生的事件:
<div class="card" style="width:22rem">
<div class="card-body">
<h3 class="card-title">@Title</h3>
<p class="card-text">@ChildContent</p>
<button @onclick="OnYes">Yes!</button>
</div>
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public string Title { get; set; }
private void OnYes()
{
Console.WriteLine("Write to the console in C#! 'Yes' button selected.");
}
}
在上述示例中,OnYes
是由按鈕的onclick
事件觸發的C#方法。對話框的文本(ChildContent
)和標題(Title
)由在其UI中使用此組件的下述組件提供。
使用HTML標記將Dialog
組件嵌入到另一個組件中。在以下示例中,Index組件(Pages/Index.razor
)使用前面的Dialog
組件。標記的Title
屬性向Dialog
組件的Title
屬性傳遞標題的值。Dialog
組件的文本(ChildContent
)由<Dialog>
元素的內容設置。向Index
組件添加Dialog
組件后,Visual Studio
中的IntelliSense
可加快使用語法和參數補全進行開發的速度。
@page "/"
<h1>Hello, world!</h1>
<p>
Welcome to your new app.
</p>
<Dialog Title="Learn More">
Do you want to <i>learn more</i> about Blazor?
</Dialog>
在瀏覽器中訪問父級Index
組件時,將呈現該對話框。當用戶選擇此按鈕時,瀏覽器的開發人員工具控制台會顯示由OnYes
方法編寫的消息:
組件呈現為瀏覽器文檔對象模型(DOM)
的內存中表現形式,它被稱為“呈現樹”,用於以靈活高效的方式更新UI。
文檔對象模型 (DOM) 是HTML和XML文檔的編程接口。它提供了對文檔的結構化的表述,並定義了一種方式可以使從程序中對該結構進行訪問,從而改變文檔的結構,樣式和內容。DOM 將文檔解析為一個由節點和對象(包含屬性和方法的對象)組成的結構集合。簡言之,它會將web頁面和腳本或程序語言連接起來。
一個web頁面是一個文檔。這個文檔可以在瀏覽器窗口或作為HTML源碼顯示出來。但上述兩個情況中都是同一份文檔。文檔對象模型(DOM)提供了對同一份文檔的另一種表現,存儲和操作的方式。 DOM是web頁面的完全的面向對象表述,它能夠使用如 JavaScript等腳本語言進行修改。
Blazor Web Assembly
Blazor Web Assembly
是單頁應用(SPA)框架
,用於使用.NET
生成交互式客戶端Web應用。Blazor Web Assembly
使用無插件或將代碼重新編譯為其他語言的開放式Web標准。Blazor Web Assembly
適用於所有新式Web瀏覽器,包括移動瀏覽器。
通過WebAssembly
(縮寫為wasm
),可在Web瀏覽器內運行.NET
代碼。Web Assembly
是針對快速下載和最大執行速度優化的壓縮字節碼格式。Web Assembly
是開放的Web標准,支持用於無插件的Web瀏覽器。
Web Assembly
代碼可通過JavaScript
(稱為JavaScript
互操作性,通常簡稱為JavaScript
互操作或JS互操作)訪問瀏覽器的完整功能。通過瀏覽器中的Web Assembly
執行的.NET
代碼在瀏覽器的JavaScript
沙盒中運行,沙盒提供的保護可防御客戶端計算機上的惡意操作。
當Blazor Web Assembly
應用生成並在瀏覽器中運行時:
- C#代碼文件和Razor文件將被編譯為.NET程序集。
- 該程序集和.NET運行時將被下載到瀏覽器。
- BlazorWebAssembly啟動.NET運行時,並配置運行時,以為應用加載程序集。BlazorWebAssembly運行時使用JavaScript互操作來處理DOM操作和瀏覽器API調用。
已發布應用的大小(其有效負載大小)是應用可用性的關鍵性能因素。大型應用需要相對較長的時間才能下載到瀏覽器,這會損害用戶體驗。Blazor Web Assembly
優化有效負載大小,以縮短下載時間:
- 在中間語言(IL)裁邊器發布應用時,會從應用刪除未使用的代碼。
- 壓縮HTTP響應。
- .NET運行時和程序集緩存在瀏覽器中。
Blazor托管模型
主要的Blazor
托管模型在Web Assembly
上的瀏覽器中運行客戶端。將Blazor
應用、其依賴項以及.NET
運行時下載到瀏覽器。應用將在瀏覽器線程中直接執行。UI更新和事件處理在同一進程中進行。應用資產作為靜態文件部署到可為客戶端提供靜態內容的Web服務器或服務中。
如果創建了Blazor Web Assembly
應用進行部署,但沒有后端ASP.NET Core
應用來為其文件提供服務,那么該應用被稱為獨立Blazor Web Assembly
應用。如果創建了應用進行部署,但沒有后端應用來為其文件提供服務,那么該應用被稱為托管的Blazor Web Assembly
應用。托管的Blazor Web Assembly Client
應用通常使用WebAPI
調用或SignalR
(結合使用ASP.NET Core SignalR
和Blazor
)通過網絡與后端Server應用交互。
blazor.webassembly.js
腳本由框架和句柄提供:
- 下載.NET運行時、應用和應用依賴項。
- 初始化運行應用的運行時。
Blazor Web Assembly
托管模型具有以下優點:
- 沒有.NET服務器端依賴項。應用下載到客戶端后即可正常運行。
- 可充分利用客戶端資源和功能。
- 工作可從服務器轉移到客戶端。
- 無需ASP.NETCoreWeb服務器即可托管應用。無服務器部署方案可行,例如通過內容分發網絡(CDN)為應用提供服務的方案。
Blazor Web Assembly
托管模型具有以下局限性:
- 應用僅可使用瀏覽器功能。
- 需要可用的客戶端硬件和軟件(例如WebAssembly支持)。
- 下載項大小較大,應用加載耗時較長。
- .NET運行時和工具支持不夠完善。例如,.NETStandard支持和調試方面存在限制。
在傳統Web應用和單頁應用(SPA)之間選擇
Atwood 定律:任何能夠用JavaScript編寫的應用程序,最終必將用JavaScript編寫。- Jeff Atwood
目前可通過兩種通用方法來構建Web應用程序:在服務器上執行大部分應用程序邏輯的傳統Web應用程序,以及在Web瀏覽器中執行大部分用戶界面邏輯的單頁應用程序(SPA)
,后者主要使用WebAPI
與Web服務器通信。也可以將兩種方法混合使用,最簡單的方法是在更大型的傳統Web應用程序中托管一個或多個豐富SPA類子應用程序。
何時使用傳統Web應用程序:
- 應用程序的客戶端要求簡單,甚至要求只讀。
- 應用程序需在不支持
JavaScript
的瀏覽器中工作。 - 團隊不熟悉
JavaScript
或TypeScript
開發技術。
何時使用SPA:
- 應用程序必須公開具有許多功能的豐富用戶界面。
- 團隊熟悉
JavaScript
、TypeScript
或Blazor Web Assembly
開發。 - 應用程序已為其他(內部或公共)客戶端公開API。
此外,SPA框架還需要更強的體系結構和安全專業知識。相較於傳統Web應用程序,SPA框架需要進行頻繁的更新和使用新框架,因此改動更大。相較於傳統Web應用,SPA應用程序在配置自動化生成和部署過程以及利用部署選項(如容器)方面的難度更大。
使用SPA方法改進用戶體驗時必須權衡這些注意事項。
傳統Web應用程序、SPA或Blazor應用之間進行選擇時要考慮的一些基本因素
因素 | 傳統 Web 應用 | 單頁面應用程序 | Blazor 應用 |
---|---|---|---|
需要團隊熟悉 JavaScript/TypeScript | 最低 | 必需 | 最低 |
支持不帶腳本的瀏覽器 | 支持 | 不支持 | 支持 |
客戶端應用程序行為極少 | 適合 | 不必要 | 可行 |
豐富而復雜的用戶界面要求 | 受限 | 適合 | 適合 |
何時選擇傳統Web應用
應用程序的客戶端要求簡單,可能要求只讀
對許多Web應用程序而言,其大部分用戶的主要使用方式是只讀。只讀(或以讀取為主)應用程序往往比那些維護和操作大量狀態的應用程序簡單得多。例如,搜索引擎可能由一個帶有文本框的入口點和用於顯示搜索結果的第二頁組成。匿名用戶可以輕松提出請求,並且很少需要使用客戶端邏輯。同樣,一般而言,博客或內容管理系統中面向公眾的應用程序主要包含的內容與客戶端行為關系不大。此類應用程序容易構建為基於服務器的傳統Web應用程序,在Web服務器上執行邏輯,並呈現要在瀏覽器中顯示的HTML。事實上,網站的每個獨特頁面都有自己的URL,搜索引擎可以將其存為書簽和編入索引(默認設置,無需將此功能添加為應用程序的單獨功能),這也是此類情況的一個明顯優勢。
應用程序需在不支持JavaScript的瀏覽器中工作
如需在有限或不支持JavaScript
的瀏覽器中工作的Web應用程序,則應使用傳統的Web應用工作流編寫(或至少可以回退到此類行為)。SPA需要客戶端JavaScript
才能正常工作;如果沒有客戶端JavaScrip
t,SPA不是好的選擇。
團隊不熟悉JavaScript或TypeScript開發技術
如果團隊不熟悉JavaScript
或TypeScript
,但熟悉服務器端Web應用程序開發,那相較於SPA,他們交付傳統Web應用的速度可能更快。除非以學習SPA編程為目的,或需要SPA提供用戶體驗,否則對已經熟悉構建傳統Web應用的團隊而言,選擇傳統Web應用的工作效率更高。
何時選擇SPA
應用程序必須公開具有許多功能的豐富用戶界面
SPA可支持豐富客戶端功能,當用戶執行操作或在應用的各區域間導航時無需重新加載頁面。SPA很少需要重新加載整個頁面,因此加載速度更快,可在后台提取數據,並且對單個用戶操作的響應更快。SPA支持增量更新,可保存尚未完成的窗體或文檔,而無需用戶單擊按鈕提交窗體。SPA支持豐富的客戶端行為,例如拖放,比傳統應用程序更容易操作。可以將SPA設計為在斷開連接的模式下運行,對客戶端模型進行更新,並在重新建立連接后將更新最終同步回服務器。如果應用要求包括豐富的功能,且超出了典型HTML窗體提供的功能,則選擇SPA樣式應用程序。
通常,SPA需要實現內置於傳統Web應用中的功能,例如在反映當前操作的地址欄中顯示有意義的URL(並允許用戶將此URL存為書簽或對其進行深層鏈接以便返回此URL)。SPA還應允許用戶使用瀏覽器的后退和前進按鈕尋找用戶意料之中的結果。
團隊熟悉JavaScript和/或TypeScript開發
編寫SPA需要熟悉JavaScript
或TypeScript
以及客戶端編程技術和庫。團隊應有能力像使用Angular
一樣使用SPA框架編寫新式JavaScript
。
參考-SPA框架
- Angular:https://angular.io
- React:https://reactjs.org/
- Vue.js:https://vuejs.org/
應用程序已為其他(內部或公共)客戶端公開API
如果已提供一個Web API
供其他客戶端使用,則相較於在服務器端窗體中復制邏輯,創建一個利用這些API
的SPA
實現更加容易。用戶與應用程序交互時,SPA
廣泛使用Web API
來查詢和更新數據。
何時選擇Blazor
應用程序必須公開豐富用戶界面
與基於JavaScript
的SPA一樣,Blazor
應用程序可以支持豐富的客戶端行為,而無需重載頁面。這些應用程序對用戶的響應更快,僅獲取響應給定用戶交互所需的數據(或HTML)。如果設計得當,可以將服務器端Blazor
應用配置為以客戶端Blazor
應用的形式運行,只需在此功能受支持后對它進行稍加更改即可。
與使用JavaScript或TypeScript開發相比,團隊更喜歡使用.NET開發
與使用JavaScript
或TypeScript
等客戶端語言相比,許多使用.NET和Razor
的開發人員的工作效率更高。由於已經使用.NET開發了應用程序的服務器端,因此,使用Blazor
可以確保團隊中的每名.NET開發人員都可以理解,並可能會生成應用程序前端的行為。
Blazor Server
Blazor
將組件呈現邏輯從UI更新的應用方式中分離出來。Blazor Server
在ASP.NET Core
應用中支持在服務器上托管Razor
組件。可通過SignalR
連接處理UI更新。
運行時停留在服務器上並處理:
- 執行應用的C#代碼。
- 將UI事件從瀏覽器發送到服務器。
- 將UI更新應用於服務器發送回的已呈現的組件。
Blazor Server
用於與瀏覽器通信的連接還用於處理JavaScript
互操作調用。
JavaScript互操作
對於需要第三方JavaScript
庫和訪問瀏覽器API的應用,組件與JavaScript
進行互操作。組件能夠使用JavaScript
能夠使用的任何庫或API。C#代碼可調用到JavaScript
代碼,而JavaScript
代碼可調用到C#代碼。
在ASP.NET Core Blazor中從.NET方法調用JavaScript函數
https://docs.microsoft.com/zh-cn/aspnet/core/blazor/call-javascript-from-dotnet?view=aspnetcore-5.0
從ASP.NET Core Blazor中的JavaScript函數調用.NET方法
https://docs.microsoft.com/zh-cn/aspnet/core/blazor/call-dotnet-from-javascript?view=aspnetcore-5.0
Blazor支持的平台
瀏覽者 | Version |
---|---|
Apple Safari,包括 iOS | 當前† |
Google Chrome,包括 Android | 當前† |
Microsoft Edge | 當前† |
Mozilla Firefox | 當前† |
†最新指的是瀏覽器的最新版本。
Blazor模板選項
- Blazor WebAssembly項目模板:
blazorwasm
- Blazor Server項目模板:
blazorserver
Blazor生命周期方法
-
OnInitializedAsync
和OnInitialized
方法,執行代碼來初始化組件。要執行異步操作,請在操作上使用OnInitializedAsync
和await
關鍵字。 -
OnParametersSetAsync
和OnParametersSet
當組件已接收到的參數從其父和值被分配給屬性被調用。這些方法在組件初始化后以及每次呈現組件時執行。 -
OnAfterRenderAsync
並OnAfterRender
在組件完成渲染后調用。此時填充元素和組件引用。使用此階段使用呈現的內容執行其他初始化步驟,例如激活對呈現的DOM元素進行操作的第三方Java庫。
創建我的第一個Blazor應用
1. 先准備".NET SDK (Software Development Kit)"環境。
如果之前沒有安裝的先安裝這個SDK:
dotnet-sdk-5.0.202-win-x64.exe
如果已經安裝過了,檢查下當前版本:
dotnet --version
2. 根據Blazor項目模板創建應用
1) 命令行方式創建
dotnet new blazorserver -o BlazorApp --no-https
這里通過DOTNET-CLI
執行新建項目的命令,使用的是blazorserver
這個項目模板,輸出項目文件夾為BlazorApp
,給該項目設置為不需要HTTPS模式--no-https
。
如果要創建Blazor WebAssembly
項目,這里將blazorserver
改成blazorwasm
即可。
dotnet new blazorwasm -o BlazorApp --no-https
2) Visual Studio方式創建
打開Visual Studio 2019
最新版本,創建新項目,找到Blazor
相關的項目模板。
- Blazor Server應用
- Blazor WebAssembly應用
點擊下一步
,輸入BlazorApp
的項目名稱,點擊下一步
進行創建。
目標框架,可以選.NET Core 3.1(長期支持)
,也可以選擇.NET 5.0(當前)
,去掉勾選復選框配置HTTPS
,點擊創建
即可。
創建成功之后,會自動打開項目解決方案。
3. 運行應用
1) 命令行方式創建
如果通過命令行方式創建,運行應用只需要執行:
dotnet watch run
通過DOTNET-CLI
的Run
命令可以運行程序,這里添加watch
參數的好處就是,等下如果改動了文件,會熱重載,這樣調試起來就很方便。
運行起來之后,會看到瀏覽器彈出應用首頁。
如果要退出運行,只需要執行如下命令即可:
Ctrl + C
2) Visual Studio方式創建
在Visual Studio
里面運行就很簡單了,直接點運行BlazorApp
或者Ctrl + F5
即可。
默認會打開一個終端控制台界面,用來顯示Console日志。
運行起來之后,會看到瀏覽器彈出應用首頁。
4. 項目結構
Program.cs
啟用這個應用服務的入口節點。Startup.cs
配置應用服務和中間件的地方。App.razor
應用組件的根組件,也就是組件的入口位置。BlazorApp/Pages
包含一些示例頁面,也是頁面目錄。BlazorApp.csproj
描述應用項目及其依賴關系。
5. 認識首頁及組件
我們看到Pages/Index.razor
這個主頁。
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
這里已經有了SurveyPrompt
這樣一個組件,同時它有一個入參Title
可以定制。
6. 查看子組件計數器
打開Pages/Counter.razor
,這是一個計數器的組件。
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
我們可以從代碼看到,頂部@page "/counter"
實際上這個頁面組件的目錄,當我們切換到它時,路徑也會變化。
每次點擊Click me
按鈕的時候,就會從onclick
事件去找到IncrementCount
這個函數,在這個函數中,我們把currentCount
做了加1
,這時候,Current count
的值會跟着刷新,可以看出只要綁定好了數據,它就會幫我們自動去更新界面顯示了,還是很方便。
7. 在首頁添加子組件計數器
打開Pages/Index.razor
,我們在末尾追加Counter
即可。
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<Counter />
這時候,我們查看頁面就已經可以看到它了。
8. 修改組件添加屬性並使用
接下來,我們打開Pages/Counter.razor
,我們給這個組件添加一個參數屬性IncrementAmount
,默認值是1
, 這樣我們可以從外部傳遞值進來修改它,並且我們讓currentCount
不再是繼續加1
了,而是改成加IncrementAmount
的值,這樣我們就可以控制每次累加的幅度。
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
[Parameter]
public int IncrementAmount { get; set; } = 1;
private void IncrementCount()
{
currentCount += IncrementAmount;
}
}
然后,我們回到Pages/Index.razor
,在Counter
組件上給他傳遞IncrementAmount
的值進來。
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<Counter IncrementAmount="10" />
這時候,界面會自動重載,這時候,我們每次點擊Click me
,這個計時器組件就會加10
了,哈哈,是不是很簡單。
生成Blazor待辦事項列表應用
1. 先准備".NET SDK (Software Development Kit)"環境。
如果之前沒有安裝的先安裝這個SDK:
dotnet-sdk-5.0.202-win-x64.exe
如果已經安裝過了,檢查下當前版本:
dotnet --version
2. 創建待辦事項列表Blazor應用
dotnet new blazorserver -o TodoList --no-https
通過DOTNET-CLI
腳本的New
命令,新建一個blazorserver
項目模板,輸出目錄命名為TodoList
,同時不需要HTTPS
的支持。
3. 添加名為“Todo”的Blazor組件
cd TodoList
先切換到TodoList
項目文件夾中。
dotnet new razorcomponent -n Todo -o Pages
然后,通過DOTNET-CLI
腳本的New
命令,輸出目錄為Pages
,新建名為Todo
的razorcomponent
組件。
其中-n
全稱為--name
,用來指定創建的名稱,-o
全稱為--output
,用來指定在這個目錄進行創建。
Razor
組件文件名要求首字母大寫。打開Pages
文件夾,確認Todo
組件文件名以大寫字母T開頭。文件名應為Todo.razor
。
將在項目文件夾的Pages
目錄中,新建得到一個Todo.razor
文件。
接下來,我們需要在Todo.razor
文件頂部添加針對該文件組件的路由位置。
@page "/todo"
4. 將“Todo”的Blazor組件添加到左側導航
位於Shared
文件夾的NavMenu.razor
組件是控制左側導航的,我們需要將“Todo”的Blazor組件添加到左側導航中。
在NavMenu.razor
組件中,找到ul
元素下的li
節點,參考前面的例子,新建屬於Todo
組件的導航位置。
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
...
<li class="nav-item px-3">
<NavLink class="nav-link" href="todo">
<span class="oi oi-list-rich" aria-hidden="true"></span> ToDoList
</NavLink>
</li>
</ul>
</div>
這里我們需要注意的幾個關鍵點是,NavLink
的href
需要填寫組件的路由位置,其中span
元素是菜單圖標,緊跟着它的就是菜單名稱,這里我們用ToDoList
來作為菜單名稱。
保存之后,我們通過執行run
命令來運行,查看導航效果。
dotnet watch run
5. 添加“TodoItem”來存儲待辦事項數據。
這里一個小插曲就是為了更好的寫出TodoItem.cs
這個模型類,我遇到了兩個不錯的VSC插件,這里就順帶記錄一下。
- 名為C# Extensions的VSC插件,可以幫助我們支持在VSC中右鍵快速新建模型類和接口,有點類似VS中那種創建,至少幫你完成了當前命名空間的添加的活,所以值得推薦。
- 名為C# XML Documentation Comments的VSC插件,平時我們在VS中寫代碼,習慣性的如果要添加函數或者屬性的注釋的話,上來就是
///
然后等待自動完成得到一串添加注釋的注釋體,這個插件就是來干這個事情的,效果還不錯。
這兩個插件,搜索
C#
這個關鍵詞就可以看到了,基本上排在前面。
有了它們,我們創建TodoItem
模型類就如虎添翼了。
在項目的Data
文件夾中,我們右鍵,選擇New C# Class
,輸入TodoItem
,然后回車即可。
然后我們就自動完成,得到了一個基礎的空的TodoItem.cs
模型類,它的namespace
都是已經處理好了,我覺得比手寫強吧。
接下里,我們往TodoItem.cs
模型類中添加兩個屬性(最好是走prop
命令來新建),一個是Title
來存儲待辦事項的標題,一個是IsDone
來標記是否已完成該事項。
接下來,為了將來更好的閱讀這個模型,我們養成一個好習慣,給對應的模型和字段都添加好注釋,這時候C# XML Documentation Comments
這個VSC插件就要派上用場了,通過在所需要注釋代碼的開頭位置,輸入///
然后回車即可。
namespace TodoList.Data
{
/// <summary>
/// 待辦事項
/// </summary>
public class TodoItem
{
/// <summary>
/// 事項名稱
/// </summary>
/// <value></value>
public string Title { get; set; }
/// <summary>
/// 事項是否已完成
/// </summary>
/// <value></value>
public bool IsDone { get; set; }
}
}
6. 實現並完善“Todo”組件的邏輯。
有了上一步的TodoItem.cs
模型類,我們算是准備好了承載待辦事項業務的數據基礎,接下來我們就可以基於它來實現並完善我們的Todo
組件的核心邏輯了。
首先,我們新建一個TodoList
來承載所有的待辦事項,從業務上它應該是個TodoItem
待辦事項的數組,在Pages
目錄的Todo.razor
文件的@code
中,我們可以添加C#
代碼,我們添加一個TodoList
,注意記得添加using
使用。
@code {
/// <summary>
/// 待辦事項列表
/// </summary>
/// <value></value>
public List<TodoItem> TodoList { get; set; } = new List<TodoItem>();
}
然后,有了數據源TodoList
,我們構建一個界面來承載這個數據源,這里最方便的就是ul-li
組合了,在最外層ul
中,我們針對TodoList
做一個For-Each
循環遍歷,然后為每一項新建一個li
來承載它,並且綁定每一項的Title
,這樣我們就可以看到這個元素了。
<ul>
@foreach (var item in TodoList)
{
<li>@item.Title</li>
}
</ul>
7. 添加對新增待辦事項的支持。
有了前面的TodoList
數據源和ul-li
展示層,接下來我們來添加一些修改數據的支持了,首先我們要支持的就是往TodoList
里面插入新數據。
為了插入新數據,那么我們需要在界面上放出一個輸入框和一個提交按鈕吧,所以接下來我們在ul-li
的后面,添加下這個新增項組合。
<input placeholder="輸入新待辦事項標題" @bind="AddTitle" />
<button @onclick="AddToDoItem">新建</button>
我們用一個input
元素來讓用戶輸入新的待辦事項,placeholder
的內容是提示語:輸入新待辦事項標題
,它的值我們通過@bind
來做,我們綁定AddTitle
這個變量的值。
並且,我們通過添加一個名為新建
的button
元素來觸發@onclick
事件,這里讓@onclick
事件綁定AddToDoItem
方法,這里留意,等添加完新項之后,我們清空下AddTitle
的值,以便下次添加新項。
為了接收上訴輸入數據和點擊事件的綁定,我們也需要在@code
中添加對應的支持,下面我們就添加對應的支持:
@code {
...
/// <summary>
/// 新增待辦項標題
/// </summary>
/// <value></value>
public string AddTitle { get; set; }
/// <summary>
/// 添加待辦事項
/// </summary>
public void AddToDoItem()
{
if(!string.IsNullOrEmpty(AddTitle))
{
var todoItem = new TodoItem {
Title = AddTitle
};
TodoList.Add(todoItem);
AddTitle = string.Empty;
}
}
}
保存之后,會自動刷新界面,我們測試下,是否符合我們的預期。
測試后,我們看到,可以成功添加新項了。
8. 添加對修改待辦事項完成狀態的支持。
前面我們已經支持了展示待辦事項和添加待辦事項,那么最后的一環就是可以修改單個待辦事項的完成狀態了,也就是我們還沒使用到TodoItem
的IsDone
屬性。
我們為每一個TodoItem
項添加一個前置的復選框,以便於可以通過勾選的交互方式來改變它的完成狀態。
<h3>Todo (@TodoList.Count(x=>!x.IsDone))</h3>
<ul>
@foreach (var item in TodoList)
{
<li>
<input type="checkbox" @bind="item.IsDone" />
<span>@item.Title</span>
</li>
}
</ul>
我們在li
中添加一個input
元素,type
設置成checkbox
,我們用它來驅動待辦事項的完成狀態,所以我們需要給它綁定item.IsDone
,不僅每一項的IsDone
同時,為了清晰看到我們是否真的改變了數據,我們在h3
這個標題這里新增一個實時的統計數據,來展示目前還有多少未完成的待辦事項。
結合ASP.NET Core SignalR和Blazor實現聊天室應用
0. 什么是SignalR?
ASP.NET Core SignalR
是一種開放源代碼庫,可簡化將實時web功能添加到應用程序的功能。實時web功能使服務器端代碼可以立即將內容推送到客戶端。
適用於SignalR
:
- 需要從服務器進行高頻率更新的應用。示例包括游戲、社交網絡、投票、拍賣、地圖和GPS應用。
- 儀表板和監視應用。示例包括公司儀表板、即時銷售更新或旅行警報。
- 協作應用。協作應用的示例包括白板應用和團隊會議軟件。
- 需要通知的應用。社交網絡、電子郵件、聊天、游戲、旅行警報和很多其他應用都需使用通知。
SignalR
提供一個API,用於創建(RPC)的服務器到客戶端遠程過程調用。Rpc
通過服務器端.NET Core
代碼從客戶端調用JavaScript
函數。
下面是的某些功能SignalR ASP.NET Core
:
- 自動處理連接管理。
- 將消息同時發送到所有連接的客戶端。例如,聊天室。
- 向特定客戶端或客戶端組發送消息。
- 可縮放以處理不斷增加的流量。
SignalR
支持以下用於按正常回退)(處理實時通信的技術:
- WebSockets
- Server-Sent事件
- 長輪詢
SignalR
自動選擇服務器和客戶端功能內的最佳傳輸方法。
1. 先准備".NET SDK (Software Development Kit)"環境。
如果之前沒有安裝的先安裝這個SDK:
dotnet-sdk-5.0.202-win-x64.exe
如果已經安裝過了,檢查下當前版本:
dotnet --version
2. 創建Blazor Server應用
dotnet new blazorserver -o BlazorServerSignalRApp --no-https
通過DOTNET-CLI
腳本的New
命令,新建一個blazorserver
項目模板,輸出目錄命名為BlazorServerSignalRApp
,同時不需要HTTPS
的支持。
3. 給項目添加"SignalR"客戶端庫
cd .\BlazorServerSignalRApp\
dotnet add package Microsoft.AspNetCore.SignalR.Client
先要切換到BlazorServerSignalRApp
這個項目目錄,然后通過dotnet add package $packageName
來添加Microsoft.AspNetCore.SignalR.Client
這個依賴包。
ASP.NET Core SignalR .Net客戶端 - 通過ASP.NET Core SignalR .net客戶端庫,你可以SignalR從.net應用程序與中心進行通信。
4. 創建對話集線器Hub
在項目根目錄,創建新目錄Hubs
,用來存放集線器。
mkdir Hubs
然后新建集線器,用來承載對話。
在vsc中,在Hubs
文件夾上右鍵,選擇New C# Class
,新建一個名為ChatHub
的集線器模型類。
接下來,我們添加對ChatHub
的實現,需要引用System.Threading.Tasks
、Microsoft.AspNetCore.SignalR
,添加一個SendMessgae
的方法來支持發送消息業務,向所有的連接客戶端都發送消息。
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace BlazorServerSignalRApp.Hubs
{
/// <summary>
/// 對話集線器
/// </summary>
public class ChatHub : Hub
{
/// <summary>
/// 發送消息
/// </summary>
/// <param name="user"></param>
/// <param name="messgae"></param>
/// <returns></returns>
public async Task SendMessage(string user, string messgae)
{
// 向全部連接客戶端發送指定消息
await Clients.All.SendAsync("ReceiveMessage", user, messgae);
}
}
}
5. 為SignalR中心添加服務和終結點
前往Startup.cs
中,在ConfigureServices
函數中,配置我們需要的服務,這里主要是添加AddSignalR
(SignalR服務)和AddResponseCompression
(響應壓縮中間件服務),這里需要添加下對Microsoft.AspNetCore.ResponseCompression
的引用。
public void ConfigureServices(IServiceCollection services)
{
// 添加SignalR服務
services.AddSignalR();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
// 添加響應壓縮中間件服務
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
}
在Configure
函數中,在頂部添加UseResponseCompression
,使用處理管道的配置頂部的“響應壓縮中間件”。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 使用響應壓縮中間件
app.UseResponseCompression();
}
在Configure
函數中,找到app.UseEndpoints
添加新的終結點,在映射Blazor中心的終結點和客戶端回退之間,為中心添加一個終結點。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
// 添加對話集線器終結點
endpoints.MapHub<ChatHub>("/chathub");
endpoints.MapFallbackToPage("/_Host");
});
}
6. 添加用於聊天的Razor組件代碼
有了前面的基礎,接下來,我們構建下聊天對話的前端組件代碼。
dotnet new razorcomponent -n Chat -o Pages
然后,通過DOTNET-CLI
腳本的New
命令,輸出目錄為Pages
,新建名為Chat
的razorcomponent
組件。
然后我們得到了Chat.razor
這個組件,我們思考下聊天室這個業務場景里面,我們需要兩個輸入框和一個發送按鈕來承載發送新消息的作用,同時我們還需要一個展示已收到消息的列表。
<h3>聊天室</h3>
<div class="form-group">
<label>
發送用戶:
<input @bind="User">
</label>
</div>
<div class="form-group">
<label>
消息內容:
<input @bind="Message">
</label>
</div>
<button @onclick="SendMessage">發送</button>
<br/>
<ul>
@foreach (var message in MessageList)
{
<li>@message</li>
}
</ul>
@code {
/// <summary>
/// 發送用戶
/// </summary>
/// <value></value>
public string User { get; set; }
/// <summary>
/// 消息內容
/// </summary>
/// <value></value>
public string Message { get; set; }
/// <summary>
/// 發送消息
/// </summary>
public void SendMessage(){
}
/// <summary>
/// 消息列表
/// </summary>
/// <typeparam name="string"></typeparam>
/// <returns></returns>
public List<string> MessageList { get; set; } = new List<string>();
}
為了查看效果,我們添加Chat.razor
組件到Index.razor
頁面。
@page "/"
<Chat />
通過dotnet watch run
先看下效果。
7. 在Razor組件中連接聊天集線器
有了前面的界面基礎,接下來我們在Chat.razor
組件中先開始做連接集線器和接收消息的支持。
我們需要在組件啟動的時候就開始指定連接,所以這里我們用到一個組件的函數OnInitializedAsync
,我們通過重寫實現它來連接集線器。
ComponentBase.OnInitializedAsync方法,當組件准備好啟動時調用的方法,它從呈現樹中的父級接收了其初始參數,需要支持異步的時候,優先使用OnInitializedAsync
與此同時,我們還需要新建一個HubConnection
類型的hubConnection
私有對象,來承載連接。
/// <summary>
/// 集線器連接
/// </summary>
/// <value></value>
private HubConnection hubConnection { get; set; }
為了查看到連接效果,我們可以設置一個連接狀態IsConnectioned
,來獲取連接狀態。
/// <summary>
/// 是否已連上集線器
/// </summary>
private bool IsConnectioned =>
hubConnection != null ?
hubConnection.State == HubConnectionState.Connected :
false;
然后我們將IsConnectioned
的值和發送按鈕的可用狀態進行綁定。
<button @onclick="SendMessage" disabled="@(!IsConnectioned)" >發送</button>
接下來,我們重寫OnInitializedAsync
來完成對集線器的定義和連接。
/// <summary>
/// 組件初始化完成事件
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
// 定義聊天集線器(含終結點)
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
.WithAutomaticReconnect()
.Build();
// 開始連接集線器
await hubConnection.StartAsync();
}
運行查看效果:
這里我們在HubConnection
使用了WithAutomaticReconnect
這個自動斷開重連配置,在沒有任何參數的情況下,會WithAutomaticReconnect()
將客戶端配置為分別等待0、2、10和30秒,然后再嘗試重新連接嘗試。
8. 在Razor組件中接收聊天集線器消息
在Chat.razor
組件的OnInitializedAsync
中,我們順帶異步接收下集線器消息,這里注意的是,接收消息的方法名稱必須和ChatHub
中SendAsync
時定義的方法名稱保持一致。
另外,需要注意的是,接收到新消息之后,我們添加到新消息到MessageList
中之后,我們需要通過StateHasChanged
來主動觸發一次通知變更。
/// <summary>
/// 組件初始化完成事件
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
// 定義聊天集線器(含終結點)
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
.WithAutomaticReconnect()
.Build();
// 接收集線器消息
hubConnection.On<string, string>("ReceiveMessage", (user, message)=>
{
var messageItem = $"發送用戶:{user},消息內容:{message}";
MessageList.Add(messageItem);
StateHasChanged();
});
// 開始連接集線器
await hubConnection.StartAsync();
}
9. 在Razor組件中發送新聊天消息
之前在Chat.razor
組件中,我們已經預留了一個SendMessage
方法,接下來我們來完善它。
首先我們判斷User
和Message
是否填寫,填寫了之后,我們直接調用當前連接的SendAsync
即可,這里需要注意的是,方法參數名稱這里需要和ChatHub
中函數名稱保持一致。
/// <summary>
/// 發送消息
/// </summary>
public async Task SendMessage()
{
// 判斷輸入是否合法
if(!string.IsNullOrEmpty(User) && !string.IsNullOrEmpty(Message))
{
// 發送消息,方法名為"SendMessage",和"ChatHub"中方法名定義需一致。
await hubConnection.SendAsync("SendMessage", User, Message);
}
}
運行下,看下效果:
10. 退出Razor組件時關閉集線器連接
我們可以使用DisposeAsync
方法來在退出組件時,主動關閉集線器連接,這里需要引用@implements IAsyncDisposable
。
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@implements IAsyncDisposable
/// <summary>
/// 退出組件銷毀時事件
/// </summary>
/// <returns></returns>
public async ValueTask DisposeAsync()
{
// 關閉連接
await hubConnection.DisposeAsync();
}
11. 跨多個客戶端在聊天室測試群發消息
dotnet watch run
運行之后,我們把網址復制下,打開三個瀏覽器窗口,這時候,每一個窗口就是個獨立的客戶端,我們試着在一個窗口發送消息,發現這時候,三個窗口都可以順利收到消息,說明群發成功。
我們可以試着在第二個、第三個聊天室發送消息,發現都可以順利收到,哈哈,是不是很開心。
參考
- ASP.NET Core Blazor 簡介
- BlazorFluentUI
- Blazor Webassembly Demo
- DOM概述
- 在傳統 Web 應用和單頁應用 (SPA) 之間選擇
- WebAssembly (abbreviated Wasm)
- 從 ASP.NET Core Blazor 中的 JavaScript 函數調用 .NET 方法
- 在 ASP.NET Core Blazor 中從 .NET 方法調用 JavaScript 函數
- ASP.NET Core Blazor 托管模型
- 用於 ASP.NET Core Blazor 的工具
- Blazor Tutorial - Build your first Blazor app
- 生成 Blazor 待辦事項列表應用
- C# Extensions - VS MarketPlace
- C# XML Documentation Comments - VS MarketPlace
- BlazorApp
- TodoList
- 結合使用 ASP.NET Core SignalR 和 Blazor
- ASP.NET Core SignalR 簡介
- ASP.NET Core SignalR .Net 客戶端
- ComponentBase.OnInitializedAsync 方法
- 通過Blazor使用C#開發SPA單頁面應用程序(3)
- Bootstrap 風格的 Blazor UI 組件庫