Blazor 10新功能介紹 - 3
在 .NET 10 的 Blazor中,安全性與前端互動能力都有重要提升。其中Passkey(通行密碼)支援讓 ASP.NET Core Identity 能直接實作無密碼登入,使用者可透過指紋、臉部辨識或硬體安全金鑰進行驗證,大幅提升安全性並降低密碼管理負擔。此外,Blazor 的 JavaScript Interop也導入更完整的物件語意,開發者可以直接呼叫 JavaScript 建構函式建立物件,並從 C# 讀寫 JavaScript 物件屬性。這些改進讓 Blazor 在身分驗證與前端整合方面更加現代化,也使開發流程更簡潔與安全。
本文將延續《Blazor 10新功能介紹 - 1》、《Blazor 10新功能介紹 - 2》一文的情境,介紹這些Blazor新功能。
Passkey(通行密碼)支援
Passkey(通行密碼)主要是取代或增強傳統密碼的加密憑證,讓使用者能夠透過設備原生的安全機制(如指紋辨識、臉部解鎖或硬體安全金鑰)來進行身分驗證,實現無密碼的登入體驗。Passkey主要優勢是在於能防範釣魚攻擊(Phishing-resistant)、安全且易於使用,同時解決了使用者記憶與管理複雜密碼的負擔。
.NET 10版的 ASP.NET Core
Identity套件已經內建了Passkey 的支援,開發者不再需手動撰寫複雜的驗證程式碼。當你建立一個Blazor Web App 的專案,只要將「Authentication type」設定為「Individual Accounts」預設就包含了 Passkey 的管理與登入介面,以及提供註冊 Passkey、列出已註冊金鑰、刪除金鑰、使用 Passkey 登入,建立無密碼的帳號等流程的完整程式碼,請參考下圖所示:
圖 1:建立使用ASP.NET Core Identity套件的專案
專案中會包含「PasskeyInputModel.cs」、「PasskeyOperation.cs」等定義型別程式檔,請參考下圖所示:
圖 2:Passkey相關型別
當網站執行時,可以直接選取首頁「Register」選單項目註冊一個使用者帳號,請參考下圖所示:
圖 3:註冊使用者帳號
下一個畫面會跳出一個錯誤訊息,按下「Apply Migrations」按鈕,使用EF移轉建立資料庫,請參考下圖所示:
圖 4:使用EF移轉建立資料庫
下一步點選「Click here to confirm your account」確認電子郵件,請參考下圖所示:
圖 5:確認電子郵件
電子郵件確認完成,請參考下圖所示:
圖 6:電子郵件確認
最後使用剛註冊的帳號登入網站,請參考下圖所示:
圖 7:登入網站
進入帳號管理畫面,便可新增想使用的Passkey,請參考下圖所示:
圖 8:新增想使用的Passkey
JavaScript互動性(JavaScript Interop)
.NET 10版中的Blazor,同樣在JavaScript互動性(JavaScript Interop)方面做了很大的改進。過去的 JavaScript 互動性主要停留在「功能性」層面,只能呼叫函式,或傳遞基本型別,而 .NET 10版則引入了真正的物件語意(Object Semantics),讓開發體驗更加簡潔與現代化。
在過去的版本中,要在
Blazor 建立一個 JavaScript 物件,通常需要使用「eval」函式,或透過全域變數來處理,這不僅容易污染全域命名空間,也不太安全。
現在,.NET 10版可以支援直接呼叫 JavaScript 建構函式(Constructor Function), 並且提供了一個「InvokeConstructorAsync」方法,讓你可以直接使用「new」運算子以非同步方式建立JavaScript 物件,不再需要使用 「eval」函式,讓程式更簡潔易懂。
舉例來說下列的「sayhi.js」檔案中匯出一個「HiHelper」類別,其中包含一個「SayHi」方法會顯示一個歡迎訊息:
// 匯出 `HiHelper` 類別供
Blazor 端建立物件。
export class HiHelper {
// 定義 `SayHi` 方法並接收外部傳入的名稱。
SayHi(name) {
// 如果傳入值是字串就去除前後空白,否則使用空字串。
const displayName = typeof name === "string" ? name.trim() : "";
// 如果有有效名稱就使用它,否則改用預設值 `Blazor`。
const finalName = displayName.length > 0 ? displayName : "Blazor";
// 顯示包含名稱的提示訊息。
alert(`HI, ${finalName}`);
}
}
在Blazor 元件中加入以下程式碼,讓使用者輸入名稱,按下按鈕後,呼叫JavaScript顯示歡迎訊息:
@page "/"
@rendermode InteractiveServer
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
@implements IAsyncDisposable
<PageTitle>Home</PageTitle>
<div class="mt-4">
<label for="nameInput" class="form-label">Name</label>
<input id="nameInput" class="form-control" @bind="_name" @bind:event="oninput" />
<button class="btn btn-primary mt-2" @onclick="InvokeSayHiAsync">Say Hi</button>
</div>
@code {
// 儲存預設名稱,初始值為 `Blazor`。
private string _name = "Blazor";
// 儲存匯入後的 JavaScript 模組參考。
private IJSObjectReference?
_module;
// 儲存透過建構函式建立的 `HiHelper`
物件參考。
private IJSObjectReference?
_hiHelper;
private async Task InvokeSayHiAsync() {
// 如果名稱為空白,則設定成預設值
`Blazor`。
if (string.IsNullOrWhiteSpace(_name)) {
_name = "Blazor";
}
// 如果模組尚未載入,則先匯入
`sayhi.js`。
_module ??= await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/sayhi.js");
// 如果 `HiHelper` 尚未建立,則建立
JavaScript 物件。
_hiHelper ??= await _module.InvokeConstructorAsync("HiHelper");
// 呼叫 JavaScript 的
`SayHi` 方法並傳入名稱。
await _hiHelper!.InvokeVoidAsync("SayHi", _name);
}
// 元件釋放時一併釋放 JavaScript 資源。
public async ValueTask DisposeAsync() {
// 如果 `HiHelper` 已建立,則釋放該物件。
if (_hiHelper is not null) {
await _hiHelper.DisposeAsync();
}
// 如果模組已載入,則釋放模組參考。
if (_module is not null) {
await _module.DisposeAsync();
}
}
}
這個範例執行的結果,請參考下圖所示:
圖 9:呼叫JavaScript範例
讀取與修改物件屬性
(Property Access)
以前存取物件屬性可能需要額外寫
JavaScript 輔助函式,現在你可以直接從
C# 讀取或設定 JavaScript 物件的屬性(包含資料屬性與存取子屬性),而不需要額外撰寫 JavaScript 輔助函式:
● 讀取屬性:使用「GetValueAsync<TValue>("propertyName")」方法可以直接以非同步方式讀取屬性值。
● 設定屬性:使用「SetValueAsync<TValue>("propertyName",
value) 」方法可以非同步方式更新屬性值;如果目標物件上尚未定義該屬性,甚至會自動建立該屬性。
我們將上個「sayhi.js」例子的程式改寫如下,加入「name」屬性:
// 匯出 `HiHelper` 類別供
Blazor 匯入使用。
export class HiHelper {
// 建構式會在建立物件時執行。
constructor() {
// 設定 `name` 屬性的預設值為 `Blazor`。
this.name = "Blazor";
}
// 顯示打招呼訊息。
SayHi(name) {
// 如果傳入的是字串就去除前後空白,否則使用空字串。
const displayName = typeof name === "string" ? name.trim() : "";
// 如果有有效輸入名稱就使用它,否則改用物件上的 `name` 屬性。
const finalName = displayName.length > 0 ? displayName : this.name;
// 跳出提示視窗顯示問候訊息。
alert(`Hi, ${finalName}`);
}
}
這時只要修改Blazor元件的程式碼如下,讀寫JavaScript 物件的 「name」 屬性以組合出歡迎訊息,執行元件會得到和上例一樣的結果:
@page "/"
@rendermode InteractiveServer
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
@implements IAsyncDisposable
<PageTitle>Home</PageTitle>
<div class="mt-4">
<label for="nameInput" class="form-label">Name</label>
<input id="nameInput" class="form-control" @bind="_name" @bind:event="oninput" />
<button class="btn btn-primary mt-2" @onclick="InvokeSayHiAsync">Say Hi</button>
</div>
@code {
// 儲存文字方塊中的名稱。
private string? _name;
// 儲存匯入後的 JavaScript 模組參考。
private IJSObjectReference?
_module;
// 儲存 JavaScript
`HiHelper` 物件參考。
private IJSObjectReference?
_hiHelper;
private async Task InvokeSayHiAsync() {
// 如果模組尚未載入,則先匯入
`sayhi.js`。
_module ??= await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/sayhi.js");
// 如果 `HiHelper` 尚未建立,則透過建構式建立物件。
_hiHelper ??= await _module.InvokeConstructorAsync("HiHelper");
// 當文字方塊有值時,將名稱寫到
JavaScript 物件的 `name` 屬性。
if (!string.IsNullOrWhiteSpace(_name)) {
await _hiHelper!.SetValueAsync("name", _name.Trim());
}
// 從 JavaScript 物件的
`name` 屬性讀取目前名稱。
var currentName = await _hiHelper.GetValueAsync<string>("name");
// 呼叫 JavaScript 的
`SayHi` 方法顯示訊息。
await _hiHelper!.InvokeVoidAsync("SayHi", currentName);
}
// 元件釋放時同步釋放 JavaScript 物件資源。
public async ValueTask DisposeAsync() {
// 如果 `HiHelper` 已建立,則釋放它。
if (_hiHelper is not null) {
await _hiHelper.DisposeAsync();
}
// 如果模組已載入,則釋放它。
if (_module is not null) {
await _module.DisposeAsync();
}
}
}
使用「IJSInProcessObjectReference」型別
「IJSInProcessObjectReference」是一種物件參考型別,可以用同步或非同步方式呼叫JavaScript,但它只適合在 Blazor WebAssembly專案之中使用。
「IJSInProcessObjectReference」針對WebAssembly 的同步執行進行了效能最佳化,很適用於注重效能的WebAssembly程式碼。
若把上例的情境搬到WebAssembly專案,在專案中加入以下「sayhi.js」:
// 匯出 `HiHelper` 類別供
Blazor 匯入使用。
export class HiHelper {
// 建構式會在建立物件時執行。
constructor() {
// 設定 `name` 屬性的預設值為 `Blazor`。
this.name = "Blazor";
}
// 顯示打招呼訊息。
SayHi(name) {
// 如果傳入的是字串就去除前後空白,否則使用空字串。
const displayName = typeof name === "string" ? name.trim() : "";
// 如果有有效輸入名稱就使用它,否則改用物件上的 `name` 屬性。
const finalName = displayName.length > 0 ? displayName : this.name;
// 跳出提示視窗顯示問候訊息。
alert(`Hi, ${finalName}`);
}
}
export function createHiHelper() {
// 建立並回傳一個新的 `HiHelper` 物件實例。
return new HiHelper();
}
在首頁「Home.razor」加入以下程式碼,內嵌「HomeInteractive」元件,設定「rendermode」為「InteractiveWebAssembly」:
@page "/"
<PageTitle>Home</PageTitle>
<BlazorAppWASM.Client.Pages.HomeInteractive @rendermode=" " />
在Client專案中加入「HomeInteractive.razor」,並在其中加入叫用JavaScript的程式碼,這樣執行程式就會得到和上例一樣的結果:
@implements IDisposable
@inject IJSRuntime JS
<div class="mt-4">
<label for="nameInput" class="form-label">Name</label>
<input id="nameInput" class="form-control" @bind="name" />
<button class="btn btn-primary mt-2" @onclick="SayHi">Say Hi</button>
</div>
@code {
// 儲存使用者在文字方塊中輸入的名稱。
private string? name;
// 儲存已匯入的 JS 模組參考,並支援同步呼叫。
private IJSInProcessObjectReference? module;
// 儲存 `HiHelper`
JavaScript 物件參考,並支援同步呼叫。
private IJSInProcessObjectReference? hiHelper;
private void SayHi()
{
// 如果 `hiHelper` 已建立,同步執行
JS 的 `SayHi` 方法,就將目前名稱傳到方法顯示問候訊息。
hiHelper?.InvokeVoid("SayHi", name);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// 如果不是首次渲染,直接結束,避免重複初始化。
if (!firstRender)
{
return;
}
// 非同步匯入 `/js/sayhi.js` 模組。
var jsModule = await JS.InvokeAsync<IJSObjectReference>("import", "/js/sayhi.js");
// 確認匯入後的模組是否支援同步呼叫介面。
if (jsModule is not IJSInProcessObjectReference inProcessModule)
{
// 若不支援同步呼叫,拋出例外說明目前範例需求。
throw new InvalidOperationException("This
component requires IJSInProcessObjectReference.");
}
// 將可同步呼叫的模組參考存入欄位供後續重複使用。
module = inProcessModule;
// 透過模組中的工廠函式建立
`HiHelper` 物件參考。
hiHelper = module.Invoke<IJSInProcessObjectReference>("createHiHelper");
}
// 在元件釋放時一併釋放已建立的 JS 物件參考。
public void Dispose()
{
// 釋放 `HiHelper` 物件參考。
hiHelper?.Dispose();
// 釋放 JS 模組參考。
module?.Dispose();
}
}
總結
整體而言,.NET 10 讓 Blazor 在安全驗證與 JavaScript 整合能力上更進一步。內建的 Passkey支援 讓 ASP.NET Core Identity 能輕鬆實作無密碼登入機制,不僅提升使用者體驗,也能有效降低釣魚攻擊風險。同時,改進後的 JavaScript Interop 支援直接建立 JavaScript 物件、讀寫物件屬性,並在 WebAssembly 環境中提供同步呼叫的最佳化機制。這些新功能讓 Blazor 在建構現代 Web 應用時更具彈性與效率,也進一步強化了 .NET 在全端開發上的整體競爭力。

0 意見:
張貼留言