溫故知新,.Net Core遇見Blazor(FluentUI),屬於未來的SPA框架


什么是Blazor

Blazor是一個使用.NET生成交互式客戶端WebUI的框架:

  • 使用C#代替JavaScript來創建信息豐富的交互式UI。
  • 共享使用.NET編寫的服務器端和客戶端應用邏輯。
  • 將UI呈現為HTML和CSS,以支持眾多瀏覽器,其中包括移動瀏覽器。
  • 與新式托管平台(如Docker)集成。

image

基於.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 PagesMVC不同,組件專門用於處理客戶端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方法編寫的消息:

image

組件呈現為瀏覽器文檔對象模型(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沙盒中運行,沙盒提供的保護可防御客戶端計算機上的惡意操作。

image

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服務器或服務中。

image

如果創建了Blazor Web Assembly應用進行部署,但沒有后端ASP.NET Core應用來為其文件提供服務,那么該應用被稱為獨立Blazor Web Assembly應用。如果創建了應用進行部署,但沒有后端應用來為其文件提供服務,那么該應用被稱為托管的Blazor Web Assembly應用。托管的Blazor Web Assembly Client應用通常使用WebAPI調用或SignalR(結合使用ASP.NET Core SignalRBlazor)通過網絡與后端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的瀏覽器中工作。
  • 團隊不熟悉JavaScriptTypeScript開發技術。

何時使用SPA:

  • 應用程序必須公開具有許多功能的豐富用戶界面。
  • 團隊熟悉JavaScriptTypeScriptBlazor 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才能正常工作;如果沒有客戶端JavaScript,SPA不是好的選擇。

團隊不熟悉JavaScript或TypeScript開發技術

如果團隊不熟悉JavaScriptTypeScript,但熟悉服務器端Web應用程序開發,那相較於SPA,他們交付傳統Web應用的速度可能更快。除非以學習SPA編程為目的,或需要SPA提供用戶體驗,否則對已經熟悉構建傳統Web應用的團隊而言,選擇傳統Web應用的工作效率更高。

何時選擇SPA

應用程序必須公開具有許多功能的豐富用戶界面

SPA可支持豐富客戶端功能,當用戶執行操作或在應用的各區域間導航時無需重新加載頁面。SPA很少需要重新加載整個頁面,因此加載速度更快,可在后台提取數據,並且對單個用戶操作的響應更快。SPA支持增量更新,可保存尚未完成的窗體或文檔,而無需用戶單擊按鈕提交窗體。SPA支持豐富的客戶端行為,例如拖放,比傳統應用程序更容易操作。可以將SPA設計為在斷開連接的模式下運行,對客戶端模型進行更新,並在重新建立連接后將更新最終同步回服務器。如果應用要求包括豐富的功能,且超出了典型HTML窗體提供的功能,則選擇SPA樣式應用程序。

通常,SPA需要實現內置於傳統Web應用中的功能,例如在反映當前操作的地址欄中顯示有意義的URL(並允許用戶將此URL存為書簽或對其進行深層鏈接以便返回此URL)。SPA還應允許用戶使用瀏覽器的后退和前進按鈕尋找用戶意料之中的結果。

團隊熟悉JavaScript和/或TypeScript開發

編寫SPA需要熟悉JavaScriptTypeScript以及客戶端編程技術和庫。團隊應有能力像使用Angular一樣使用SPA框架編寫新式JavaScript

參考-SPA框架

應用程序已為其他(內部或公共)客戶端公開API

如果已提供一個Web API供其他客戶端使用,則相較於在服務器端窗體中復制邏輯,創建一個利用這些APISPA實現更加容易。用戶與應用程序交互時,SPA廣泛使用Web API來查詢和更新數據。

何時選擇Blazor

應用程序必須公開豐富用戶界面

與基於JavaScript的SPA一樣,Blazor應用程序可以支持豐富的客戶端行為,而無需重載頁面。這些應用程序對用戶的響應更快,僅獲取響應給定用戶交互所需的數據(或HTML)。如果設計得當,可以將服務器端Blazor應用配置為以客戶端Blazor應用的形式運行,只需在此功能受支持后對它進行稍加更改即可。

與使用JavaScript或TypeScript開發相比,團隊更喜歡使用.NET開發

與使用JavaScriptTypeScript等客戶端語言相比,許多使用.NET和Razor的開發人員的工作效率更高。由於已經使用.NET開發了應用程序的服務器端,因此,使用Blazor可以確保團隊中的每名.NET開發人員都可以理解,並可能會生成應用程序前端的行為。

Blazor Server

Blazor將組件呈現邏輯從UI更新的應用方式中分離出來。Blazor ServerASP.NET Core應用中支持在服務器上托管Razor組件。可通過SignalR連接處理UI更新。

image

運行時停留在服務器上並處理:

  • 執行應用的C#代碼。
  • 將UI事件從瀏覽器發送到服務器。
  • 將UI更新應用於服務器發送回的已呈現的組件。

Blazor Server用於與瀏覽器通信的連接還用於處理JavaScript互操作調用。

image

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生命周期方法

  • OnInitializedAsyncOnInitialized方法,執行代碼來初始化組件。要執行異步操作,請在操作上使用OnInitializedAsyncawait關鍵字。

  • OnParametersSetAsyncOnParametersSet當組件已接收到的參數從其父和值被分配給屬性被調用。這些方法在組件初始化后以及每次呈現組件時執行。

  • OnAfterRenderAsyncOnAfterRender在組件完成渲染后調用。此時填充元素和組件引用。使用此階段使用呈現的內容執行其他初始化步驟,例如激活對呈現的DOM元素進行操作的第三方Java庫。

創建我的第一個Blazor應用

https://github.com/CraigTaylor/BlazorApp

1. 先准備".NET SDK (Software Development Kit)"環境。

如果之前沒有安裝的先安裝這個SDK:

dotnet-sdk-5.0.202-win-x64.exe

如果已經安裝過了,檢查下當前版本:

dotnet --version

image

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應用

image

點擊下一步,輸入BlazorApp的項目名稱,點擊下一步進行創建。

image

目標框架,可以選.NET Core 3.1(長期支持),也可以選擇.NET 5.0(當前),去掉勾選復選框配置HTTPS,點擊創建即可。

image

創建成功之后,會自動打開項目解決方案。

image

3. 運行應用

1) 命令行方式創建

如果通過命令行方式創建,運行應用只需要執行:

dotnet watch run

通過DOTNET-CLIRun命令可以運行程序,這里添加watch參數的好處就是,等下如果改動了文件,會熱重載,這樣調試起來就很方便。

image

運行起來之后,會看到瀏覽器彈出應用首頁。

image

如果要退出運行,只需要執行如下命令即可:

Ctrl + C

image

2) Visual Studio方式創建

Visual Studio里面運行就很簡單了,直接點運行BlazorApp或者Ctrl + F5即可。

image

默認會打開一個終端控制台界面,用來顯示Console日志。

image

運行起來之后,會看到瀏覽器彈出應用首頁。

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"實際上這個頁面組件的目錄,當我們切換到它時,路徑也會變化。

image

每次點擊Click me按鈕的時候,就會從onclick事件去找到IncrementCount這個函數,在這個函數中,我們把currentCount做了加1,這時候,Current count的值會跟着刷新,可以看出只要綁定好了數據,它就會幫我們自動去更新界面顯示了,還是很方便。

image

7. 在首頁添加子組件計數器

打開Pages/Index.razor,我們在末尾追加Counter即可。

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<Counter />

這時候,我們查看頁面就已經可以看到它了。

image

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了,哈哈,是不是很簡單。

image

生成Blazor待辦事項列表應用

https://github.com/CraigTaylor/TodoList

1. 先准備".NET SDK (Software Development Kit)"環境。

如果之前沒有安裝的先安裝這個SDK:

dotnet-sdk-5.0.202-win-x64.exe

如果已經安裝過了,檢查下當前版本:

dotnet --version

image

2. 創建待辦事項列表Blazor應用

dotnet new blazorserver -o TodoList --no-https

通過DOTNET-CLI腳本的New命令,新建一個blazorserver項目模板,輸出目錄命名為TodoList,同時不需要HTTPS的支持。

image

3. 添加名為“Todo”的Blazor組件

cd TodoList

image

先切換到TodoList項目文件夾中。

dotnet new razorcomponent -n Todo -o Pages

然后,通過DOTNET-CLI腳本的New命令,輸出目錄為Pages,新建名為Todorazorcomponent組件。

其中-n全稱為--name,用來指定創建的名稱,-o全稱為--output,用來指定在這個目錄進行創建。

Razor組件文件名要求首字母大寫。打開Pages文件夾,確認Todo組件文件名以大寫字母T開頭。文件名應為Todo.razor

image

將在項目文件夾的Pages目錄中,新建得到一個Todo.razor文件。

image

接下來,我們需要在Todo.razor文件頂部添加針對該文件組件的路由位置。

@page "/todo"

image

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>

image

這里我們需要注意的幾個關鍵點是,NavLinkhref需要填寫組件的路由位置,其中span元素是菜單圖標,緊跟着它的就是菜單名稱,這里我們用ToDoList來作為菜單名稱。

保存之后,我們通過執行run命令來運行,查看導航效果。

dotnet watch run

image

image

5. 添加“TodoItem”來存儲待辦事項數據。

這里一個小插曲就是為了更好的寫出TodoItem.cs這個模型類,我遇到了兩個不錯的VSC插件,這里就順帶記錄一下。

  • 名為C# Extensions的VSC插件,可以幫助我們支持在VSC中右鍵快速新建模型類和接口,有點類似VS中那種創建,至少幫你完成了當前命名空間的添加的活,所以值得推薦。

image

  • 名為C# XML Documentation Comments的VSC插件,平時我們在VS中寫代碼,習慣性的如果要添加函數或者屬性的注釋的話,上來就是///然后等待自動完成得到一串添加注釋的注釋體,這個插件就是來干這個事情的,效果還不錯。

image

這兩個插件,搜索C#這個關鍵詞就可以看到了,基本上排在前面。

有了它們,我們創建TodoItem模型類就如虎添翼了。

在項目的Data文件夾中,我們右鍵,選擇New C# Class,輸入TodoItem,然后回車即可。

image

然后我們就自動完成,得到了一個基礎的空的TodoItem.cs模型類,它的namespace都是已經處理好了,我覺得比手寫強吧。

image

接下里,我們往TodoItem.cs模型類中添加兩個屬性(最好是走prop命令來新建),一個是Title來存儲待辦事項的標題,一個是IsDone來標記是否已完成該事項。

image

接下來,為了將來更好的閱讀這個模型,我們養成一個好習慣,給對應的模型和字段都添加好注釋,這時候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; }
    }
}

image

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>();
}

image

然后,有了數據源TodoList,我們構建一個界面來承載這個數據源,這里最方便的就是ul-li組合了,在最外層ul中,我們針對TodoList做一個For-Each循環遍歷,然后為每一項新建一個li來承載它,並且綁定每一項的Title,這樣我們就可以看到這個元素了。

<ul>
    @foreach (var item in TodoList)
    {
        <li>@item.Title</li>
    }
</ul>

image

7. 添加對新增待辦事項的支持。

有了前面的TodoList數據源和ul-li展示層,接下來我們來添加一些修改數據的支持了,首先我們要支持的就是往TodoList里面插入新數據。

為了插入新數據,那么我們需要在界面上放出一個輸入框和一個提交按鈕吧,所以接下來我們在ul-li的后面,添加下這個新增項組合。

<input placeholder="輸入新待辦事項標題" @bind="AddTitle" />
<button @onclick="AddToDoItem">新建</button>

image

我們用一個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;
        }
    }
}

image

保存之后,會自動刷新界面,我們測試下,是否符合我們的預期。

image

測試后,我們看到,可以成功添加新項了。

image

8. 添加對修改待辦事項完成狀態的支持。

前面我們已經支持了展示待辦事項和添加待辦事項,那么最后的一環就是可以修改單個待辦事項的完成狀態了,也就是我們還沒使用到TodoItemIsDone屬性。

我們為每一個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這個標題這里新增一個實時的統計數據,來展示目前還有多少未完成的待辦事項。

image

結合ASP.NET Core SignalR和Blazor實現聊天室應用

https://github.com/CraigTaylor/BlazorServerSignalRApp

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

image

2. 創建Blazor Server應用

dotnet new blazorserver -o BlazorServerSignalRApp --no-https

通過DOTNET-CLI腳本的New命令,新建一個blazorserver項目模板,輸出目錄命名為BlazorServerSignalRApp,同時不需要HTTPS的支持。

image

3. 給項目添加"SignalR"客戶端庫

cd .\BlazorServerSignalRApp\
dotnet add package Microsoft.AspNetCore.SignalR.Client

image

先要切換到BlazorServerSignalRApp這個項目目錄,然后通過dotnet add package $packageName來添加Microsoft.AspNetCore.SignalR.Client這個依賴包。

image

ASP.NET Core SignalR .Net客戶端 - 通過ASP.NET Core SignalR .net客戶端庫,你可以SignalR從.net應用程序與中心進行通信。

4. 創建對話集線器Hub

在項目根目錄,創建新目錄Hubs,用來存放集線器。

mkdir Hubs

image

然后新建集線器,用來承載對話。

在vsc中,在Hubs文件夾上右鍵,選擇New C# Class,新建一個名為ChatHub的集線器模型類。

image

接下來,我們添加對ChatHub的實現,需要引用System.Threading.TasksMicrosoft.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" });
    });
}

image

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");
    });
}

image

6. 添加用於聊天的Razor組件代碼

有了前面的基礎,接下來,我們構建下聊天對話的前端組件代碼。

dotnet new razorcomponent -n Chat -o Pages

然后,通過DOTNET-CLI腳本的New命令,輸出目錄為Pages,新建名為Chatrazorcomponent組件。

image

然后我們得到了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 />

image

通過dotnet watch run先看下效果。

image

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();
}

運行查看效果:

image

這里我們在HubConnection使用了WithAutomaticReconnect這個自動斷開重連配置,在沒有任何參數的情況下,會WithAutomaticReconnect()將客戶端配置為分別等待0、2、10和30秒,然后再嘗試重新連接嘗試。

8. 在Razor組件中接收聊天集線器消息

Chat.razor組件的OnInitializedAsync中,我們順帶異步接收下集線器消息,這里注意的是,接收消息的方法名稱必須和ChatHubSendAsync時定義的方法名稱保持一致。

另外,需要注意的是,接收到新消息之后,我們添加到新消息到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();
}

image

9. 在Razor組件中發送新聊天消息

之前在Chat.razor組件中,我們已經預留了一個SendMessage方法,接下來我們來完善它。

首先我們判斷UserMessage是否填寫,填寫了之后,我們直接調用當前連接的SendAsync即可,這里需要注意的是,方法參數名稱這里需要和ChatHub中函數名稱保持一致。

/// <summary>
/// 發送消息
/// </summary>
public async Task SendMessage()
{
    // 判斷輸入是否合法
    if(!string.IsNullOrEmpty(User) && !string.IsNullOrEmpty(Message))
    {
        // 發送消息,方法名為"SendMessage",和"ChatHub"中方法名定義需一致。
        await hubConnection.SendAsync("SendMessage", User, Message);
    }
}

image

運行下,看下效果:

image

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();
}

image

11. 跨多個客戶端在聊天室測試群發消息

dotnet watch run

運行之后,我們把網址復制下,打開三個瀏覽器窗口,這時候,每一個窗口就是個獨立的客戶端,我們試着在一個窗口發送消息,發現這時候,三個窗口都可以順利收到消息,說明群發成功。

image

我們可以試着在第二個、第三個聊天室發送消息,發現都可以順利收到,哈哈,是不是很開心。

image

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM