2021年6月16日 星期三

Blazor Server 快速開發專案樣板 4 - 資料庫重建與紀錄初始化

Blazor Server 快速開發專案樣板 4 - 資料庫重建與紀錄初始化

Blazor Server 快速開發專案樣板 - 相關系列文章清單

上一篇的文章 : Blazor Server 快速開發專案樣板 3 - 日誌與來源 IP

在這篇文章將會說明在進行 Blazor 專案開發的時候,通常會使用存取資料庫的紀錄,在這個 [Blazor Server 快速開發專案樣板] 內,使用了 Entity Framework Core 5 Code First 方式來進行存取資料庫,並且在開發階段將會使用本機電腦上的 localDB 資料庫來進行開發。

在 Blazor Server 快速開發專案樣板 1 - 建立一個新的專案 文章中有提到,一旦建立起一個新專案之後,需要建立起一個開發用的資料庫,這樣這個 [Blazor Server 快速開發專案樣板] 便可以開始運作了,還有相當多的運用也都會使用到資料庫來進行資料存取。

[Blazor Server 快速開發專案樣板] 內會使用到底下的資料庫綱要

這些定義的資料表 Table 並沒有多少,其主要的目的如下

  • MyUser

    這個系統內的使用者,其中有個內建的帳號為 god,這表示了為開發者專用的帳號,會有這樣的帳號是因為想要設計某些功能僅能夠讓開發者可以來使用,客戶管理者或者一般帳號是看不到這些功能,就算直接輸入這些 URL,還是無法使用這些功能。

    從上面的螢幕截圖,是使用 god 開發者帳號登入所看到的功能表清單,其中,會有一個 [系統管理者] 功能表群組項目,點選這個名稱之後,就會展開三個子功能表項目,便可以進行額外的內部系統設定與調整。

    上面截圖就是當使用者就算使用系統管理者登入,並且直接輸入了 https://localhost:5001/menurole 網頁端點,因為身分沒有被授權,還是無法進行登入。

    不過,在這個專案範本內也做了一些額外的保護,就算使用者直接變更資料庫內的 god 紀錄,還是會受到基本的保護。

  • MenuRole / MenuData

    這兩個資料表則是用來定義使用者可以看到的功能表群組清單項目,因此,這個系統可以做到不同使用者登入後,僅能夠看到該使用者授權使用的作業畫面。

  • SystemLog

    這個資料表將適用於儲存 Blazor Server 快速開發專案樣板 3 - 日誌與來源 IP 文章中提到的客製化日誌紀錄,開發者可以透過適當的網頁來查詢到相關的系統運作核心訊息紀錄。

  • Product / OrderMaster / OrderItem

    這三個資料表是用於展示如何透過這個專案設計出 單一資料表 或者 一對多資料表 的作業所使用到的資料表。

因此,可以看到這個資料庫內的資料表架構可以說是非常的簡單,可以讓開發者方便的快速進行擴充開發成為所規劃的任何系統。

接下來將會來說明這些設計過程。

InitializationPage.razor

在這個 [Blazor Server 快速開發專案樣板] 啟動之後,輸入這個 https://localhost:5001/Initialization 網址,就可以看到這個資料庫重建與初始化的作業。

當然,這樣的作業對於該系統上線到 Production 網站上之後,這個網頁應該不能夠讓任何來存取,因此,建議可以將所有的 HTML 標記都標示在 <environment include="Development">...<environment> 內,這樣就可以確保這個網頁是不會出現在正式環境的系統內。

那為什麼不直接這樣的設計,這是因為作者會將這個專案部署到 Azure 上,其可以透過 https://backendhol.azurewebsites.net/ 這個網址來看到最新的這個專案系統,因為這樣因素,作者本身偶而會需要能夠在 Production 環境下來直接進行資料庫刪除、建立、產生相關測試紀錄的功能。

對於這個 [InitializationPage.razor] 頁面的相關標記與程式碼如底下內容

@page "/Initialization"
@using Microsoft.Extensions.Configuration;
@using Microsoft.EntityFrameworkCore;
@using System.Threading.Tasks
@using System.Threading
@using System.Diagnostics

@inject IWebHostEnvironment env
@inject DatabaseInitService DatabaseInitService

@layout EmptyLayout

@using System.Security.Claims
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Hosting
@using Syncfusion.Blazor.Spinner

<h1 class="bg-primary text-white-50 my-1">資料庫初始化作業!</h1>

<environment include="Staging,Production">
    @if (verified == false)
    {
        <div>
            <div class="h2 text-success">@Question</div>
            <p>請輸入驗證碼</p>
            <div class="input-group mb-3">
                <input type="text" class="form-control"
                       placeholder="驗證碼" aria-label="Username" aria-describedby="basic-addon1"
                       @bind="verifyCode">
            </div>
            <div class="input-group mb-3">
                <button class="btn btn-primary"
                        @onclick="OnVerifyCode">
                    送出
                </button>
            </div>
        </div>
    }
</environment>

@if (verified)
{
    <div class="card mb-4">
        <div class="card mb-4">
            <div class="card-header bg-danger text-white">
                <span class="h2">資料庫初始化</span>
            </div>
            <article class="card-body bg-light">
                <div>
                    <button class="btn btn-danger" @onclick="Init">資料庫重新建立與資料初始化</button>
                </div>
            </article>
        </div>
    </div>
}

<div id="container">
    <SfSpinner @bind-Visible="@VisibleProperty">
    </SfSpinner>
</div>


@code {
    bool verified = false;
    string verifyCode = "";
    int Question = 0;
    bool VisibleProperty = false;

    protected override void OnInitialized()
    {
        Question = new Random().Next(10000, 90000);
        if (env.IsDevelopment())
        {
            verified = true;
            verifyCode = Question.ToString();
        }
    }
    public async Task Init()
    {
        this.VisibleProperty = true;
        await DatabaseInitService.InitDataAsync();
        this.VisibleProperty = false;
    }
    void OnVerifyCode()
    {
        try
        {
            int verifyNumber = 0;
            int answer = 0;
            verifyNumber = Convert.ToInt32(verifyCode);
            answer = 98765 - Question;
            if (verifyNumber == answer)
            {
                verified = true;
            }
        }
        catch { }
    }
}

一旦點選 [資料庫初始化] 按鈕之後,就會 DatabaseInitService.InitDataAsync() 這個方法,而這個 DatabaseInitService 服務物件將會是透過相依性注入容器來注入這個物件 (使用這個敘述來宣告注入能力 @inject DatabaseInitService DatabaseInitService)

DatabaseInitService.cs

這個 InitDataAsync() 方法將會定義在 [Services] 資料夾內的 [DatabaseInitService.cs] 檔案內,底下將會僅列出這個類別內的 InitDataAsync() 方法內的部分程式碼。

public async Task InitDataAsync()
{
    Random random = new Random();
    #region 適用於 Code First ,刪除資料庫與移除資料庫
    string Msg = "";
    Msg = $"適用於 Code First ,刪除資料庫與移除資料庫";
    Logger.LogInformation($"{Msg}");
    await context.Database.EnsureDeletedAsync();
    Msg = $"刪除資料庫";
    Logger.LogInformation($"{Msg}");
    await context.Database.EnsureCreatedAsync();
    Msg = $"建立資料庫";
    await SystemLogHelper.LogAsync(new SystemLogAdapterModel()
    {
        Message = Msg,
        Category = LogCategories.Initialization,
        Content = "",
        LogLevel = LogLevels.Information,
        Updatetime = DateTime.Now,
        IP = HttpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString(),
    });
    Logger.LogInformation($"{Msg}");
    #endregion
    ...
}

從上面的程式碼可以看到,一開始將會執行 await context.Database.EnsureDeletedAsync(); 這個敘述,其中 EnsureDeletedAsync() 代表以非同步方式確保內容的資料庫不存在。 如果不存在,則不會採取任何動作。 如果存在,則會刪除資料庫。

接著將會呼叫 await context.Database.EnsureCreatedAsync(); 這個敘述,該方法 EnsureCreatedAsync() 根據微軟文件的上面描述為:以非同步方式確保內容的資料庫存在。 如果存在,不會採取任何動作。 如果不存在,則會建立資料庫及其所有架構。 如果資料庫存在,則不需進行任何工作,以確保它與此內容的模型相容。

由於這個專案採用 Entity Framework Core 5.0 的 Code First 方式進行開發,因此,透過上面兩個敘述會把資料庫刪除並且重新起來,相關的存在於資料庫上的紀錄將會都被刪除,而且重新建立資料庫之後,該資料庫內是沒有任何紀錄的。

最後使用 [await SystemLogHelper.LogAsync()] 方法,將會寫入到系統內部的日誌資料表內。

接下來便是要進行這個新的資料庫要來建立起相關紀錄

這裡會用到底下的程式碼

依據資料表的相依關係,將會需要先建立功能表角色與相關功能表清單項目,這裡將會使用 await 建立功能表角色與項目清單Async(); 這個方法來建立

其中將會建立三個功能表角色,分別是 開發者角色、系統管理員角色、使用者角色 ,第一個角色可以看到的功能表清單在前面已經看到了,底下將分別是另外兩個角色可以看得到的功能項目清單

系統管理員角色

使用者角色

接下來則是要建立 MyUser 資料表內的測試使用的相關紀錄,這裡將會使用到 await 建立使用者紀錄Async(); 這個方法,其中當建立使用者的時候,同時會指定該使用者需要搭配的功能表角色的紀錄。

最後,使用 List<Product> products = await 建立產品紀錄Async(); 與 await 建立訂單紀錄Async(random, products); 將會建立起產品與訂單用的測試紀錄。

到這裡為止,資料庫重新建立好了,測試紀錄也新增完成了,現在可以使用這個 Blazor 專案建立的網站服務,開始進行登入,並且使用相關的作業服務。

在這裡建立好的測試用的使用者帳號如下:

  • 系統開發者帳密 : god / 123
  • 管理者帳密 : admin / 123
  • 一般使用者帳密 : user1~4 / 123

透過這樣的設計方式可以達到這的效果,當系統需求變更的時候,造成資料庫架構有變動,例如:新增資料表、刪除資料表、資料表的欄位新增或者修改或刪除、限制條件修正等等,透過這個 https://localhost:5001/Initialization 網址所提供的服務,線上直接資料庫的重新建立作業,讓整體開發過程變得相當的簡單與容易。 





2021年6月15日 星期二

Blazor Server 必會開發技能 - 建立一個新的頁面

Blazor Server 必會開發技能 - 建立一個新的頁面



Blazor Server 必會開發技能

Blazor Server 建立一個新的頁面

Blazor Server 元件生命週期事件 Component Life Cycle

Blazor Server C# 程式碼設計方法

Blazor Server 單向資料綁定 One Way Data Binding 與 重新轉譯 Binding

Blazor Server Hello 互動頁面與事件設計 Two Way Data Binding

Blazor Server 元件間的參數傳遞與回應事件 Component Parameter EventCallback

Blazor Server C# 與 JavaScript 互相呼叫 IJSRuntime

Blazor Server 表單和驗證 Form Validation 



對於許多網站開發者、尤其是使用 .NET C# 程式語言,第一次聽到 Blazor 這個 UI 開發框架,都會抱持著既期待又怕傷害的心情來面對這個工具;尤其,更進一步去了解之後,又發現到 [Blazor] 竟然又有 Blazor WebAssembly 與 Blazor Server 兩種託管模式要來選擇。

相信許多人面對這樣的情境,都會有著選擇性的障礙與情緒極度不安的心情。首先,對於不想使用或者對於 .NET C# 這樣開發程式語言沒有信心的人,強烈建議你不要勉強自己來選擇 [Blazor] 這樣的開發方式,畢竟,在 [Blazor] 的開發模式下,是僅需要使用 .NET C# / HTML / CSS 這三套語言,就可以完成絕大部分的網站開發工作。

什麼,沒有看錯嗎?為什麼沒有 JavaScript 這個程式語言呢?

你沒有看錯,對於擁有 JavaScript 這個程式語言並且身懷絕技開發者而言,使用 [Blazor] 似乎沒有太多的用處,你僅需要的是要能夠精通與專注於 C# 程式語言的開發即可。

另外,要使用 [Blazor WebAssembly] 或者 [Blazor Server] 方式來開發專案,這裡有個建議,若僅是想要評估與學習如何使用 [Blazor] 這套 UI 框架來進行網站開發,可以直接使用 [Blazor Server] 的模式來開始學習;一旦絕大部分的 [Blazor] 功能都學會之後,想要切換到 [Blazor WebAssembley] 模式下來開發,只要相關的類別設計能夠充分使用相依性注入設計模式來開發,將會相當容易與快速的從 [Blazor Server] 轉換到 [Blazor WebAssembly]。

對於 [Blazor Server] 與 [Blazor WebAssembly] 的差異與比較,網路上存在著相當豐富的文字,到 Google 上搜尋就可以看到一大票的這樣文章。對於作者而言,因為本身所處的環境,相當適合於 [Blazor Server] 的開發,因此,從 2020年6月,一開始就選擇了 [Blazor Server] 這樣的模式來進行 Blazor 專案開發,到現在為止已經開發出快要7套以上的網站專案。

因此,建議對於想要使用 [Blazor] 來開發專案的讀者或者想要評估與測試 [Blazor] 這套開發工具的讀者,可以先採用 [Blazor Server] 的方式來著手進行。

大家一定相當的關心,整體成效如何呢?作者只能說:相當的讚、超乎預期的優異、快速開發且縮短時間、專案好維護,還有一個很重要的絕佳優勢那就是,當開發人力不足的時候,招募新人進來,只需要具備 .NET C# 的開發經驗,最多給予 1 個月的教育訓練,這些新招募進來的開發人員便可以瞬間變身成為一個網站開發工程師,光是這樣的好處,就十分值得採用 [Blazor] 來開發網站應用程式。

更多關於作者本身使用 [Blazor] 的實戰故事,可以參考 Blazor實戰故事經驗分享 1 - 風起雲湧 如何從無到有建立Blazor團隊與採用全端開發方式設計出給上市企業使用的Web系統 與 Blazor實戰故事經驗分享 2 - 風雲再現 探究 Blazor 可以快速開發出來內部細節

首先,先來看看如何在 [Blazor] 專案下建立一個新的網頁頁面。對於傳統的網專站專案開發想要完成這樣的需求,那就是要在該專案內建立一個 .html 的檔案即可,不過,使用 [Blazor] 完成這樣的需求,也是相當容易的。

這裡說明的範例專案原始碼位於 BS01

建立 Blazor Server-Side 的專案

  • 打開 Visual Studio 2019

  • 點選右下方的 [建立新的專案] 按鈕

  • [建立新專案] 對話窗將會顯示在螢幕上

  • 從[建立新專案] 對話窗的中間區域,找到 [Blazor 應用程式] 這個專案樣板選項,並且選擇這個項目

  • 點選右下角的 [下一步] 按鈕

  • 現在 [設定新的專案] 對話窗將會出現

  • 請在這個對話窗內,輸入適當的 [專案名稱] 、 [位置] 、 [解決方案名稱]

    在這裡請輸入 [專案名稱] 為 BS01

  • 完成後,請點選 [建立] 按鈕

  • 當出現 [建立新的 Blazor 應用程式] 對話窗的時候

  • 請選擇最新版本的 .NET Core 與 [Blazor 伺服器應用程式]

  • 完成後,請點選 [建立] 按鈕

    稍微等會一段時間,Blazor 專案將會建立起來

加入靜態圖片資源

  • 在 Blazor 專案內找到 [wwwroot] 節點

  • 滑鼠右擊這個節點

  • 從彈出功能表選擇 [新增] > [新增資料夾]

  • 將這個新資料夾命名為 [Images]

  • 找到一個圖片檔案,拖拉到 [Images] 資料夾內

    在這裡,作者是把本身 Facebook 網站上的大頭貼照片下載到本機電腦上,該圖片命名為 [me.jpg] ,接著,使用檔案總管拖拉到 Visual Studio 方案總管之 Blazor 專案下的 [wwwroot] > [Images] 目錄下

建立第一個 Blazor 專案的頁面

  • 滑鼠右擊 Blazor 專案內的 [Pages] 資料夾

  • 選擇 [加入] > [Razor 元件]

    這裡選擇的 [Razor 元件] 指的就是 [Blazor 元件 Component],兩者指的是同一件事情,千萬要注意,在這裡不要選擇 [Razor 頁面] 這個選項

  • 當 [新增項目 - BS01] 對話窗出現之後,請在下方名稱欄位內,輸入 MyFirstBlazorPage

  • 最後點選 [新增] 按鈕

  • 在 [方案總管] 視窗內的 Blazor 專案下,展開 [Pages] 資料夾,就可以看到 [MyFirstBlazorPage.razor] 這個 [Razor 元件] 已經建立起來了。

  • 其中,[MyFirstBlazorPage.razor] 這個檔案也已經開啟了

    在下面圖片中可以看出,一個 Blazor 的開發者,可以在 [Razor 元件] 檔案內設計各種 HTML 的標記語言代碼

    另外,可以在這個 [Razor 元件] 檔案內使用 Inline 內嵌 方式來加入 C# 程式碼,

  • 請依據底下程式碼輸入到這個檔案內 [MyFirstBlazorPage.razor]

    在最前面有使用 Directive 指示詞 @page 用來指定可以加入這樣的路由到 Blazor 專案內,關於更關這方面的資訊,可以參考 路由範本

    根據微軟官方的 ASP.NET Core Razor 元件 文件中有描述到:指示詞 Directive ,用於變更剖析元件標記的方式或函數。 例如,指示詞 @page 會指定具有路由範本的可路由元件,並可在瀏覽器中以特定 URL 直接觸達使用者的要求。

@page "/MyFirstBlazorPage"

@*這裡可以撰寫 HTML 相關標記*@
<h3>MyFirstBlazorPage</h3>

<div>
    <img src="Images/me.jpg" />
</div>

<div>
    我使用
    <span class="text-danger">Blazor</span>
</div>

<div>
    我的名字叫做
    <span class="h3 text-danger">Vulcan Lee</span>
</div>

@code {
    //這裡可以撰寫 C# 程式碼
}

執行且觀看執行結果

  • 按下 [F5] 按鍵,開始執行這個 Blazor 專案

  • 一旦啟動完成,就會自動開以瀏覽器

  • 在瀏覽器位址列輸入這個 https://localhost:5001/MyFirstBlazorPage 網址,就會看到如下圖執行結果

進階問題探討

  • 為了方便進行說明問題,請先在 Visual Studio 按下 [Shift] + [F5] 來停止這個專案的執行

  • 接著,請按下 [F5] 開始重新執行這個專案

  • 一旦專案執行完成之後,請在瀏覽器上按下 [F12] 來使用 [開發人員工具]

  • 請在 [開發人員工具] 視窗內,切換到 [網路] 分頁標籤頁次

  • 接著點選最下方第一筆紀錄的 [localhost] 項目

  • 現在可以看到當要開啟這個網頁時候,瀏覽器端送出的 HTTP Get 需求的相關封包內容

  • 現在,可以點選這個網頁左邊功能項目清單的任何項目 [Home] [Counter] [Fetch data] 連結

  • 此時,可以觀察 [開發人員工具] 內的 [網路] 分頁中的內容,是沒有任何變化的,不過,可以觀察到瀏覽器的位址列上的網址是有變化的,這似乎與其他開發工作所做出來的網站應用程式有所不同,Blazor Server 會有這樣的表現這是因為相關的頁面導航,都是透過 SignalR 來完成的,因此,不會每次都對遠端的 HTTP 伺服器發出 HTTP Request 請求。

  • 現在瀏覽器位址列輸入這個 https://localhost:5001/MyFirstBlazorPage 網址

  • 此時,可以從 [開發人員工具] 視窗中看到,左邊名稱欄位的第一筆紀錄是 [MyFirstBlazorPage],而且在右邊視窗中可以驗證此時瀏覽器發出了一個 HTTP Get 請求,路徑為 /MyFirstBlazorPage 到 localhost:5001 伺服器上

  • 同樣的,請點選該網頁最左邊的功能清單的任何項目,網頁一樣切換正常,並且同樣在 [開發人員工具] 視窗內沒有看到新的 HTTP 請求對 Web 伺服器送出

將 MyFirstBlazorPage 頁面加入到左邊功能表選項清單內

  • 請展開 Blazor 專案內的 [Shared] 資料夾

  • 在該資料夾內找到並且打開 [NavMenu.razor] 檔案

  • 找到 Counter 這個功能表清單選項的程式碼,如下圖第15行~第19行

  • 複製這段程式碼,並且修改成為 [MyFirstBlazorPage] 要使用的資訊

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="MyFirstBlazorPage">
                <span class="oi oi-plus" aria-hidden="true"></span> 我的第一個頁面
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
    </ul>
</div>
  • 請重新執行這個 Blazor 專案

  • 當瀏覽器出現後,可以在網頁的最左方看到四個功能表選項

  • 請切換這四個功能表項目,確認網頁會正常顯示相關內容,並且從 [開發人員工具] 視窗內不會看到其他的 HTTP 請求發出,也就是相關的網頁切換運作動作,都是透過 [SignalR] 來完成的。