大家好,我是沙漠盡頭的狼。
Dotnet9 網站回歸 Blazor 重構,訪問速度確實飛快,同時用上 Blazor 的互動能力,站長也同步加入了幾個線上工具,這篇文章分享下 Blazor 的重構過程,希望對大家網站開發時做技術選型有個參考。

1. 先聊聊 Razor Pages
上個版本網站前台使用的 Razor Pages 開發,當時選擇這個技術棧主要是為了搜尋引擎的 SEO 最佳化考量。
關於 MVC 和 Razor Pages 哪個更優,我們這裡只說說 Razor Pages 相對的優勢。
首先,Razor Pages 相對於 MVC 來說,更加簡單和直觀。由於 Razor Pages 將檢視和處理邏輯封裝在同一個頁面中,開發人員可以更容易地理解和維護程式碼。對於小型專案或者只有少量頁面的應用來說,Razor Pages 可以提供更快的開發速度和更簡潔的程式碼結構,這是站長當時從 MVC 重構成 Razor Pages 的主要選擇理由。
其次,Razor Pages 在 SEO(搜尋引擎最佳化)方面具有一定的優勢。由於 Razor Pages 將檢視和處理邏輯封裝在同一個頁面中,搜尋引擎可以更容易地理解和索引頁面的內容。這對於需要更好搜尋引擎排名的應用來說,是一個重要的考量因素。
說 Razor Pages 優勢,那為啥現在又換 Blazor 了?因為 Blazor 可能又是更好的選擇了,我們接著說。
2. 關鍵聊聊 Blazor
Blazor 是一個新興的 Web 開發框架,它可以讓開發人員使用 C# 語言來編寫 Web 應用程式,而不必使用 JavaScript,當然只能說盡量少用,完全不用也不太現實。相比於 Razor Pages 和 MVC,Blazor 提供了一種全新的開發模式,具有許多獨特的優勢和適用場景。
首先,Blazor 提供了真正的前端開發體驗。傳統的 Web 開發中,前端開發人員需要使用 JavaScript 來處理頁面的互動和動態效果,而後端開發人員則負責處理業務邏輯和資料操作。這種分離的開發模式可能導致開發人員之間的溝通和協作問題。而 Blazor 使用 C# 語言來編寫前端程式碼,使得前端和後端開發人員可以使用相同的語言和工具,更加高效地協作開發。
其次,Blazor 提供了更好的效能和使用者體驗,Blazor 提供了用戶端和服務端兩種模式(Blazor 混合模式有機會我們再談):
- 用戶端模式:Blazor 使用 WebAssembly 技術,在瀏覽器中直接執行編譯後的二進位碼,可以實現接近原生應用的效能。
- 服務端模式:與傳統的基於 HTTP 請求的頁面重新整理相比,Blazor 使用 SignalR 連線來實現即時資料更新和雙向繫結,可以提供更快速和流暢的使用者體驗。
另外,Blazor 還具有更好的可重用性和元件化開發。Blazor 提供了豐富的元件庫和工具,可以幫助開發人員更快地構建出漂亮且功能強大的介面。開發人員可以將常用的 UI 元件封裝成可重複使用的元件,提高開發效率和程式碼品質。
此外,Blazor 還支援現代化的前端開發技術和工具。開發人員可以使用 Blazor 與現有的 JavaScript 函式庫和框架進行整合,如 React、Vue.js 等。
總之,Blazor 對於 Razor Pages 和 MVC 來說是一個更好的選擇,特別是對於需要更好的前端開發體驗、更好的效能和使用者體驗以及更好的可重用性和元件化開發的專案來說。然而,選擇使用哪種開發模式還是要根據專案的具體需求和開發團隊的偏好來決定。無論選擇哪種模式,重要的是根據專案的實際情況做出合理的選擇,並且在開發過程中遵循良好的設計原則和最佳實踐。
3. 再聊聊為啥又用 Blazor 了?
站長在去年對網站前台使用 Blazor Server 開發過一個版本,當時因為斷線重連體驗的問題,站長選擇用 Razor Pages 重構了。
這次站長回歸 Blazor 的轉折點在 6 月 13 號 – .NET 8 Preview 5 發布,VS2022 預覽版也跟著出了 Blazor Web App 專案範本,各個技術群也討論瘋了,站長在 Razor Pages 中加入了 Razor 元件嘗試,微軟確實牛逼,旨在使 Blazor 元件能夠滿足用戶端和伺服器端的所有 Web UI 需求。
但目前該模式 Razor 元件無法互動,頁面還出現了重連置灰 UI,索性直接用 Blazor Server 重構,經過幾天的奮戰,網站前台已經用 Blazor Server 完全取代 Razor Pages,煩人的重連也解決了,現在訪問網站飛快,不知道是不是錯覺,個人感覺很好。(重連問題參考微軟文件【ASP.NET Core Blazor SignalR 指南】和 Token 佬寫的文章 【如何取消 Blazor Server 煩人的重新連接?】。)
Razor Pages(MVC)與 Blazor 都使用的 Razor 語法,所以理論上切換是無縫的,核心程式碼改動不大,專案程式碼檔案結構對比看下面截圖,不再贅述,有興趣看原始碼吧,兩個版本程式碼都在。
| Razor Pages 版工程結構 | Blazor Server 版工程結構 |
![]() |
![]() |
4. Blazor 的互動便利:帶來幾個線上工具
對於頁面的事件處理,使用 Blazor 就方便了,下面是昨晚加的 4 個小工具:

有興趣的朋友可以點擊體驗:https://dotnet9.com/tools, 我們直接貼上 4 個小工具程式碼,你可能會喜歡上 Blazor 的程式碼風格。
4.1. JSON 格式化
訪問位址:https://dotnet9.com/tools/jsonformatter

頁面程式碼:
@page "/tools/jsonformatter"
@using System.Text.Json
<PageTitle>@_title</PageTitle>
<MApp>
<h2 style="margin-bottom: 30px; margin-top: 10px; text-align: center;">@_title</h2>
<div>
<MTextarea BackgroundColor="grey lighten-2" Solo
Color="orange orange-darken-4" TValue="string" @bind-Value="_inputJson"
Label="輸入 Json" Rows="8" style="font-size:12px;" RowHeight="15" AutoGrow/>
</div>
<div>
<MButton Color="success" class="ma-2" OnClick="() => FormatJson(true)">格式化</MButton>
<MButton Color="lime" OnClick="() => FormatJson(false)">壓縮</MButton>
</div>
<div>
<MTextarea BackgroundColor="amber lighten-4" Solo
Color="orange orange-darken-4" TValue="string" @bind-Value="_formattedJson"
Label="格式化或壓縮後 Json" Rows="8" style="font-size:12px;" RowHeight="15" AutoGrow/>
</div>
</MApp>
@code
{
private const string? _title = "工具箱-JSON格式化";
private string? _inputJson;
private string? _formattedJson;
private void FormatJson(bool writeIndented)
{
try
{
var jsonObject = JsonDocument.Parse(_inputJson).RootElement;
_formattedJson = JsonSerializer.Serialize(jsonObject, new JsonSerializerOptions { WriteIndented = writeIndented });
}
catch (JsonException)
{
_formattedJson = "無效的JSON格式";
}
}
}
4.2. 線上字串編碼工具
訪問位址:https://dotnet9.com/tools/string-encoder

頁面程式碼:
@page "/tools/string-encoder"
<PageTitle>@Title</PageTitle>
<MApp>
<h2 style="margin-bottom: 30px; margin-top: 10px; text-align: center;">@Title</h2>
<p>
<MTextarea BackgroundColor="grey lighten-2"
Color="cyan" Solo TValue="string" @bind-Value="_inputString"
Label="輸入字串"/>
</p>
<p>
<MTextarea BackgroundColor="amber lighten-4" Solo
Color="orange orange-darken-4" TValue="string" @bind-Value="_encodedOrDecodeString"
Label="編/解碼結果"/>
</p>
<p>
<MButton OnClick="@Encode">編碼</MButton>
<MButton OnClick="@Decode">解碼</MButton>
<MButton OnClick="@Clear">清空</MButton>
</p>
</MApp>
@code {
private const string Title = "工具箱-線上字串編碼工具";
private string? _inputString;
private string? _encodedOrDecodeString;
private void Encode()
{
_encodedOrDecodeString = System.Web.HttpUtility.UrlEncode(_inputString);
}
private void Decode()
{
_encodedOrDecodeString = System.Web.HttpUtility.UrlDecode(_inputString);
}
private void Clear()
{
_inputString = string.Empty;
_encodedOrDecodeString = string.Empty;
}
}
4.3. 倒數計時
訪問位址:https://dotnet9.com/tools/countdown

頁面程式碼:
@page "/tools/countdown"
<PageTitle>@Title</PageTitle>
<MApp>
<h2 style="margin-bottom: 30px; margin-top: 10px; text-align: center;">@Title</h2>
<p>
<MTextField Label="持續時間(秒)" Type="number" TValue="int" @bind-Value="@_duration"/>
</p>
<p>
<MButton Color="success" class="ma-2" OnClick="@StartCountdown" Disabled="@_isCountingDown">開始</MButton>
<MButton Color="pink" class="ma-2 white--text" OnClick="@PauseCountdown" Disabled="!_isCountingDown">暫停</MButton>
<MButton Color="deep-orange" class="ma-2 white--text" OnClick="@ResetCountdown" Disabled="!_isCountingDown">重設</MButton>
剩餘時間(秒):@_remainingTime
</p>
<div class="text-center">
<MProgressCircular Value="@_remainingTimePercent" Size="100" Width="15" Rotate="360" Color="teal">@_remainingTime</MProgressCircular>
</div>
</MApp>
@code {
private const string Title = "工具箱-倒數計時";
private int _duration = 20;
private int _remainingTime;
private int _remainingTimePercent;
private bool _isCountingDown;
private bool _isPause;
private CancellationTokenSource? _countdownTokenSource;
private async Task StartCountdown()
{
if (_duration < 0)
{
_duration = 10;
}
if (_isCountingDown)
{
return;
}
_isCountingDown = true;
if (!_isPause || _remainingTime <= 0)
{
_remainingTime = _duration;
ChangeRemainingTimePercent();
}
_countdownTokenSource = new CancellationTokenSource();
await Countdown(_countdownTokenSource.Token);
}
private void PauseCountdown()
{
if (!_isCountingDown)
{
return;
}
_isCountingDown = false;
_isPause = true;
_countdownTokenSource?.Cancel();
}
private async void ResetCountdown()
{
_isPause = false;
if (_isCountingDown && _countdownTokenSource != null)
{
await _countdownTokenSource.CancelAsync();
}
_remainingTime = _duration;
_isCountingDown = false;
ChangeRemainingTimePercent();
}
private async Task Countdown(CancellationToken cancellationToken)
{
while (_remainingTime > 0)
{
await Task.Delay(1000, cancellationToken);
_remainingTime--;
ChangeRemainingTimePercent();
if (cancellationToken.IsCancellationRequested)
{
return;
}
}
_isCountingDown = false;
}
private async void ChangeRemainingTimePercent()
{
_remainingTimePercent = (int)(_remainingTime * 100.0 / _duration);
await InvokeAsync(StateHasChanged);
}
}
4.4. 時間戳轉換
訪問位址:https://dotnet9.com/tools/timestamp
站長原來寫過一篇,可以看這裡:使用 Blazor 做個簡單的時間戳線上轉換工具。
5. 總結
網站可能存在 Bug,不,一定存在 Bug,站長會一直重構迭代下去。
很高興將網站前台重構後分享這個喜悅給大家,祝大家端午安康。
- 網站位址:https://dotnet9.com/
- 網站原始碼:https://github.com/dotnet9/Dotnet9
- .NET 版本: .NET 8.0.0-preview.5.23280.8

