2021年6月21日 星期一

Blazor Server 快速開發專案樣板 6 - 動態功能表

 

Blazor Server 快速開發專案樣板 6 - 動態功能表



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

上一篇的文章 : Blazor Server 快速開發專案樣板 5 - 使用者身分驗證與授權

這一篇文章所討論到的需求,是大多數 Blazor 開發者想要知道開發技能,但是,當使用 Visual Studio 建立 Blazor Server 專案的時候,這個預設專案範本僅提供的靜態功能表的設計方式,因此,在這篇文章將會說明如何設計出這樣的功能。

在這個 [Blazor Server 快速開發專案樣板] 中,將會設計三種功能表角色 (當然,開發者可以根據本身不同需求,自行擴增),分別是 [開發者角色]、[系統管理員角色]、[使用者角色],若使用不同的使用者登入 ( god , admin , user1 而密碼皆為 123 ),將會看到不同的功能表清單。

  • 具有 [開發者角色] 可以看到的功能表清單

  • 具有 [系統管理員角色] 可以看到的功能表清單

  • 具有 [使用者角色] 可以看到的功能表清單

資料庫需要設計的資料表

對於想要設計這樣的功能,會將不同功能表角色與相關功能表項目清單資訊儲存在資料表內,在這個 [Blazor Server 快速開發專案樣板] 內,將會設計底下資料表關係

其中

  • MyUser

    這裡儲存每個使用者的紀錄

  • MenuRole

    這裡是儲存每個使用者可以綁定的功能表角色紀錄

  • MenuData

    每個功能表角色紀錄所對應的功能表項目,都會儲存在這個資料表內

上面的三個資料表的 SQL 定義如下

  • MyUser
CREATE TABLE [dbo].[MyUser] (
    [Id]         INT            IDENTITY (1, 1) NOT NULL,
    [Account]    NVARCHAR (MAX) NOT NULL,
    [Password]   NVARCHAR (MAX) NOT NULL,
    [Name]       NVARCHAR (MAX) NOT NULL,
    [Salt]       NVARCHAR (MAX) NULL,
    [Status]     BIT            NOT NULL,
    [MenuRoleId] INT            NOT NULL,
    CONSTRAINT [PK_MyUser] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_MyUser_MenuRole_MenuRoleId] FOREIGN KEY ([MenuRoleId]) REFERENCES [dbo].[MenuRole] ([Id])
);


GO
CREATE NONCLUSTERED INDEX [IX_MyUser_MenuRoleId]
    ON [dbo].[MyUser]([MenuRoleId] ASC);
  • MenuRole
CREATE TABLE [dbo].[MenuRole] (
    [Id]     INT            IDENTITY (1, 1) NOT NULL,
    [Name]   NVARCHAR (MAX) NOT NULL,
    [Remark] NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_MenuRole] PRIMARY KEY CLUSTERED ([Id] ASC)
);
  • MenuData
CREATE TABLE [dbo].[MenuData] (
    [Id]         INT            IDENTITY (1, 1) NOT NULL,
    [Name]       NVARCHAR (MAX) NOT NULL,
    [Level]      INT            NOT NULL,
    [IsGroup]    BIT            NOT NULL,
    [Sequence]   INT            NOT NULL,
    [Icon]       NVARCHAR (MAX) NULL,
    [CodeName]   NVARCHAR (MAX) NOT NULL,
    [Enable]     BIT            NOT NULL,
    [ForceLoad]  BIT            NOT NULL,
    [MenuRoleId] INT            NOT NULL,
    CONSTRAINT [PK_MenuData] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_MenuData_MenuRole_MenuRoleId] FOREIGN KEY ([MenuRoleId]) REFERENCES [dbo].[MenuRole] ([Id])
);


GO
CREATE NONCLUSTERED INDEX [IX_MenuData_MenuRoleId]
    ON [dbo].[MenuData]([MenuRoleId] ASC);

設計功能表清單 UI

Visual Studio 預設的 Blazor 專案範本會在 [Shared] 資料夾內建立一個 [NavMenu.razor] 檔案,這是一個 [Razor 元件],這裡將會使用靜態的方式來定義這個系統可以使用的功能表項目。

而在這個 [NavMenu.razor] 元件將會在 [Shared] > [MainLayout.razor] 元件內來使用,底下將會是 [MainLayout.razor] 元件的程式碼

@inherits LayoutComponentBase

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

    <div class="main">
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

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

因此,在這裡將會額外在 [Shared] 設計一個 [NavDynamicMenu.razor] 動態功能表清單元件,這個元件同樣會在 [MainLayout.razor] 元件內來參考使用。

在這個 [NavDynamicMenu.razor] 動態功能表清單元件內,使用 List<MainMenu> mainMenus = new List<MainMenu>(); 屬性來儲存要顯示的功能表清單項目。

對於 [mainMenus] 這個屬性的集合物件值,將會在 Blazor 生命週期的 [OnAfterRenderAsync] 事件內,使用 menuHelper.MakeMenuObjectAsync 方法來取得,不過,首先需要使用 AuthenticationStateProvider.GetAuthenticationStateAsync().User 這個物件來取得當前登入的使用者是哪個使用者,不過,先要檢查這個 user.Identity.IsAuthenticated 屬性來確認現在開啟的網頁,是否已經成功登入,完成身分驗證的程序。

若使用者已經成功登入這個系統,則會使用 int menuRoleId = Convert.ToInt32(user.FindFirst(c => c.Type == MagicHelper.MenuRoleClaim)?.Value); 方法來取得這名使用者所配置的功能表角色紀錄 Id,並且透過 var foo = user.FindFirst(c => c.Type == System.Security.Claims.ClaimTypes.Role && c.Value == MagicHelper.開發者的角色聲明); // 確認這個使用者為系統管理者 敘述來判斷這個使用者是否具有 開發者的角色聲明 ,因為若這個使用者為 開發者 專用的帳號,則接下來將會額外產生的功能表項目。

最後,將會使用 mainMenus = await menuHelper.MakeMenuObjectAsync(foo == null ? false : true); 敘述來取得該使用者可以看得到的功能表項目清單設定到 mainMenus 屬性內,之後就會透過 Blazor 的 資料綁定 Data Binding 技術,將這名使用者可以看到的功能表項目清單顯示在網頁的最左方。

從資料庫取得使用者可以用的功能表項目清單

想要做到從資料庫取得使用者可以用的功能表項目清單,這樣的需求將會設計在 [Helpers] 資料夾內 [MenuHelper.cs] 檔案,底下將會是這個方法內的原始碼。

public async Task<List<MainMenu>> MakeMenuObjectAsync(bool sHttc)
{
    #region 讀取該使用者角色的所有功能表清單
    var dataRequest = new DataRequest()
    {
        Sorted = new SortCondition()
        {
            Id = (int)MenuDataSortEnum.Default,
            Title = "預設"
        },
        Skip = 0,
        Take = int.MaxValue
    };
    var allMenuData = await MenuDataService.GetByHeaderIDAsync(RoleId, dataRequest);
    var menuDatas = allMenuData.Result;
    menuDatas = menuDatas
        .Where(x => x.Enable == true)
        .OrderBy(x => x.Sequence).ToList();
    #endregion
    List<MainMenu> mainMenus = new();
    MainMenu mainMenu = new();
    SubMenu subMenu = new();
    #region 依據資料庫內的紀錄,產生要顯示的功能表物件
    foreach (var item in menuDatas)
    {
        if (item.Level == 0)
        {
            #region 第一層功能清單
            mainMenu = new MainMenu()
            {
                IsSubMenu = item.IsGroup,
                IsExpand = false,
                MenuData = item
            };
            mainMenus.Add(mainMenu);
            #endregion
        }
        else
        {
            #region 第二層功能清單
            subMenu = new SubMenu()
            {
                MenuData = item
            };
            mainMenu.SubMenus.Add(subMenu);
            #endregion
        }
    }
    #endregion
    #region 加入鴻才管理者可以使用的功能表清單
    if (isHttc == true)
    {
        mainMenu = new MainMenu()
        {
            IsSubMenu = true,
            IsExpand = false,
            MenuData = new()
            {
                Level = 0,
                Name = "系統管理者",
                Enable = true,
                ForceLoad = false,
                Icon = "mdi-apple-icloud",
                CodeName = "mdi-apple-icloud",
                IsGroup = true,
            }
        };
        mainMenus.Add(mainMenu);
        #region 第二層功能清單
        subMenu = new SubMenu()
        {
            MenuData = new MenuDataAdapterModel()
            {
                Name = ShareBusiness.Helpers.MagicHelper.功能表角色功能名稱,
                CodeName = "MenuRole",
                Enable = true,
                Icon = "mdi-menu",
                IsGroup = false,
                Level = 1,
            }
        };
        mainMenu.SubMenus.Add(subMenu);
        subMenu = new SubMenu()
        {
            MenuData = new MenuDataAdapterModel()
            {
                Name = ShareBusiness.Helpers.MagicHelper.系統日誌功能名稱,
                CodeName = "SystemLog",
                Enable = true,
                Icon = "mdi-message-processing",
                IsGroup = false,
                Level = 1,
            }
        };
        mainMenu.SubMenus.Add(subMenu);
        subMenu = new SubMenu()
        {
            MenuData = new MenuDataAdapterModel()
            {
                Name = ShareBusiness.Helpers.MagicHelper.Excel匯入功能名稱,
                CodeName = "Import",
                Enable = true,
                Icon = "mdi-database-import",
                IsGroup = false,
                Level = 1,
            }
        };
        mainMenu.SubMenus.Add(subMenu);
        #endregion
    }
    #endregion  
    #region 加入登出按鈕
    if (mainMenus.Count > 0)
    {
        mainMenu = new MainMenu()
        {
            IsSubMenu = false,
            IsExpand = false,
            MenuData = new()
            {
                Level = 0,
                Name = "登出",
                Enable = true,
                ForceLoad = true,
                Icon = "mdi-logout",
                CodeName = "/Logout",
                IsGroup = false,
            }
        };
        mainMenus.Add(mainMenu);
    }
    #endregion
    return mainMenus;
}

在這個 #region 讀取該使用者角色的所有功能表清單 ... #endregion 區段內,根據該使用者的功能表角色 Id ,使用 MenuDataService.GetByHeaderIDAsync 方法,取得這個功能表角色內擁有的功能表項目紀錄;這裡將會取得一個 [MenuData] 集合物件,接著,把排除尚未啟用的 MenuData 紀錄。

在接下來的 #region 依據資料庫內的紀錄,產生要顯示的功能表物件 ... #endregion 區段內,則會將從資料庫取得的紀錄,轉換成為要顯示在畫面上的集合物件。

最重要的是,若這個使用者為 開發者 帳號,則在 #region 加入鴻才管理者可以使用的功能表清單 ... #endregion 區段內,額外加入專屬於開發者帳號才能夠看到的功能項目,也就是說這些功能項目不會定義在資料庫內,完全定義在程式碼內;在這裡的程式碼首先會加入一個功能表項目節點 [系統管理者],這是一個子功能表項目,也就是說,若點選這個 [系統管理者] 功能表項目,不會導航到任何的頁面,而是會展開一個子功能表清單,這裡將會出現 [功能表角色] 、 [系統日誌] 、 [Excel匯入];因此,日後若需要擴充 開發者 帳號才能夠看到的功能表選項,便可以要在這裡擴充相關程式碼。

另外,對於這些專屬於 開發者 帳號才能夠看到的功能表項目,在設計這些頁面的時候,需要宣告要具備 developer 這樣的聲明在該頁面做前面,例如,底下將會是位於 [Pages] > [MenuRolePage.razor] 檔案內容

@page "/MenuRole"
@attribute [Authorize(Roles = ShareBusiness.Helpers.MagicHelper.開發者的角色聲明)]

<div>
    <div class="page-title">@ShareBusiness.Helpers.MagicHelper.功能表角色功能名稱</div>
    <MenuRoleView PageSize="@MagicHelper.GridPageSize" />
</div>

最後,將會使用 #region 加入登出按鈕 ... #endregion 區段程式碼,為每個使用者加入 [登出] 功能表選項。











2021年6月19日 星期六

ASP.NET Core 應該具備知識 - 從空白專案建立 Blazor 應用

ASP.NET Core 應該具備知識 - 從空白專案建立 Blazor 應用


ASP.NET Core 應該具備知識

ASP.NET Core 應用程式啟動與 Startup 類別

ASP.NET Core 靜態檔案 UseStaticFiles

ASP.NET Core 相依性注入設計模式 Dependency Injection

ASP.NET Core 中介軟體 Middleware

ASP.NET Core 設定 Configuration

ASP.NET Core 選項模式 IOptions

ASP.NET Core 日誌記錄 Logger

ASP.NET Core 從空白專案建立 Blazor 應用

建立一個 空白 ASP.NET Core 專案

  • 開啟 Visual Studio 2019

  • 在 [Visual Studio 2019] 對話窗中,點選右下方的 [建立新的專案] 選項

  • 在 [建立新專案] 對話窗中,在中間上方的專案範本過濾條件中

    1. 設定程式語言為 [C#]
    2. 設定專案範本為 [Web]
    3. 選擇專案範本項目清單,點選 [空白 ASP.NET Core] 這個專案範本項目
    4. 點選右下方的 [下一步] 按鈕

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

    在 [專案名稱] 內,輸入 AC99

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

  • 在 [其他資訊] 對話窗出現後,確認 [目標 Framework] 的下拉選單要選擇 [.NET 5.0 (目前)]

  • 點選右下角的 [建立] 按鈕

  • 此時這個 [ASP.NET Core] 專案已經建立完成,從方案總管視窗內可以看到如下圖的結構

修正成為 Blazor 專案 - 啟動服務的設定與註冊

  • 打開專案根目錄下 [Startup.cs] 檔案
  • 搜尋找到 [ConfigureServices] 方法
  • 這個方法內沒有任何的敘述
  • 加入底下兩行 Blazor 需要用到的相依性注入宣告
#region 這裡加入 Blazor 需要用到的相依性注入宣告
services.AddRazorPages();
services.AddServerSideBlazor();
#endregion
  • 搜尋找到 [Configure] 方法
  • 找到這個 app.UseRouting(); 敘述
  • 在這個敘述之前,要加入這個敘述 app.UseStaticFiles();,以便可以執行 SignalR 的 JavaScript 程式
#region 要加入這個敘述,以便可以執行 SignalR 的 JavaScript 程式
app.UseStaticFiles();
#endregion
  • 找到這個 app.UseEndpoints 敘述
  • 使用底下的敘述用來啟動 SignalR 服務與註冊最終路由端點
app.UseEndpoints(endpoints =>
{
    #region 啟動 SignalR 服務與註冊最終路由端點
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
    #endregion
});

修正成為 Blazor 專案 - 建立需要用到的基本檔案

  • 滑鼠右擊專案節點

  • 在彈出功能表中,選擇 [加入] > [新增項目]

  • 出現 [新增項目 - AC99] 對話窗

  • 依序點選 [已安裝] > [Visual C#] > [ASP.NET Core] > [Web] 節點

  • 在中間的檔案類型清單中,點選 [Razor 元件]

  • 在底下 [名稱] 欄位中輸入 _Imports.razor

  • 最後點選 [新增] 按鈕

  • 將底下的程式碼替換掉這個檔案內的內容

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AC99
@using AC99.Shared
  • 滑鼠右擊專案節點
  • 在彈出功能表中,選擇 [加入] > [新增項目]
  • 出現 [新增項目 - AC99] 對話窗
  • 依序點選 [已安裝] > [Visual C#] > [ASP.NET Core] > [Web] 節點
  • 在中間的檔案類型清單中,點選 [Razor 元件]
  • 在底下 [名稱] 欄位中輸入 App.razor
  • 最後點選 [新增] 按鈕
  • 將底下的程式碼替換掉這個檔案內的內容
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
    <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>
  • 滑鼠右擊專案節點

  • 在彈出功能表中,選擇 [加入] > [新增資料夾]

  • 命名這個資料夾為 [Shared]

  • 滑鼠右擊 [Shared] 資料夾節點

  • 在彈出功能表中,選擇 [加入] > [Razor 元件]

  • 在底下 [名稱] 欄位中輸入 MainLayout.razor

  • 最後點選 [新增] 按鈕

  • 將底下的程式碼替換掉這個檔案內的內容

@inherits LayoutComponentBase

<div class="page">
    <div class="main">
        <div class="content px-4">
            @Body
        </div>
    </div>
</div>
  • 滑鼠右擊專案節點

  • 在彈出功能表中,選擇 [加入] > [新增資料夾]

  • 命名這個資料夾為 [Pages]

  • 滑鼠右擊 [Pages] 資料夾節點

  • 在彈出功能表中,選擇 [加入] > [Razor 頁面]

  • 當出現 [新增 Scaffold 項目] 對話窗

  • 點選 [Razor 頁面 - 空白] 這個項目

  • 點選 [加入] 按鈕

  • 當出現 [新增項目 AC99] 對話窗

  • 在底下 [名稱] 欄位中輸入 _Host.cshtml

  • 最後點選 [新增] 按鈕

  • 打開 [_Host.cshtml] 檔案

  • 將底下的程式碼替換掉這個檔案內的內容

@page "/"
@namespace AC99.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AC99</title>
    <base href="~/" />
</head>
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />
    <script src="_framework/blazor.server.js"></script>
</body>
</html>
  • 滑鼠右擊 [Pages] 資料夾節點
  • 在彈出功能表中,選擇 [加入] > [Razor 元件]
  • 在底下 [名稱] 欄位中輸入 Index.razor
  • 最後點選 [新增] 按鈕
  • 將底下的程式碼替換掉這個檔案內的內容
@page "/"

<h1>Hello, 這是從空白 ASP.NET Core 專案範本來建立一個 Blazor 專案!</h1>

<h2>體驗 ASP.NET Core 與 Blazor UI 開發框架的關係</h2>

<div>
    <span>你的名字</span>
    <input @bind="Name"/>
</div>
<div>
    <button @onclick="Submit">送出</button>
</div>
<div>
    @Message
</div>

@code{
    public string Name { get; set; }
    public string Message { get; set; }
    void Submit()
    {
        Message = $"你好 {Name}";
    }

}

執行這個專案

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

  • 瀏覽器的畫面如下

  • 請在 [你的名字] 欄位中輸入的的名字

  • 最後,點選 [送出] 按鈕

  • 此時,可以在送出按鈕的下方看到向你問好的文字

  • 到了這裡可以證明,原先 建立一個 空白 ASP.NET Core 專案,經過稍加修正,已經可以進行 Blazor 專案開發了