2020年4月8日 星期三

Blazor 綁定屬性變更 Part 1 - 透過 DOM 事件機制,用來偵測有興趣的物件有變化時,需要作出相對應的處理機制

Blazor 綁定屬性變更 Part 1 - 透過 DOM 事件機制,用來偵測有興趣的物件有變化時,需要作出相對應的處理機制

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

Blazor 綁定屬性變更 Part 1 - 透過 DOM 事件機制,用來偵測有興趣的物件有變化時,需要作出相對應的處理機制
這是 Blazor 綁定屬性變更問題需求探討的系列文章第一篇,首先,先來說明這系列文章想要設計出來的執行效果。當專案執行起來,將會想要出現底下畫面,在這裡共會有三個欄位 First Name 名 , Last Name 姓 , Age 年紀 ,其中前兩個為文字型態的內容,而年紀這個欄位,將會為 整數 數值。
當使用者在輸入年紀的時候,希望能夠即時在最下方顯示出相關文字,底下是要顯示文字的規則
  • 若年紀小於 13 ,將會顯示 你是小朋友
  • 若年紀小於 13 ,將會顯示 你是年青人
  • 若年紀小於 13 ,將會顯示 你是青年人
  • 否則 ,將會顯示 你是成年人
另外,還有一個需求,那就是當所輸入的 年紀 值小於 13 歲,則最下方的按鈕將會停用,無法點選,若年紀大於 13 歲,則可以點選
這個說明專案的原始碼位於 bzDOMEventChanged

建立 Blazor Server-Side 的專案

  • 打開 Visual Studio 2019
  • 點選右下方的 [建立新的專案] 按鈕
  • [建立新專案] 對話窗將會顯示在螢幕上
  • 從[建立新專案] 對話窗的中間區域,找到 [Blazor 應用程式] 這個專案樣板選項,並且選擇這個項目
  • 點選右下角的 [下一步] 按鈕
  • 現在 [設定新的專案] 對話窗將會出現
  • 請在這個對話窗內,輸入適當的 [專案名稱] 、 [位置] 、 [解決方案名稱]
    在這裡請輸入 [專案名稱] 為 bzDOMEventChanged
  • 完成後,請點選 [建立] 按鈕
  • 當出現 [建立新的 Blazor 應用程式] 對話窗的時候
  • 請選擇最新版本的 .NET Core 與 [Blazor 伺服器應用程式]
  • 完成後,請點選 [建立] 按鈕
稍微等會一段時間,Blazor 專案將會建立起來

建立需求網頁 Balzor 元件

在這裡,初步需求的設計程式碼與 HTML 標記,將會採用 Index.razor 這個檔案來設計,因此
  • 打開 [Pages] 資料夾內的 [Index.razor] 檔案
  • 使用底下 Razor 元件標記與程式碼,替換該檔案內的原有內容
@page "/"
@using System.ComponentModel.DataAnnotations

<h1>在 Blazor 使用 DOM 事件,設計屬性變更的需求</h1>

<div class="form-group">
    <label for="FirstName"></label>
    <input id="FirstName" class="form-control" @bind="MyPerson.FirstName" />
</div>
<div class="form-group">
    <label for="LastName"></label>
    <input id="LastName" class="form-control" @bind="MyPerson.LastName" />
</div>
<div class="form-group">
    <label for="Age">年紀</label>
    <input type="number" id="Age" class="form-control"
           @bind="MyPerson.Age" @bind:event="oninput" />
</div>

<button type="submit" class="btn btn-primary" disabled="@IsDisabled">Submit</button>

<div class="text-danger">
    @MyPerson.Age
</div>
<div class="text-danger">
    這裡可以顯示年紀分類說明文字
</div>

@code{

    class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }

    Person MyPerson = new Person();
    bool IsDisabled = true;
}
在這裡將會設計一個類別名為 Person ,可以讓使用者輸入 First Name , Last Name , Age 這三個屬性值;並且在這個 Blazor 元件中,使用敘述 Person MyPerson = new Person(); 建立一個 MyPerson 物件,該物件將會用於在 HTML 頁面中使用資料綁定 Razor 語法,來與 HTML 內的 DOM 元素進行綁定在一起。
在上面的標記與程式碼中,將可以看到,對於 Last Name , First Name , Age 這三個欄位,將會透過 Blazor Directive @bind 宣告使用 input 標記的雙向綁定,這裡分別使用了:@bind="MyPerson.FirstName" , @bind="MyPerson.LastName" , @bind="MyPerson.Age"
然而,對於 Age 這個欄位, 使用了 <input type="number" ... 宣告這裡要輸入的內容將會是一個數值,並且對於這個年紀欄位,由於需要即時偵測出使用者當時輸入了甚麼內容,而不是要等到使用者輸入完成之後,輸入焦點離開年紀欄位之後才會更新,所以,在這裡使用了 @bind:event="oninput" 這樣的語法來宣告要能夠即時更新使用輸入的內容到 .NET 物件, MyPerson.Age 欄位。
對於這個需求 [當所輸入的 年紀 值小於 13 歲,則最下方的按鈕將會停用,無法點選,若年紀大於 13 歲,則可以點選] ,在這裡將會使用 C# 宣告一個布林欄位 IsDisabled,並且將這個欄位使用單向綁定技術,綁定到按鈕的 disabled 的屬性值上。
現在,可以開始執行這個專案,並且在年紀欄位中,輸入任何的整數數值,但不用將輸入焦點離開這個欄位,就會看到底下紅色文字就會顯示出即時所輸入的文字內容。

建立解決問題之元件

雖然 Blazor 的資料綁定 Data Binding 機制,解決了可以將 .NET 的物件值與 HTML DOM 內的環境,綁定在一起現在要能解決這個問題,也就是說,透過了雙向綁定作法,若任何一方(來源 Source 或者目的 Target)的值有變動,將會即時更新到另外一方。
對於當使用者有輸入新的年紀數值的時候,此時,在 .NET 物件的 MyPerson.Age 屬性上屬性值,就已經透過 Blazor Two-way data binding 雙向綁定機制,自動把使用者輸入的最新數值,更新到 MyPerson.Age 值上;可是,此時對於在這個 Blazor 元件中的 C# 程式碼中,要如何知道 MyPerson.Age 的值已經有變動了,接下來,便需要針對當初提出的需求,進行設計:針對當前的年紀,顯示適當的文字與修正按鈕是否可以使用。
很多人對於這樣的需求想說,這裡可以使用 含有支援欄位的屬性 內的設定 set 存取子來進行設計,不過,由於 Age 這個屬性是在 Person 類別內,是無法知道與直接更新這個元件內的欄位 Field 或者 屬性 Property。
在這篇文章中,將會使用最常用的設計技術,也就是 事件 Event 來解決此一問題,也就是說,要透過 DOM 的事件來通知這個元件內的某個委派方法,告知所關注的物件已經有變動了,可以開始執行所綁定的委派方法。
  • 滑鼠右擊專案中的 [Pages] 資料夾節點
  • 點選 [加入] > [新增項目]
  • 現在 [新增項目] 對話窗將會出現
  • 在該對話窗最左邊,點選 [Visual C#]
  • 在該對話窗的中間,找到並點選 [Razor 元件] 選項
  • 在下方 [名稱] 欄位中,輸入 AgePropertyChanged
  • 點選右下方的 [新增] 按鈕
  • 完成新增這個元件檔案
  • 使用底下 Razor 元件標記與程式碼,替換該檔案內的原有內容
@page "/AgePropertyChanged"
@using System.ComponentModel.DataAnnotations

<h1>在 Blazor 使用 DOM 事件,設計屬性變更的需求</h1>
<h2>AgePropertyChanged</h2>
<div class="form-group">
    <label for="FirstName">名</label>
    <input id="FirstName" class="form-control" @bind="MyPerson.FirstName" />
</div>
<div class="form-group">
    <label for="LastName">姓</label>
    <input id="LastName" class="form-control" @bind="MyPerson.LastName" />
</div>
<div class="form-group">
    <label for="Age">年紀</label>

    <input type="number" id="Age" class="form-control"
           value="@MyPerson.Age"
           @oninput="@(x=>OnAge(x))" />
</div>

<button type="submit" class="btn btn-primary" disabled="@IsDisabled">Submit</button>

<div class="text-danger">
    @MyPerson.Age
</div>
<div class="text-danger">
    @AgeRange
</div>

@code{
    void OnAge(ChangeEventArgs e)
    {
        try
        {
            MyPerson.Age = Convert.ToInt32(e.Value);
        }
        catch { }
        AgeChanged();
    }

    class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }

    Person MyPerson = new Person();
    bool IsDisabled = true;
    string AgeRange = "";

    void AgeChanged()
    {
        if (MyPerson.Age < 13)
        {
            AgeRange = "你是小朋友";
            IsDisabled = true;
        }
        else if (MyPerson.Age < 20)
        {
            AgeRange = "你是年青人";
            IsDisabled = false;
        }
        else if (MyPerson.Age < 28)
        {
            AgeRange = "你是青年人";
            IsDisabled = false;
        }
        else
        {
            AgeRange = "你是成年人";
            IsDisabled = false;
       }
        StateHasChanged();
    }
}
首先,先來設計一個方法,用來根據當前的年紀,根據所指定的規格,設定適當的文字,這裡將會儲存到新加入的 AgeRange 欄位變數內,這裡將會設計到 AgeChanged() 方法內;而且,這個 AgeRange 變數,將會透過單向資料綁定 one-way data binding 的方式,使用底下宣告來顯示在螢幕上
<div class="text-danger">
    @AgeRange
</div>
另外,在 AgeChanged() 方法內,也會修正 IsDisabled 變數的數值內容,也就會間接影響到當時的按鈕是否可以使用,這裡透過了 disabled="@IsDisabled" 宣告方式,透過單向資料綁定來做到。
在這裡的原始碼中,將會發現到對於年紀的 HTML 標記宣告,變成了 <input type="number" id="Age" class="form-control" value="@MyPerson.Age" @oninput="@(x=>OnAge(x))" /> 這樣,原先使用 @bind="MyPerson.Age" 的雙向資料綁定宣告,修改成為了 value="@MyPerson.Age" 單向的資料綁定。

問題探討

若對於上述的說明不是很了解的,可以請讀者對於上述的說明進行研究之後,將你的看法留言在本部落格文章的留言處
由於現在變更成為了單向資料綁定,所以,就宣告了 @oninput="@(x=>OnAge(x))" /> oninput 指示詞 (directive)來做到綁定網頁端的 DOM 的事件 GlobalEventHandlers.oninput,也就是說,當使用者有輸入任何內容的時候,將會觸發這個元件中內 C# 的 OnAge 事件委派方法。
從 [OnAge] 方法中,將會看到有宣告一個參數,其型別為 ChangeEventArgs,這裡將會透過這個參數,取得使用者最新輸入的內容,該內容可以透過 e.Value 來取得。並且將該內容轉型成為 int 型別,最後設定到 MyPerson.Age 屬性上。
由於 MyPerson.Age 屬性已經有異動了,所以,將會根據當前的 MyPerson.Age 屬性值來計算出適當要顯示的訊息,並且決定按鈕是否要啟用。
底下的螢幕截圖,將會是執行結果:

Blazor 資料綁定的屬性變更

Blazor 資料綁定的屬性變更
Blazor 資料綁定的屬性變更



在 Visual Studio 內,指定特定的 NuGet 套件安裝來源作法

在 Visual Studio 內,指定特定的 NuGet 套件安裝來源作法

當在進行專案開發的時候,可以將團隊需要用到的各種常用 API,設計到類別庫內,接著,可以將該類別庫打包成為一個 NuGet 套件,而該 NuGet 套件便可以散發到網路上,提供其他開發團隊的成員,在 Visual Studio 內來設定該 NuGet 套件來源,取得所需要的套件。通常,開發者會打開他們的 Visual Studio,點選功能表 [工具] > [選項] > [NuGet 套件管理員] > [套件來源]
NuGet 可用套件來源
當看到 [可用套件來源] 對話窗出現之後,便可以在這個對話窗中,輸入想要新增的額外 NuGet 來源與名稱,這些 NuGet 的來源可能是公司內部的某個檔案伺服器的路徑、公司內部的私有 NuGet 伺服器、外部其他廠商提供的私有 NuGet 伺服器。
在這篇文章中,我已經將一些授課或者寫書會用到的常用 API,設計成為一個 類別庫,並且打包成為 NuGet 檔案,最後,上傳到 https://www.myget.org/ 網站上;當對於這些 API 有興趣者,只要輸入這個 URL https://www.myget.org/F/course-lab/api/v3/index.json 到 NuGet 來源設定中,便可以取得這些套件。
不過,問題來了,若在某台電腦上的 Visual Studio 已經設定了這個 NuGet 來源,對於使用其他電腦的開發者,一旦打開了這個正在開發的專案,則會因為那台電腦上沒有設定相關 NuGet 來源,而造成無法順利建置該專案;又或者是你自己的電腦重新安裝作業系統或者換了一台新電腦,也會遇到需要重新進行設定的困境。
現在可以使用 [nuget.config] 設個檔案,定義該專案要使用的其他 NuGet 來源,一旦設定好了之後,這些 NuGet 來源,便會跟著該專案跑,也就是說,當這個專案在別台電腦上打開之後,在這台電腦上,就已經會看到這些額外設定的 NuGet 來源。
這個說明專案的原始碼為 CustomNuGetSource

建立練習專案

  • 在這裡將會建立一個 Console 類型的專案來做為說明
  • 當該專案建立之後,請在該方案總管的節點上,使用滑鼠右擊 [加入] > [新增資料夾]
  • 對於該新的方案資料夾,輸入 nuget
  • 完成之後,滑鼠右擊 [nuget] 這個資料夾,選擇 [加入] > [新增項目]
  • 當新增項目對話窗顯示之後,在 [一般] 標籤頁次內的中間區域,會看到 [XML檔] 這個選項
  • 請點選 [XML 檔]這個選項,在下方的名稱欄位中,輸入 : nuget.config
  • 最後,請將底下內容,填入到這個檔案內
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="course-lab" value="https://www.myget.org/F/course-lab/api/v3/index.json" />
  </packageSources>
</configuration>

檢查 NuGet 來源是否已經綁定專案了

  • 請先關閉 Visual Studio ,並且重新開啟 Visual Studio
  • 滑鼠右擊該專案的 [參考] 節點,選擇 [管理 NuGet 套件]
  • 當管理NuGet套件視窗出現之後,可以點選該視窗右上方的 [套件來源] 下拉選單
  • 此時,將會看到一個新的 NuGet 套件來源選項,這就是剛剛設定的 NuGet 套件來源,而該套件來源,將會跟著該專案跑。
    NuGet 套件
  • 另外,點選 Visual Studio 功能表 [工具] > [選項]
  • 當選項對話窗出現之後,展開 [NuGet 套件管理員] > [套件來源],也會看到這個新增的套件來源項目
    Visual Studio 可用套件來源

設計測試專案原始碼

在 [Program.cs] 檔案中,使用底下程式碼替代
    class Program
    {
        static void Main(string[] args)
        {
            var foo = Vulcan.ServiceSample.MonkeyData.Monkeys;
            foreach (var item in foo)
            {
                Console.WriteLine($"{item.Name}");
            }


            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }



2020年2月26日 星期三

ASP.NET Core Blazor 使用網站跟目錄與其他目錄的靜態圖片使用練習

ASP.NET Core Blazor 使用網站跟目錄與其他目錄的靜態圖片使用練習

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


在這篇文章中,將要來練習如何在 Blazor 專案內,顯示專案中的靜態圖片檔案;這些靜態的圖片檔案可以存在於該專案的網站根目錄下,也可以另外建立一個方案資料夾,宣告這個這料夾內可以儲存任何網站的靜態檔案,這也包括了圖片檔案。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載

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

想要進行這樣的專案開發練習,可以參考底下的操作步驟
  • 打開 Visual Studio 2019 開發工具
  • 當 [Visual Studio 2019] 對話窗出現之後,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗內,請找出 [Blazor 應用程式] 這個專案開發範本,並且點選這個專案開發範本
  • 請點選右下角 [下一步] 按鈕
  • 出現 [設定新的專案] 對話窗,輸入適當的 [專案名稱] 、 [位置] ,完成後,請點選右下角 [建立] 按鈕
    在這個範例程式碼中,將會建立一個 BlazorOutsideImage 專案名稱
  • 此時將會看到 [建立新的 Blazor 應用程式] 對話窗,這裡可以根據當時開發專案的需要,自行決定是否有調整 Blazor 專案的其他特性,若無,請點選右下角的 [建立] 按鈕
  • 此時,這個 Blazor 專案已經建立完成

建立相關方案資料夾與複製圖片檔案

現在,將會準備三個圖片檔案: blazor-webassembly.png 、 blazor-server.png 、 JavaScriptInterop.png,完成後的結果將會如同下面螢幕截圖。
  • 滑鼠右擊這個專案節點
  • 選擇 [加入] > [新增資料夾] 選項
  • 使用 StaticFilesFolder 名稱作為該方案資料夾的名稱
  • 滑鼠右擊 [wwwroot] 這個特殊資料夾
  • 選擇 [加入] > [新增資料夾] 選項
  • 使用 Images 名稱作為該方案資料夾的名稱
  • 接著,將圖片檔案 blazor-webassembly.png 複製到 [wwwroot] > [Images] 資料夾內
  • 將圖片檔案 blazor-server.png 複製到 [wwwroot] 資料夾內
  • 將圖片檔案 JavaScriptInterop.png 複製到 [StaticFilesFolder] 目錄下
  • 記得要將 JavaScriptInterop.png 這個圖片檔案,在方案總管的屬性視窗內,宣告 [建置動作] 欄位的值為 [永遠複製]
    若沒有宣告相關圖片檔案的 [建置動作] 欄位的值為 [永遠複製],則將無法在網頁上看到這些圖片

修正 Startup.cs

  • 請打開 Startup.cs 這個檔案
  • 找到 Configure
  • 在 app.UseStaticFiles(); 敘述下面,加入這段程式碼
    // 這裡加入底下 Middleware 中介軟體 宣告,在這個專案內新增一個檔案提供者指向 /StaticFiles 目錄
    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), "StaticFilesFolder")),
        RequestPath = "/StaticFiles"
    });
現在,這個專案可以使用這個 URL /StaticFiles 來指向任何在 [StaticFilesFolder] 方案資料夾內的靜態檔案了
底下的程式碼將會是完成的結果
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();
    // 這行不能刪除,因為這會指向專案內的 wwwroot 目錄下
    app.UseStaticFiles();
    // 這裡加入底下 Middleware 中介軟體 宣告,在這個專案內新增一個檔案提供者指向 /StaticFiles 目錄
    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), "StaticFilesFolder")),
        RequestPath = "/StaticFiles"
    });

    app.UseRouting();

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

關於 ASP.NET Core 中的檔案提供者

ASP.NET Core 透過使用檔案提供者,將檔案系統存取抽象化,可以參考 關於 ASP.NET Core 中的檔案提供者 得到更多詳細說明

修改 Index.razor Blazor 元件

現在,要在 Index.razor 元件中,來顯示這些靜態圖片
  • 在 Pages 資料夾內找到 Index.razor 檔案
  • 打開這個檔案,使用底下程式碼進行替換
@page "/"

<h1>Hello, world!</h1>

<h2>網站根目錄 /blazor-server.png</h2>
<div>
    <img src="/blazor-server.png" />
</div>

<h2>網站根目錄下的目錄 /Images/blazor-webassembly.png</h2>
<div>
    <img src="/Images/blazor-webassembly.png" />
</div>

<h2>非網站根目錄,這裡是另外宣告的 StaticFilesFolder 目錄 /StaticFiles/JavaScriptInterop.png</h2>
<div>
    <img src="/StaticFiles/JavaScriptInterop.png" />
</div>
@code
{

}

查看執行結果

好的,可以來執行這個專案
當這個 Blazor 網站跑起來之後,就會看到如上面螢幕截圖,這三個圖片都正常顯示在網頁上了。

2020年2月23日 星期日

Blazor Server 的不正確使用者登入驗證做法 - 透過 Blazor 元件

Blazor Server 的不正確使用者登入驗證做法 - 透過 Blazor 元件

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


當要進行 Blazor Server 專案開發的時候,若想要設計具有使用者身分驗證的功能,很理所當然的會想說,在 Blazor 專案內建立一個 Blazor 元件,在該元件上加入登入用的帳號與密碼與登入按鈕,想說這樣就可以了,不過,這可能會帶來問題,在這篇文章中將會來說明所會遇到的問題。在這裡的開發練習專案中,將會試圖想要設計一個使用 Cookie 的使用者身分驗證的功能。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載

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

想要進行這樣的專案開發練習,可以參考底下的操作步驟
  • 打開 Visual Studio 2019 開發工具
  • 當 [Visual Studio 2019] 對話窗出現之後,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗內,請找出 [Blazor 應用程式] 這個專案開發範本,並且點選這個專案開發範本
  • 請點選右下角 [下一步] 按鈕
  • 出現 [設定新的專案] 對話窗,輸入適當的 [專案名稱] 、 [位置] ,完成後,請點選右下角 [建立] 按鈕
    在這個範例程式碼中,將會建立一個 BlazorSignInAsync 專案名稱
  • 此時將會看到 [建立新的 Blazor 應用程式] 對話窗,這裡可以根據當時開發專案的需要,自行決定是否有調整 Blazor 專案的其他特性,若無,請點選右下角的 [建立] 按鈕
  • 此時,這個 Blazor 專案已經建立完成

修正 Startup.cs

  • 請打開 Startup.cs 這個檔案
  • 找到 ConfigureServices 方法
  • 在該方法的最後面加入底下程式碼
#region 加入使用 Cookie 認證需要的宣告
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(
    CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie();
#endregion
這裡將會註冊 IHttpContextAccessor 這個服務,因為,想要能夠在 Blazor 元件內來使用 HttpContext 這個物件
另外,也會啟用 Cookie 身分認證功能
  • 找到 Configure 方法
  • 在 app.UseStaticFiles(); 敘述下面,加入這段程式碼
app.UseAuthentication();

設計一個登入 Blazor 元件 Component

  • 滑鼠右擊這個專案節點內的 [Pages] 資料夾
  • 選擇 [加入] > [新增項目] 選項
  • 在 [新增項目] 對話窗內,選擇 [Razor 元件] 選項
  • 在下方 [名稱] 欄位內,輸入 BlazorLoginLogout.razor
  • 點選 [新增] 按鈕,完成這個登入 Razor 頁面的建立
  • 使用底下的 Razor 程式碼,替換掉產生出來的程式碼
@using Microsoft.AspNetCore.Identity;
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Authentication
@using System.Security.Claims
@using Microsoft.AspNetCore.Authentication.Cookies

@inject IHttpContextAccessor HttpContextAccessor
@inject NavigationManager NavigationManager

@page  "/BlazorAuth"

<h3>Blazor 的登入與登出身分驗證</h3>

<div class="row">
    <div class="col-md-12 bg-light">
        <EditForm Model="@userModel" OnValidSubmit="@LoginAsync">
            <div class="form-group">
                <label>帳號: </label>
                <input @bind-value="userModel.Email" class="form-control" />
            </div>
            <div class="form-group">
                <label>密碼: </label>
                <input type="password" @bind-value="userModel.Password" class="form-control" />
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-primary">登入</button>
            </div>
        </EditForm>
    </div>
</div>

@code {
    public UserModel userModel { get; set; } = new UserModel();

    public async Task LoginAsync()
    {

        var claims = new List<Claim>()
        {
            new Claim(ClaimTypes.Name, userModel.Email),
            new Claim("UserID", "123")
        };

        var claimsIdentity = new ClaimsIdentity(claims, "myTest");
        var principal = new ClaimsPrincipal(claimsIdentity);

        await HttpContextAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                        principal,
                        new AuthenticationProperties
                        {
                            ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
                            IsPersistent = false,
                            AllowRefresh = true
                        });
        NavigationManager.NavigateTo("/");
    }

    public class UserModel
    {
        public string Email { get; set; }
        public string Password { get; set; }
    }
}
首先,在該 Razor 元件的最前面,將會使用 @using 這樣的語法,加入這個 Blazor 元件內將會使用到各個 API 用到的命名空間,另外,也會使用 @inject 語法來使用相依注入 Dependency Injection 容器服務 Container Server (DI Container)來注入 IHttpContextAccessor & NavigationManager 這兩個服務物件到這個 Razor 元件內。
對於 HTML 標記內容而言,則是非常簡單的使用這個 Blazor 內建的 EditForm 元件,來設計出一個輸入帳號與密碼的頁面,當使用者點選了 [登入] 按鈕之後,便會觸發 EditForm 元件中的 OnValidSubmit 事件參數,也就是會執行 LoginAsync 這個方法。
在 LoginAsync 方法內,將不會檢查 帳號與密碼是否正確,因為,這裡只是要進行使用者的身分驗證執行程序,因此,將會建立起一個 ClainsPrincipal 這個 principal 物件,接者,透過相依注入的 HttpContextAccessor 物件,便可以取得 HttpContext 物件;因為已經加入了 Microsoft.AspNetCore.Authentication 命名空間參考,所以,便可以呼叫 SignInAsync 這個方法,來進行新的使用者登入作業。

修改 Index.razor Blazor 元件

現在,要在 Index.razor 元件中,來顯示這些靜態圖片
  • 在 Pages 資料夾內找到 Index.razor 檔案
  • 打開這個檔案,使用底下程式碼進行替換
@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<BlazorLoginLogout/>

查看執行結果

好的,可以來執行這個專案
請在帳號與密碼欄位中隨意輸入任何文字,並且點選 登入 按鈕,將會看到如下面截圖的最下方的錯誤訊息。
想要知道發生了甚麼問題,可以先按下 [F12] ,啟動開發人員工具。
接著,按下 [F5] 按鈕,重新整理這個網頁,便可以重新輸入任意文字到帳號與密碼欄位上,而後再一次點選 登入按鈕,現在,便可以在開發人員工具視窗中,看到底下的錯誤訊息。
Error: System.InvalidOperationException: Headers are read-only, response has already started.

   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()

   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key, StringValues value)

   at Microsoft.AspNetCore.Http.ResponseCookies.Append(String key, String value, CookieOptions options)

   at Microsoft.AspNetCore.Authentication.Cookies.ChunkingCookieManager.AppendResponseCookie(HttpContext context, String key, String value, CookieOptions options)

   at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)

   at Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, String scheme, ClaimsPrincipal principal, AuthenticationProperties properties)

   at BlazorSignInAsync.Pages.BlazorLoginLogout.LoginAsync() in D:\Vulcan\Projects\BlazorSignInAsync\BlazorSignInAsync\Pages\BlazorLoginLogout.razor:line 47

   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)

   at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()

   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)

   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
從例外異常的訊息中 Error: System.InvalidOperationException: Headers are read-only, response has already started. 可以明確的看出來,現在這個樣的 Blazor 使用者登入身分驗證的方式(使用 Blazor 元件與 SignInAsync 方法) 所設計出來的登入做法,是不成功的。