2019年12月29日 星期日

使用 Server Side Blazor 開發真實專案的經驗分享與感想


教育訓練課程小幫手

更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。


這是一個使用 Blazor 開發出來的專案,提供輔助教育訓練課程之用

相關文章


最近正在進行 Microsoft 微軟開發出來的新一代 Web Framework 開發框架,那就是 Blazor,在這個時間點,微軟僅推出 Blazor Server Side 伺服器端 的開發框架工具,至於 WebAssembly 的用戶端工具,需要等到 2020 年之後才會推出;從微軟官方的文件中,可以看到 Blazor 會有幾個功能:裝載模型、範本、元件、生命週期、表單和驗證、元件程式庫、版面配置、相依插入、路由、JavaScript Interop、安全性及身分識別、狀態管理、錯誤處理、偵錯、呼叫 Web API 等技術。
作者本身並不具備網站開發能力,也就是說,我本身並沒有實際開發過一個網站專案程式,我的技術背景是從事於行動裝置應用程式 App 的開發,原則上,我都會使用 .NET Framework 平台與 C# 程式語言作為開發技術首選,當然,我自己對於 Xamarin.Forms 這個跨平台開發工具十分的孰悉;不過,我對於 HTML / CSS / Bootstrap / JavaScript 僅具有基本的認識,然而,因為沒有實際的網站開發實戰經驗,所以,這些 Web 開發技術通常僅是有看過相關文件與資料而已,若真的需要實際使用這些工具來開發網站專案,真正的結果只有不斷的撞牆、碰壁、上網找解決方案、不斷的 Try Error 而言。
所以,在 2018 年的時候,我看到了 Blazor 這個開發技術,我的眼睛為之一亮,這不是我正要找尋的開發技術嗎?(雖然,之前有嘗試要使用 XAML 相關的網站開發技術來進行網頁開發,不過,總是會有許多不安的感覺),因為,想要透過 Blazor 這套開發框架來進行網站開發,僅需要精通 C# 程式語言、HTML 宣告標記語言與 Bootstrap 的使用方式,這樣就夠了,可是,這樣真的就已經足夠了嗎?那時,心中存在著滿滿的懷疑。不過,我還是在 Blazor 還是實驗性版本的時候,嘗試了幾個開發技術,整體開發感覺上覺得驚豔,因為,我不再需要去學 JavaScript 程式語言,也不要擔心要選擇哪套 JavaScript 開發框架: jQuery, Angular , Reac , Vue 等等,也不再需要去擔心繁雜的 Web 開發工具的使用方式,我都可以把它拋諸腦後。
可是,事實真的是正樣嗎?不過,等待還是有價值的,在 2019 年 9 月,微軟正式推出 伺服器端的 Blazor 開發框架工具,我便開始想要來進行網站開發實戰體驗,那麼,到底要選擇甚麼樣的題目來做為專案開發的標的呢?因為這幾年都在從事於 Xamarin.Forms 的教育訓練工作,通常在上課的時候,會想要提供上課學員一個好的學習體驗,那個時候就想要設計一個 教育訓練課程小幫手 這樣的應用程式,我想過要使用 Xamarin.Forms 來開發,可是,畢竟,開發成為 Web 類型的專案,更適合於上課學員的使用,因此,我就決定要開發出一套 教育訓練課程小幫手 網站應用程式。
這套 教育訓練課程小幫手 應用程式要具備後台專案的 CRUD Create Retrive Update Delete 新增 查詢 修改 刪除的功能開發、分頁顯示、也要將輸入的資料寫入到資料庫內、當要輸入資料的時候,可以使用彈出對話窗 Modal Dialog 讓使用者輸入資料、使用者可以進行登入/登出的身分驗證操作、尚未登入的使用者無法看到需要授權的網頁、已經登入的使用者僅能夠看到這個帳號授權可以看到的網頁、對於某些網頁而言,不同登入成功的帳號,將會看到不同的畫面內容、無需使用 JavaScript 與其相關 JavaScript Framework。我想,這樣的目標設定若能夠使用 Blazor 這套工具開發出來,我想, Blazor 這套工具,對於 .NET C# 開發者而言,應該是足以適任作為網站開發首選工具,因為,開發網站的時候,終於可以與 JavaScript 說聲掰掰了。
底下是這套 教育訓練課程小幫手 專案,佈署在 Azure 平台上的執行結果截圖
Blazor Project Course Assistant

身分驗證與授權

這個畫面為沒有任何使用登入的狀態下,在網頁左邊的功能表,僅會看到首頁這個功能選項,現在可以在網頁上方輸入帳號與密碼,接著點選 Login 按鈕,進行登入作業。
底下截圖為管理者身分的使用者成功登入後的畫面。
管理者身分的使用者成功登入後的畫面
當管理者角色帳號成功驗證之後,將會看到所有前台與後台的相關功能(在網頁的最左邊)
底下截圖為一般使用者身分的使用者成功登入後的畫面。
一般使用者身分的使用者成功登入後的畫面
當一般角色帳號成功驗證之後,將會看到所有前台的相關功能(在網頁的最左邊)
就算這個時候,使用者以一般身分使用者進行登入,自行輸入 https://courseassistant.azurewebsites.net/Courses 或者 https://courseassistant.azurewebsites.net/Users 網址,將會出現底下畫面截圖
存取沒有授權的網頁畫面

CRUD 作業

這裡將會使用 課程維護 這個功能作為展示說明
首先是查詢,只要進入到 課程維護 功能後,就會看到底下畫面
CRUD - Retrive 查詢功能
當資料透過 ASP.NET Core 內設計的相依插入服務功能,取得資料之後,就會透過 Blazor 的雙向資料綁定機制,將這些資料綁定到 HTML Table 相關標記上。
在這個應用程式也有設計分頁機制,當所要顯示的紀錄數量超過每頁數量設定,就會以分頁方式來顯示,分頁元件將會顯示在 右上方。
當點選 新增 或者 修改 按鈕,此時,就會彈出一個對話窗 Modal Dialog,可以讓使用者輸入資料,如底下截圖
CRUD - Create / Update 新增與修改功能
在該對話窗內所顯示的內容,將會使用 Blazor 元件這項功能,將這些 UI & 程式碼處理邏輯都一個 Component 元件內,如此,便可以達到重複使用目的
現在可以點選 刪除 按鈕,同樣會彈出一個對話窗,如下圖
CRUD - Delete 刪除功能
這裡將會詢問使用者是否要刪除這筆紀錄,使用者可以點選 取消 按鈕,或者點選螢幕上灰色區域,便可以取消此次刪除動作請求。
對於 CUD 新增 修改 刪除 這三個動作,當完成或者取消的時候,都會在螢幕的右下方看到彈出快顯通知視窗,如下圖
CRUD - 快顯通知視窗

設定綁定使用者的彈出視窗功能

這個系統,可以設定使用者能夠看到或者參與那些課程,延續上面的課程維護畫面,每筆課程紀錄的右邊,將會看到一個使用者按鈕,按鈕文字可以看出這個課程有多少使用者可以使用;因此,當點選這個按鈕之後,便會取出這個系統內的所有使用者,顯示在對話窗上,如下圖
選取可以參與的使用者

依照使用者顯示能看到的資料

這樣的需求也是經常出現在一般專案設計上,因此,在這裡切換另外一個使用者 test 這個使用者僅能夠看到兩個課程,如下圖
test 使用者看到的課程清單
為了展示這項功能,所以又再切換另外一個使用者 u1 這個使用者僅能夠看到三個課程,如下圖
u1 使用者看到的課程清單

問題提出與回答

當要提出問題與回答問題的時候,需要在課程清單畫面上的某個課程紀錄右方的 問答 按鈕,把它點選下去,就會出現底下畫面
選取可以參與的使用者
要注意的是,這個畫面沒有專屬 URL 可以直接進入,一定需要先從課程清單頁面看到可以參加的課程,才能夠進入到這裡,這樣對於有些需要適度安全保護的設計需求,是可以提供不錯的選擇。
在這裡,會判斷該問題是否已經回答過了,而顯示出不同的圖示與按鈕。
一般使用者,僅能夠提出問題與修改和刪除問題,回答問題的部分,需要具有管理者權限的使用者,才會看到,如下圖
選取可以參與的使用者
要在 Blazor 系統上實作出訊息廣播系統,也是相當容易的
網站層面的訊息廣播功能
只要使用者提出一個問題,所有開啟這個網站的使用者,就算沒有登入進來,都可以收到一個新訊息已經產生的快顯通知 Toast。

結論

當這個專案開發完成之後,我遇到了這些問題
  • CSS / Bootstrap 不孰悉
    對於這樣的需求,通常是上網搜尋,看看能否有解決方案,不過,遇到這樣的問題,大多使版面排版上的問題。
  • 第一次使用 Blazor 開發
    由於,這是我第一次使用 Blazor 來開發專案,因為,整個過程都在摸索一個制式或者標準的 CRUD 開發過程與模式,這裡也著實花費了不少時間,不過,一旦對於一個 CRUD 開發樣板模式準備好的話,要開發出其他資料表的 CRUD 需求,明顯的加快了許多
  • 體驗第三方 Blazor 套件
    這個專案開發過程,我所寫的程式碼完全沒有直接呼叫到 JavaScript 程式碼,那麼,對於對話窗的彈出這樣的功能,又是如何做到的呢?這裡會有幾種作法,不過,我選擇的是使用別人包裝好的 Blazor 套件,就可以做到了。
  • 對於 ASP.NET Core 3.1 平台孰悉度
    對於許多功能,都會直接使用 ASP.NET Core 平台上會用到的技術,例如:相依注入、抽象介面與具體服務實作的設計、資料庫存取使用到 Entity Framework Core、身分驗證則是自己來設計;對於這些功能的孰悉程度,將會影響到您的專案開發完成的時間
最後,我個人認為,Blazor 現階段已經開發出實用的 Web 專案,而且對於整體開發時間花費、開發過程的愉悅體驗,都是呈現極高的評價,我想,我將會開始使用 Blazor 來進行我夢想的網站開發了。






ASP.NET Core Blazor元件中的參數傳遞與值變化

ASP.NET Core Blazor元件中的參數傳遞與值變化

更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。


在這篇關於 Blazor 的參數傳遞與使用變化上的文章,將會進行自我解惑相關問題,所以,將會需要建立一個測試專案,將相關問題在這個專案上實際跑看看,以便知道實際執行結果。
也就是說,若一個參數傳遞某個元件A,且該元件A內部也使用了元件B,而元件B也會接收到來自元件A相同的參數,現在的問題是,要顯示元件A的 Blazor 元件,變更了傳遞參數物件的值,對於元件A與元件B內的參數物件,會有甚麼變化呢?另外,若在元件A中變更了參數物件值,對於元件B與要顯示元件A的那個元件,其參數物件又會有甚麼樣子的變化呢?
首先,建立 Blazr 伺服器端的專案範本來建立一個專案,接著,建立一個方案資料夾,並且在這個資料夾內,建立起四個元件,如下圖
對於 HighLevelComponent [高階元件] 這個元件,將會接收一個參數,並且將這個參數顯示在螢幕上
而對於 HighLevelComponentWithInput [高階元件有 input index] 這個元件,將會接收一個參數,並且將這個參數顯示在螢幕上,而且,還會有一個 input 標籤,透過雙向綁定將這個傳遞進來的參數綁定再一起。
對於 LowLevelComponent [低階元件] 這個元件,將會接收一個參數,並且將這個參數顯示在螢幕上,這個元件除了可以在 index.razor 首頁頁面顯示之外,也會用於底下的 [高階元件有 input index] 這個元件內,以便觀察當在傳遞進來的 Index 有變化的時候,會有甚麼影響。
而對於 HighLevelComponentWithLow [高階元件自我包含低階元件] 這個元件,將會接收一個參數,並且將這個參數顯示在螢幕上,而且,還會顯示 LowLevelComponent [低階元件] 這個元件,當然,也會將同樣的參數傳遞到 [低階元件] 這個元件。
並且將這四個元件,實際用於這個專案的 index.razor 元件中,底下是執行效果
在這篇文章所提到的專案原始碼,可以從 GitHub 下載

HighLevelComponent [高階元件] 這個元件

底下是這個元件的 Blazor 程式碼,可以看到這裡使用了 [Parameter] 這個 C# Attribute 屬性,宣告 HighLevelMessage 這個字串變數為一個參數變數,也就是,這個物件值,可以透過 Blazor 參數傳遞的方式進行變更。
另外,這個 HighLevelMessage 參數也會透過單向綁定方式,顯示在 HTML 頁面上
<div class="card">
    <div class="card-body">
        <h5 class="card-title">高階元件</h5>
        <p class="card-text">@HighLevelMessage</p>
    </div>
</div>

@code {
    [Parameter]
    public string HighLevelMessage { get; set; } = "Higher";
}

HighLevelComponentWithInput [高階元件有 input index] 這個元件

底下是這個元件的 Blazor 程式碼,可以看到這裡使用了 [Parameter] 這個 C# Attribute 屬性,宣告 HighLevelComponentWithInputMessage 這個字串變數為一個參數變數,也就是,這個物件值,可以透過 Blazor 參數傳遞的方式進行變更。
另外,這個 HighLevelComponentWithInputMessage 參數也會透過單向綁定方式,顯示在 HTML 頁面上,而且,這個 HighLevelComponentWithInputMessage 參數,也會透過雙向資料綁定的方式,綁定到 input 這個標記上(使用 @bind-value="HighLevelComponentWithInputMessage")
<div class="card">
    <div class="card-body">
        <h5 class="card-title">高階元件有 input</h5>
        <p class="card-text">@HighLevelComponentWithInputMessage</p>
        <div class="card-text m-3">
            <input @bind-value="HighLevelComponentWithInputMessage" @bind-value:event="oninput" />
        </div>
    </div>
</div>

@code {
    [Parameter]
    public string HighLevelComponentWithInputMessage { get; set; } = "Higher";
}

LowLevelComponent [低階元件] 這個元件

底下是這個元件的 Blazor 程式碼,可以看到這裡使用了兩個 [Parameter] 這個 C# Attribute 屬性,宣告 LowLevelMessage 這個字串變數為一個參數變數與 BackgroundColor 這個字串變數,作為要設定用於 css 背景顏色的支用,也就是,這個物件值,可以透過 Blazor 參數傳遞的方式進行變更。
另外,這個 LowLevelMessage 參數也會透過單向綁定方式,顯示在 HTML 頁面上,這個 BackgroundColor 參數也會透過單向綁定方式,套用到 class 屬性的值上
<div class="card @BackgroundColor">
    <div class="card-body">
        <h5 class="card-title">低階元件</h5>
        <p class="card-text">@LowLevelMessage</p>
    </div>
</div>

@code {
    [Parameter]
    public string LowLevelMessage { get; set; } = "Lower";
    [Parameter]
    public string BackgroundColor { get; set; } = "bg-info";
}

HighLevelComponentWithLow [高階元件自我包含低階元件] 這個元件

底下是這個元件的 Blazor 程式碼,可以看到這裡使用了 [Parameter] 這個 C# Attribute 屬性,宣告 HighLevelWithLowMessage 這個字串變數為一個參數變數,也就是,這個物件值,可以透過 Blazor 參數傳遞的方式進行變更。
另外,這個 HighLevelMessage 參數也會透過單向綁定方式,顯示在 HTML 頁面上
不過,在這個元件內,也同時使用了 LowLevelComponent 這個元件,並且使用了 LowLevelMessage="@HighLevelWithLowMessage" BackgroundColor="bg-success" 語法,將引數傳遞到 LowLevelComponent 這個元件內。
<div class="bg-warning p-4">

    <div class="card">
        <div class="card-body">
            <h5 class="card-title">高階元件自我包含低階元件</h5>
            <p class="card-text">@HighLevelWithLowMessage</p>
        </div>
    </div>
    <LowLevelComponent LowLevelMessage="@HighLevelWithLowMessage"
                       BackgroundColor="bg-success" />
</div>

@code {
    [Parameter]
    public string HighLevelWithLowMessage { get; set; } = "Higher";
}

關於 index.razor 這個元件

現在要把剛剛設計的元件,全部整合在一起,在這裡,將會把這個專案範本內的 index.razor 元件內的宣告與程式碼,修改成為如下面列表
@page "/"

<h1 class="display-5 text-danger">Blazor元件中的參數傳遞與值變化</h1>

<div class="my-3">
    <input @bind-value="UserInput" @bind-value:event="oninput" />
</div>

<div class="card-columns">
    <HighLevelComponent HighLevelMessage="@UserInput" />
    <HighLevelComponentWithInput HighLevelComponentWithInputMessage="@UserInput"/>
    <LowLevelComponent LowLevelMessage="@UserInput" />
    <HighLevelComponentWithLow HighLevelWithLowMessage="@UserInput" />
</div>

@code{
    public string UserInput { get; set; } = "Index";
}

進行測試

現在,來看看所關心的問題之執行結果,首先,會看到執行結果的畫面如下圖
首先,將螢幕左上角的 Input 文字輸入盒內的 Index 文字,修改為 [第一次變更],此時,將會看到底下的效果
在這裡,所有接收到來自於 index.razor 內傳送過去的引數,都會完全變更了
現在,請修改左下角的 input 文字輸入盒,將文字輸入盒的內容修改成為 [第2次變更],此時,執行結果如下圖。
在這裡,將會看到,這樣的操作動作,並不會影響到其他的參數物件值,僅有自己會影響到。
最後,請修改左上角的 input 文字輸入盒,將內容修改為 [第三次變更],網頁內容將會變成底下結果
此時,所有的參數都受到了影響,全部變更成為這次修正的內容




2019年12月28日 星期六

Blazor 使用者身分驗證 之 登入與登出

ASP.NET Core Blazor 使用者身分驗證 之 登入與登出

更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。


在這篇文章中,將會說明如何不使用 ASP.NET Core 上的身分識別 功能 (ASP.NET Core 身分識別是支援使用者介面(UI)登入功能的 API。管理使用者、密碼、設定檔資料、角色、宣告、權杖、電子郵件確認等等。),來實作出登入/登出的效果,並且,也將會說明如何設計使用者有登入與沒有登入的時候,將會看到不同的網頁內容。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載

使用預設專案範本建立 Blazor 專案

想要進行這樣的專案開發練習,可以參考底下的操作步驟
  • 打開 Visual Studio 2019 開發工具
  • 當 [Visual Studio 2019] 對話窗出現之後,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗內,請找出 [Blazor 應用程式] 這個專案開發範本,並且點選這個專案開發範本
  • 請點選右下角 [下一步] 按鈕
  • 出現 [設定新的專案] 對話窗,輸入適當的 [專案名稱] 、 [位置] ,完成後,請點選右下角 [建立] 按鈕

    🕬 說明

    在這個範例程式碼中,將會建立一個 BlazorAuthentication 專案名稱
    #
  • 此時將會看到 [建立新的 Blazor 應用程式] 對話窗,這裡可以根據當時開發專案的需要,自行決定是否有調整 Blazor 專案的其他特性,若無,請點選右下角的 [建立] 按鈕
  • 此時,這個 Blazor 專案已經建立完成

🔑 注意事項

在建立練習專案的時候,無須在 [建立新的 Blazor 應用程式] 對話窗下,點選該對話窗右上方的 [驗證] 設定選項,也就是,維持 [驗證] 選項為 [無驗證]
#

加入本專案會用到的 NuGet 套件

  • 滑鼠右擊這個專案節點
  • 選擇 [管理 NuGet 套件] 選項
  • 點選 [瀏覽] 標籤頁次
  • 請記得在這裡一定要勾選 [包括搶鮮版] 選項
  • 從清單中找到 [Microsoft.AspNetCore.Blazor.HttpClient] 項目,請點選這個項目
  • 點選右上方的 [安裝] 按鈕
  • 完成這個 [Microsoft.AspNetCore.Blazor.HttpClient] NuGet 套件的安裝

建立相關方案資料夾

  • 滑鼠右擊這個專案節點
  • 選擇 [加入] > [新增資料夾] 選項
  • 使用 Controllers 名稱作為該方案資料夾的名稱

建立登入與登出的 Razor Page 頁面

建立登入 Razor Page 頁面

  • 滑鼠右擊這個專案節點內的 [Pages] 資料夾
  • 選擇 [加入] > [新增項目] 選項
  • 在 [新增項目] 對話窗內,選擇 [Razor 頁面] 選項
  • 在下方 [名稱] 欄位內,輸入 Login.cshtml
  • 點選 [新增] 按鈕,完成這個登入 Razor 頁面的建立
  • 打開 [Pages] 資料夾內的 [Login.cshtml] 檔案
  • 使用底下 Razor 標記語法替換掉這個檔案內的內容
@page
@model BlazorAuthentication.Pages.LoginModel
@{
    ViewData["Title"] = "登入";
}
<h2>登入</h2>
  • 在 [Pages] 資料夾內,找到 [Login.cshtml] 節點內的 [Login.cshtml.cs]
  • 使用滑鼠雙擊這個 [Login.cshtml.cs] 節點,打開這個檔案內容
  • 使用底下 C# 程式碼替換掉原先這個檔案內的內容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

#region 這裡將會是新加入的命名空間宣告
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
#endregion

namespace BlazorAuthentication.Pages
{
    [AllowAnonymous]
    public class LoginModel : PageModel
    {
        public string ReturnUrl { get; set; }
        public async Task<IActionResult> OnGetAsync(string paramUsername, string paramPassword)
        {
            string returnUrl = Url.Content("~/");
            try
            {
                // 清除已經存在的登入 Cookie 內容
                await HttpContext
                    .SignOutAsync(
                    CookieAuthenticationDefaults.AuthenticationScheme);
            }
            catch { }

            #region 這裡將會要針對傳入的使用者帳號與密碼進行驗證

            #region 本練習簡化將不做任何驗證,不過,本練習將簡化不做任何設計

            #endregion

            #region 加入這個使用者需要用到的 宣告類型 Claim Type
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, paramUsername),
                new Claim(ClaimTypes.Role, "Administrator"),
            };
            #endregion

            #region 建立 宣告式身分識別
            // ClaimsIdentity類別是宣告式身分識別的具體執行, 也就是宣告集合所描述的身分識別
            var claimsIdentity = new ClaimsIdentity(
                claims, CookieAuthenticationDefaults.AuthenticationScheme);
            #endregion

            #region 建立關於認證階段需要儲存的狀態
            var authProperties = new AuthenticationProperties
            {
                IsPersistent = true,
                RedirectUri = this.Request.Host.Value
            };
            #endregion

            #region 進行使用登入
            try
            {
                await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity),
                authProperties);
            }
            catch (Exception ex)
            {
                string error = ex.Message;
            }
            #endregion

            #endregion

            return LocalRedirect(returnUrl);
        }
    }
}

建立登出 Razor Page 頁面

  • 滑鼠右擊這個專案節點內的 [Pages] 資料夾
  • 選擇 [加入] > [新增項目] 選項
  • 在 [新增項目] 對話窗內,選擇 [Razor 頁面] 選項
  • 在下方 [名稱] 欄位內,輸入 Logout.cshtml
  • 點選 [新增] 按鈕,完成這個登入 Razor 頁面的建立
  • 打開 [Pages] 資料夾內的 [Logout.cshtml] 檔案
  • 使用底下 Razor 標記語法替換掉這個檔案內的內容
@page
@model BlazorAuthentication.Pages.LogoutModel
@{
    ViewData["Title"] = "登出";
}
<h2>登出</h2>
  • 在 [Pages] 資料夾內,找到 [Logout.cshtml] 節點內的 [Logout.cshtml.cs]
  • 使用滑鼠雙擊這個 [Logout.cshtml.cs] 節點,打開這個檔案內容
  • 使用底下 C# 程式碼替換掉原先這個檔案內的內容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

#region 這裡將會是新加入的命名空間宣告
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
#endregion

namespace BlazorAuthentication.Pages
{
    public class LogoutModel : PageModel
    {
        public async Task<IActionResult> OnGetAsync()
        {
            // 清除已經存在的登入 Cookie 內容
            await HttpContext
                .SignOutAsync(
                CookieAuthenticationDefaults.AuthenticationScheme);
            return LocalRedirect(Url.Content("~/"));
        }
    }
}

建立登出入使用的 Blazor Razor Component 元件

  • 滑鼠右擊這個專案節點內的 [Shared] 資料夾
  • 選擇 [加入] > [新增項目] 選項
  • 在 [新增項目] 對話窗內,選擇 [Razor 頁面] 選項
  • 在下方 [名稱] 欄位內,輸入 LoginControl.razor
  • 點選 [新增] 按鈕,完成這個登入 Razor 頁面的建立
  • 打開 [Shared] 資料夾內的 [LoginControl.razor] 檔案
  • 使用底下 Razor 標記語法替換掉這個檔案內的內容
@page "/LoginControl"
<AuthorizeView>
    <Authorized>
        <b>Hello, @context.User.Identity.Name!</b>
        <a class="ml-md-auto btn btn-primary"
           href="/logout?returnUrl=/"
           target="_top"> 登出 </a>
    </Authorized>
    <NotAuthorized>
        <input type="text"
               placeholder="User Name"
               @bind="@Username" />
        &nbsp;&nbsp;
        <input type="password"
               placeholder="Password"
               @bind="@Password" />
        <a class="ml-md-auto btn btn-primary"
           href="/login?paramUsername=@Username&paramPassword=@Password"
           target="_top"> 登入 </a>
    </NotAuthorized>
</AuthorizeView>
@code {
    string Username = "";
    string Password = "";
}

進行 ASP.NET Core 專案的啟動設定修正

  • 在這個專案內找到 [Startup.cs] 檔案
  • 使用滑鼠雙擊這個檔案,打開這個檔案
  • 使用底下 C# 程式碼替換掉原有的檔案內容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using BlazorAuthentication.Data;

#region 這裡將會是新加入的命名空間宣告
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
#endregion

namespace BlazorAuthentication
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            #region 加入使用 Cookie 認證需要的宣告
            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
            });
            services.AddAuthentication(
                CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie();
            #endregion

            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();

            #region 加入會用到的服務宣告
            services.AddHttpContextAccessor();
            services.AddScoped<HttpContextAccessor>();
            services.AddHttpClient();
            services.AddScoped<HttpClient>();
            #endregion
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            #region 指定要使用 Cookie & 使用者認證的中介軟體
            app.UseCookiePolicy();
            app.UseAuthentication();
            #endregion

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");

                #region 這裡要加入 Web API 控制器會用到的路由
                endpoints.MapControllerRoute("default", "{controller=Home}/action=Index/{id}");
                #endregion
            });
        }
    }
}

修正這個 Blazor 專案的整體路由設定

  • 在該專案根目錄下,找到並且打開 [App.razor] 檔案
  • 使用底下 Razor 元件 標記語法替換掉這個檔案內的內容
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

修正主要版片配置的內容

  • 在該專案的 [Shared] 目錄下,找到並且打開 [MainLayout.razor] 檔案
  • 使用底下 Razor 元件 標記語法替換掉這個檔案內的內容
@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        @*在這裡加入剛剛建立的登出入 Blazor 元件*@
        <LoginControl />
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>

修正該專案的首頁 Blazor 元件

  • 在該專案的 [Pages] 目錄下,找到並且打開 [Index.razor] 檔案
  • 使用底下 Razor 元件 標記語法替換掉這個檔案內的內容
@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<AuthorizeView>
    <Authorized>
        <h1>你好, @context.User.Identity.Name!</h1>
        <p>因為已經通過身分驗證成功,所以,會看到這個訊息</p>
    </Authorized>
    <NotAuthorized>
        <p>你尚未通過身分驗證,所以,會看到這個訊息</p>
    </NotAuthorized>
</AuthorizeView>

執行專案

  • 請執行這個 Blazor 專案
  • 將會看到如下圖的網頁顯示出來
  • 在網頁的最上方,將會看到剛剛設計的 [LoginControl.razor] 元件
  • 因為現在尚未通過身分驗證,所以,在 [LoginControl.razor] 元件上將會看到登入按鈕
  • 而且,在首頁上因為使用了 AuthorizeView 這個 Blazor 內建的元件,因此,將會根據使用者是否有成功通過驗證,而顯示出不同的內容
  • 由於現在尚未通過驗證,因此,在首頁上將會顯示尚未通過驗證的訊息
  • 請在 [User Name] 與 [Password] 欄位,輸入使用的帳號與密碼 (因為在這個練習專案中,並未實際做使用帳號與密碼的檢查,因此,可以輸入任何內容即可)
  • 請點選右上方的 [登入] 按鈕,如下面螢幕截圖
  • 在成功通過身分驗證過程,也就是登入成功了,在網頁最上方的 [LoginControl.razor] 元件內容也自動切換成已經登入的狀態
  • 並且,在首頁頁面上,也因為成功登入了,顯示出已經登入訊息,這裡的內容將會動態的切換,與尚未成功登入前有所不同
  • 因為採用 Cookie 的方式,所以,不論關閉這個網頁,或者開啟一個新的瀏覽器標籤頁次,都會看到如上面螢幕截圖的畫面