初探 Blazor WebAssembly (WASM)
2023-12-10
筆記使用 Blazor WebAssembly 開發的初體驗,想要寫一個純粹前端的簡易應用,但這次不是選擇 Vue.js 或者是 jQuery,而是直接把後端的 C# 拿來前端寫 😆 最後結合 GitHub Action 的另一個初體驗,達成 Commit 自動部署在 gh-pages 分支的功效,完成 Commit 即部署的便利 😎
Blazor WebAssembly
在 .NET 7,Blazor 分為 Blazor Server 以及 Blazor WebAssembly,本次選擇的是完全部署於 Client 端的 Web Assembly。
Features | Blazor Server | Blazor WebAssembly |
---|---|---|
執行速度 | 初始執行快,因為大部分 Code 在 Server 執行 | 初始執行慢,需要下載整個應用 Dependency |
部署複雜度 | 簡單,所有 Code 在 Server 執行 | 複雜,需要前端部署 |
資源利用 | Server 管理資源 | 利用 Client Side 計算能力 |
數據保護 | 較容易保護敏感數據 | 需要 Client Side 安全措施 |
網路依賴 | 高度依賴網路連線品質 | 下載後可以 Offline Running |
伺服器負擔 | 隨用戶增加而增加 | 主要在 Client Side 處理 |
延遲敏感 | 對網路延遲敏感 | 較少受網路延遲影響 |
適用場景 | 快速部署、頻繁 Server 交互的應用 | 高即時性、需要 Offlline 功能的應用 |
專案建立之後,馬上開寫,延續著之前寫 Vue.js 的經驗,一開始就是單檔開發,完全沒考慮 Components 設計的 😁
而在 Blazor 當中是撰寫 /Pages/Index.razor
因為有 ASP.NET MVC 寫 Razor 的經驗,之前也把玩過 Razor Pages 專案類型,因此寫起來完全沒問題。也因為 Vue.js 的經驗,大概知曉上面 Template 下面 Code 的區隔,只差沒有 Scoped CSS 😅
以往要透過 JS 的處理,延續著 Vue.js 的經驗,找到如何在 Blazor Razor 使用 Two-Way Binding,一切的工作就輕鬆的寫了起來。
Pages/Index.razor
<td>@(item.Defence - (item.IsEnhanceable ? item.Enhanced : 0))</td>
<td>
@if (item.IsEnhanceable)
{
<button
class="btn btn-outline-info me-lg-3 btn-sm @((appRuntime.CoolDownState) ? "disabled" : "" )"
@onclick="() => Enhance(item)">防卷</button>
}
</td>
邏輯控制什麼的就是 Razor 習慣的語法,Click 事件 則透過 @onclick
搭配 lambda 的方式來註冊使用。
<div class="col-12 col-lg-4 d-flex align-items-center">
<label>LV:</label>
<input type="number" @bind="appRuntime.Level" class="form-control" />
</div>
<div class="col-12 col-lg-4 mt-3 mt-lg-0 d-flex align-items-center">
<span>AC:</span>
<input type="number" value="@sumDefence()" class="form-control" disabled/>
</div>
public int sumDefence()
{
var baseDef = 10;
var defFromLevelDex = (int)Math.Floor((int)appRuntime.Level / 4.0);
foreach (var equip in appRuntime.Equipments)
{
baseDef = baseDef + equip.Defence - equip.Enhanced;
}
baseDef -= defFromLevelDex;
return baseDef;
}
Two-Way Binding 根據 Input 的操作,對應的動態計算,只需要在 input 透過 @Bind
就可以輕鬆達成。
寫著寫著,因為狀態管理的處理多了起來,於是就獨立抽出來,延續 ASP.NET MVC 的習慣,用 Models 來管理資料模型,也因為應用系統不大,沒有再額外做 Service 或者 Business Logic 的分層,直接在 Models 集合起來。
/Models/AppRuntime.cs
public class AppRuntime
{
public bool CoolDownState { get; set; } = false;
public int Level { get; set; } = 48;
public int Dex { get; set; } = 18;
public int Experience { get; set; } = 0;
public List<Equipment> Equipments { get; set; }
public AppRuntime()
{
Equipments = new List<Equipment> {
new Equipment { Name = "艾爾穆的祝福", Safe = 6, Defence = -6, Image = "helmet" },
new Equipment { Name = "精靈金屬盔甲", Safe = 6, Defence = -6, Image = "armor" },
};
}
}
Program.cs
builder.Services.AddScoped<AppRuntime>();
需要共用的狀態資料,透過 IoC 的方式注入。
/Pages/Index.razor
@using LineageEnhance.Models
@inject AppRuntime appRuntime
對於需要使用的頁面直接 @inject
以及 @using
就可以使用,超級方便 😍
Async & Await
而這次實驗的 Apps,想要使用非同步的效果,在工作執行後,先暫停重複觸發工作,但同時又可以進行其他類型工作的方式。
以往暫停都是透過 Thread.Sleep
,但這樣會讓所有的操作都停下來等待,不是非同步的效果。
運用共用的狀態 CoolState
,當 Button 觸發,會觸發非同步的 Method,但不必 Await,而是會任由等待,於是同時就可以做其他的事情。
/Pages/Index.razor
private async Task Enhance(Equipment equipment, bool blessed = false)
{
if (!equipment.IsEnhanceable || appRuntime.CoolDownState == true)
{
return;
}
appRuntime.CoolDownState = true;
var enhanceSystem = new EnhanceSystem(appRuntime);
await enhanceSystem.Enhance(equipment, blessed).ConfigureAwait(false);
}
這邊的 Enhance 類似 Controller 的用途,主要先將按鈕的狀態透過 appRuntime.CoolDownState
調整為不可使用,而 Controller 本身也會檢查如果是不允許使用的狀態會直接 return。
實際的運作則是透過已經抽出來的 EnhanceSystem.Enhance
來進行,其實是不需要使用 await
但 IDE 不喜歡,IDE 認為 Enhace 是 async Task
就應該要有 await
,但可以透過 ConfigureAwait(false)
的方式來解除 😅
/Models/EnhanceSystem.cs
public async Task Enhance(Equipment equipment, bool blessed = false)
{
...
await Task.Delay(25);
_appRuntime.CoolDownState = false;
}
而因為 CoolDownState
是實作刻意的,透過 await Task.Delay
來達成,而非使用 Thread.Sleep
,最後也在 EnhanceSystem.Enhance
的結尾恢復 CoolDownState
。
Deploy to GitHub Page with GitHub Action
- Settings "Workflow permissions" : "Read and write permissions" : 解決 GitHub Action 執行 發生 '/usr/bin/git' failed with exit code 128 權限不足的問題
- 新增
.nojekyll
: 直接在/wwwroot
加入空檔案 - 新增 404.html : 透過 GitHub Action 去處理
- 修正
index.html
base href: 透過 GitHub Action
name: .NET
on:
push:
branches: [ master ]
jobs:
deploy-to-github-pages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: 7.0.x
- name: Publish .NET Core Project
run: dotnet publish LineageEnhance.csproj -c Release -o release --nologo
- name: Modify index.html
run: sed -i 's/\/LineageEnhance"/\/LineageEnhance\/"/' release/wwwroot/index.html
- name: copy index.html to 404.html
run: cp release/wwwroot/index.html release/wwwroot/404.html
- name: Commit wwwroot to GitHub Pages
uses: JamesIves/github-pages-deploy-[email protected]
with:
BRANCH: gh-pages
FOLDER: release/wwwroot
參考 PuTaoNini 所分享的將 Blazor WebAssembly 部署到 GitHub Pages