Blazor 3 使用事件聚合器來動態顯示或者隱藏控制練習
在 .NET C# 開發環境下,想要完成這樣的需求,可以使用 委派 Delegate 或者 事件 Event,然而對於這兩個技術想要在 Blazor 開發環境下來使用,讓不同的階層的 元件 可以彼此溝通訊息,對於 委派 而言,可以透過元件參數傳遞委派到子元件內,而對於事件而言,開發者必須宣告一個靜態事件,透過這樣全域靜態方法,讓各個子元件可以訂閱與觸發特定的事件,但是,這樣的設計方式也是非常的不好,因為這個靜態事件會存在於整個應用程式生命週期內,若有個子元件訂閱了這個事件,並且該子元件不再使用到了,該子元件必須要解除訂閱這個事件,否則,該子元件物件是沒有辦法被 .NET CLR 資源回收 Garbage Collection GC (關於 GC 的內容,可以參考 記憶體回收的基本概念 ),並且會有記憶體洩漏 Memory Leak 的嚴重問題產生。
在這篇文章將會使用 事件聚合器 Event Aggregator 這個設計模式,提供各 子元件 間的通訊通知之用,在事件聚合器設計模式下,提供了 訂閱 Subscribe 事件與 發布 Publish 事件兩個方法,當使用者執行了 發布 特定事件的時候,所有 訂閱 該特定事件的物件,就可以執行其對應的程式碼。不過,在這裡將不會說明如何從無到有的設計出 事件聚合器 這樣的設計模式程式碼,因為,我們不需要為了要使用輪子,而去發明另外一個輪子;在這裡將會從 Prism 這個開發框架內,抽取出該 Prism 所設計的 事件聚合器程式碼,並且在這個範例專案中來使用。
使用 Prism 所提供的 事件聚合器 程式碼的好處是:可以使用相依性注入的方式來注入 IEventAggregator 的具體實作物件、對於所綁定特定事件的訂閱方法,採用的 弱式參考 WeakReference 的方式來訂閱,因此,當某個 子元件 在 .NET 環境下不被在使用的時候,不會因為該 子元件 內還有訂閱 事件聚合器 內的某個事件,而導致該 子元件 物件不會被 .NET CLR 資源回收 Garbage Collection GC ,這是因為採用的 弱式參考 架構來訂閱事件,所以,不再使用到的 子元件 還是會被 .NET CLR 資源回收 Garbage Collection 機制進行從記憶體內回收該物件所佔用的記憶體空間。
這個說明範例將會修改 Blazor 專案範本建立的專案,修正該網頁的最上方會有一個 Login 按鈕,當使用者在 Counter 元件中點選按鈕,且當時的計數器值為偶數的時候,Login 按鈕會隱藏起來,而 Logout 按鈕會顯示出來,反之若當時計數器值為奇數的時候,Login 按鈕便會顯示出來,Logout 按鈕就會隱藏起來;而在這裏的顯示與隱藏功能,將會使用 Bootstrap 4 內宣告的 collapse 來實作出來, 本篇文章的範例原始碼,可以從 BlazorEventAggregator 取得。
建立測試專案
現在可以開始建立第一個 Blazor 開發專案。
- 啟動 Visual Studio 2017
- 點選功能表的 [檔案] > [新增] > [專案] 功能表選項
- 在 [新增專案] 對話窗中左邊區域,選擇 [已安裝] > [Visual C#] > [Web] > [ASP.NET Core Web 應用程式]
- 在對話窗的下方的 [名稱] 欄位中,輸入這個練習專案的名稱 BlazorEventAggregator
- 在對話窗的下方的 [位置] 欄位中,選擇這個專案要儲存的檔案路徑
- 最後點選對話窗的右下方 [確定] 按鈕
- 在 [新增 ASP.NET Core Web 應用程式] 對話窗左上方區域,在第一個下拉選單,選擇 [.NET Core] 在第二個下拉選單,請選擇最新的 ASP.NET Core 版本,在現在這個時間點,可以選擇 [ASP.NET Core 2.1]
- 在對話窗中間區域,請點選 [Blazor] 這個項目
- 最後點選對話窗的右下方 [確定] 按鈕
- 使用滑鼠右擊在最上方的方案節點,選擇 [加入] > [新增專案]
- 在 [新增專案] 對話窗中,點選左方的 [已安裝] > [Visual C#]
- 接著,在對話窗的中間,點選 [類別庫 .NET Standard]
- 在對話窗下方的名稱欄位,輸入 EventAggreators ,最後點選 [確定] 按鈕,完成建立這個 .NET Standard 類別庫
- 請將 BlazorEventAggregator 這個 Github Repository 內專案名稱為 EventAggreators 下的 Events 目錄,複製到剛剛建立的 EventAggreators 專案下。
- 滑鼠右擊 BlazorEventAggregator 專案內的 [相依性] 節點,選擇 [加入參考]
- 當 [參考管理員] 對話窗出現之後,點選左方的 [專案] 節點,在勾選中間的 EventAggregators 組件名稱
- 點選 [確定] 按鈕
- 底下是完成後的專案架構
進行 Prism 事件聚合器的 DI 容器註冊
當使用 事件聚合器 的時候,在這個專案內需要有個全域的 事件聚合器 物件,這樣才能夠讓各個 子元件 來進行訂閱特定事件的需求;在 Blazor 專案內,將會使用 IoC 容器 Container 機制進行 事件聚合器 介面與具體實作類別的註冊,而在每個 子元件 若要透過 事件聚合器 進行特定事件的訂閱或者發布的時候,就可以注入 IEventAggregator 這個介面,便可以取得 Prism 事件聚合器 的實作物件。
- 打開專案內的 [StartUp.cs] 檔案
- 找到 [ConfigureServices] 方法,使用 AddSingleton 方法來進行註冊,這裡可以使用
services.AddSingleton<IEventAggregator, EventAggregator>();
這個方法。
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IEventAggregator, EventAggregator>();
}
public void Configure(IBlazorApplicationBuilder app)
{
app.AddComponent<App>("app");
}
}
建立 事件聚合器 會用到的 事件資料模型
接下來要建立兩個 事件聚合器 的事件 ShowLoginEvent 與 ShowLogoutEvent 兩個類別,這兩個類別都需要繼承 PubSubEvent 這個類別,其中泛型型別的 T 為該特定事件發布與觸發的時候,所要傳送與接收的資料型別。
- 滑鼠右擊 BlazorEventAggregator 專案節點,選擇 [加入] > [新增資料夾],並且輸入 [Models] 資料夾名稱
- 建立 ShowLoginEvent 事件類別
- 滑鼠右擊 [Models] 資料夾節點,選擇 [加入] > [類別]
- 在 [新增項目] 對話窗中,點選 [已安裝] > [ASP.NET Core] > [密碼] > [類別] 選項,接著,在下方名稱欄位,輸入 ShowLoginEvent.cs ,最後點選右下方的 [新增] 按鈕
- 將底下程式碼輸入到剛剛建立的 ShowLoginEvent 類別內
Models > ShowLoginEvent.cs
public class ShowLoginEvent : PubSubEvent<ShowLoginEventPayload>
{
}
public class ShowLoginEventPayload
{
public bool IsShow { get; set; }
}
- 建立 ShowLogoutEvent 事件類別
- 滑鼠右擊 [Models] 資料夾節點,選擇 [加入] > [類別]
- 在 [新增項目] 對話窗中,點選 [已安裝] > [ASP.NET Core] > [密碼] > [類別] 選項,接著,在下方名稱欄位,輸入 ShowLogoutEvent.cs ,最後點選右下方的 [新增] 按鈕
- 將底下程式碼輸入到剛剛建立的 ShowLogoutEvent 類別內
Models > ShowLogoutEvent.cs
public class ShowLogoutEvent : PubSubEvent<ShowLogoutEventPayload>
{
}
public class ShowLogoutEventPayload
{
public bool IsShow { get; set; }
}
修正網頁共用版型元件
在這裡將會要在網頁的最上方加入兩個按鈕: Login 與 Logout ,整個網頁在一啟動的時候,將只會顯示 Login 按鈕,而 Logout 按鈕將會收起隱藏起來,這裡將會透過 單向資料綁定 功能,在這裡兩個按鈕上分別使用
@ShowLogin
與 @ShowLogout
在 class 屬性上,進而來綁定 .NET C# 中的 ShowLogin / ShowLogout 這兩個變數。因此,當想要顯示某個 DOM 項目的時候,僅需要把特定變數的字串值設定為空字串,若要隱藏某個 DOM 項目 Element 的時候,便可以設定特定變數的字串值為 collapse。
那麼,要在哪個地方來進行變更特定 DOM 項目的顯示與隱藏的變數值設定呢?此時,先要宣告這個 元件 需要自動注入 IEventAggregator 這個 事件聚合器 物件,因此,在最上方使用
@inject IEventAggregator eventAggregator
陳述式,這樣,在這個元件內就可以使用 eventAggregator 這個變數來操作 Prism 的事件聚合器;而在接下來的設計過程,將會需要使用到剛剛建立的兩個事件類別,因此,使用 @using BlazorEventAggregator.Models
陳述式,宣告這個元件可以使用 BlazorEventAggregator.Models 命名空間內的型別。
最後,在這個元件內需要進行 事件 訂閱 的設計,也就是當其他元件要把登入按鈕隱藏起來,並且顯示登出按鈕,在這個訂閱事件內便可以處理這些需求,那麼,該在哪裡設計這樣的需球,這個時候需要 覆寫 override OnInit 這個方法,並且在這個方法內進行使用 事件聚合器 來訂閱特定事件的委派方法。
當要使用事件聚合器來訂閱特定事件,可以使用事件聚合器物件的 GetEvent 泛型方法,取得 特定事件型別,接著就可以使用 Subscribe 這個方法來指定一個委派方法,例如,
eventAggregator.GetEvent<ShowLoginEvent>().Subscribe
這個敘述,將會使用事件聚合器物件,來進行 ShowLoginEvent 事件的訂閱。
最後,就可以在委派的訂閱事件方法內,根據使用者使用 Publish 方法所傳送過來的狀態物件,進行設定 ShowLogin 或者 ShowLogout 變數值,不過,最後一定需要執行
base.StateHasChanged();
方法,這樣 ShowLogin 或者 ShowLogout 變數值 就會透單向資料綁定更新到網頁的 DOM 上了。關於更多關於 StateHasChanged 的說明,可以參考 Class BlazorComponent
現在,請依照底下說明來修正專案程式碼
- 請打開 [Shared] 資料夾內的 [MainLayout.cshtml] 檔案
- 使用底下程式碼替換掉 [MainLayout.cshtml] 檔案內容
Shared > MainLayout.cshtml
@inherits BlazorLayoutComponent
@using Prism.Events
@using BlazorEventAggregator.Models
@inject IEventAggregator eventAggregator
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-8">
<button class="btn btn-primary @ShowLogin">Login</button>
<button class="btn btn-warning @ShowLogout">Logout</button>
<a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
@functions
{
string ShowLogin = "";
string ShowLogout = "collapse";
protected override void OnInit()
{
eventAggregator.GetEvent<ShowLoginEvent>().Subscribe(x =>
{
if (x.IsShow == true)
{
ShowLogin = "";
}
else
{
ShowLogin = "collapse";
}
base.StateHasChanged();
});
eventAggregator.GetEvent<ShowLogoutEvent>().Subscribe(x =>
{
if (x.IsShow == true)
{
ShowLogout = "";
}
else
{
ShowLogout = "collapse";
}
});
base.StateHasChanged();
}
}
修正 Counter 元件,發佈特定事件通知
現在將會要來修正 Counter 元件,當使用者在 Counter 元件中點選按鈕,且當時的計數器值為偶數的時候,Login 按鈕會隱藏起來,而 Logout 按鈕會顯示出來,反之若當時計數器值為奇數的時候,Login 按鈕便會顯示出來,Logout 按鈕就會隱藏起來;而在這裏的顯示與隱藏功能。現在,先在該元件的最上方加入
@inject IEventAggregator eventAggregator
表示要注入 事件聚合器 物件與 @using BlazorEventAggregator.Models
表示要參考 BlazorEventAggregator.Models 命名空間。
在按鈕事件 IncrementCount 中,判斷 currentCount 這個整數變數值為單數或者是偶數,接著可以使用
eventAggregator.GetEvent<ShowLoginEvent>().Publish
與 eventAggregator.GetEvent<ShowLogoutEvent>().Publish
方法,分別對 事件聚合器 物件送出兩個事件通知,這樣,只要有透過該 事件聚合器 物件訂閱的方法,就會被觸發執行了。
現在,請依照底下說明來修正專案程式碼
- 請打開 [Pages] 資料夾內的 [Counter.cshtml] 檔案
- 使用底下程式碼替換掉 [Counter.cshtml] 檔案內容
Pages > Counter.cshtml
@page "/counter"
@using Prism.Events
@using BlazorEventAggregator.Models
@inject IEventAggregator eventAggregator
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>
@functions {
int currentCount = 0;
void IncrementCount()
{
currentCount++;
if (currentCount % 2 == 0)
{
eventAggregator.GetEvent<ShowLoginEvent>().Publish(new ShowLoginEventPayload()
{
IsShow = true
});
eventAggregator.GetEvent<ShowLogoutEvent>().Publish(new ShowLogoutEventPayload()
{
IsShow = false
});
}
else
{
eventAggregator.GetEvent<ShowLoginEvent>().Publish(new ShowLoginEventPayload()
{
IsShow = false
});
eventAggregator.GetEvent<ShowLogoutEvent>().Publish(new ShowLogoutEventPayload()
{
IsShow = true
});
}
}
}
執行結果
現在,請執行這個專案,點選左邊側邊攔的 Counter 項目,接著在網頁中間來點選 Click me 這個按鈕,現在將會發現到,若 currentCount 這個變數為偶數的時候,上方的僅會出現 Login 按鈕
反之,若 currentCount 這個變數為奇數的時候,上方的僅會出現 Logout 按鈕
更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。
當在使用 Blazor 開發框架進行開發的時候,將會設計許多的 元件 Component ,也就是網頁中的特定 UI 畫面,藉由組合這些 元件 就會形成整個網頁內容;然而,有些時候將會需要在某個 元件A 中進行操作的時候,想要變更其他 元件B 的顯示狀態,也許 元件B 是 元件A 的 父元件或者是 子元件,這個時候將會有幾個技術可以選擇。第一個就是透過 JavaScript 來變更網頁上的 DOM 項目 Element 的 屬相 Attribut 或者變更其 CSS 設定;另外就是直接使用 .NET C# 中的技術,不去使用 JavaScript 程式語言,這樣對於 .NET C# 的開發者而言,可以更佳的輕鬆、自在的使用自己所孰悉的技術。