使用scaffold功能設計Blazor應用程式- 2
作者:許薰尹
本篇文章將延續本站《使用scaffold功能設計Blazor應用程式 - 1》一文的情境,介紹如何使用Visual Studio 2022的Scaffolding功能建立Blazor CRUD頁面,利用工具的圖型介面進行資料移轉以及說明自動產生的程式碼用途。
使用EFC資料移轉建立資料庫
Visual Studio 2022提供一個圖型介面,不必記憶或輸入指令,就可以使用EFC資料移轉功能來建立資料庫。從「Solution Explorer」視窗 >專案名稱下方「Connected Services」項目上按滑鼠右鍵,從快捷選單選擇「Manage Connected Services」選項,請參考下圖所示:

圖 1:「Manage Connected Services」選項。
點選開啟的畫面「SQL Server Express LocalDB (local)」項目右方的「…」按鈕,從快捷選單選擇「Add Migraiton」選項,請參考下圖所示:

圖 2:使用EFC移轉 (EFC Migration)。
接下來會看到「Entity Frameowk Migration」視窗,在「Migration name」項目設定一個名稱,「DbContext class names」項目選取「BlazorScaffold.Data .BlazorScaffoldContext」,然後按「Finish」按鈕,請參考下圖所示:

圖 3:設定使用EFC移轉名稱。
接著按「Close」按鈕結束「Entity Frameowk Migration」畫面,請參考下圖所示:

圖 4:關閉「Entity Frameowk Migration」視窗。
Visual Studio 2022工具會自動在專案中產生一個「Migrations」資料夾,其中包含C#程式碼用來建立資料庫,請參考下圖所示:

圖 5:「Migrations」資料夾。
點選開啟的「Manage Connected Services」畫面,在「SQL Server Express LocalDB (local)」項目右方的「…」按鈕,從快捷選單選擇「Update Database」選項更新資料庫,請參考下圖所示:

圖 6:使用EFC移轉更新資料庫。
接著按「Finish」按鈕結束「Entity Frameowk Migration」畫面,請參考下圖所示:

圖 7:更新資料庫。
接著按「Close」按鈕結束「Entity Frameowk Migration」畫面,請參考下圖所示:

圖 8:關閉「Entity Frameowk Migration」視窗。
在Visual Studio 2022開發工具,按CTRL+F5執行網站,執行結果參考如下,選取「Employees」選項可以看到圖書清單的畫面,因為資料庫剛建立清單內容為空:

圖 9:「Index.razor」執行結果。
Visual Studio 2022開發工具會在「Pages」資料夾下建立一個「EmployeesPages」資料夾,其中包含CRUD操作頁面,分別說如下。
Index頁面
「Index」頁面的程式參考如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25  | @page "/employees"@using Microsoft.AspNetCore.Components.QuickGrid@inject BlazorScaffold.Data.BlazorScaffoldContext DB@using BlazorScaffold.Models<PageTitle> Index </PageTitle><h1> Index </h1><p>    <a href = "employees/create"> Create New </a></p><QuickGrid Class = "table" Items = "DB.Employees">    <PropertyColumn Property = "employee => employee.EmployeeName" />    <PropertyColumn Property = "employee => employee.BirthDay" />    <PropertyColumn Property = "employee => employee.IsMarried" />    <PropertyColumn Property = "employee => employee.Department" />    <TemplateColumn Context = "employee">        <a href = "@($"employees/edit?employeeid={employee.EmployeeId}")"> Edit </a> |        <a href = "@($"employees/details?employeeid={employee.EmployeeId}")"> Details </a> |        <a href = "@($"employees/delete?employeeid={employee.EmployeeId}")"> Delete </a>    </TemplateColumn></QuickGrid> | 
 
「Index.razor」用於顯示員工資料的頁面。首先,我們可以看到 「@page "/employees"」這行程式碼,表示這個元件的URL路由為「/employees」,當使用者在瀏覽器中輸入 /employees 時,這個元件將會顯示在瀏覽器中。
接下來,「@using」語句引入「Microsoft.AspNetCore.Components.QuickGrid」命名空間,「QuickGrid」是一個用於快速建立資料表格的元件。接著使用「@inject」指示詞透過相依性插入取得「BlazorScaffold.Data.BlazorScaffoldContext」物件用於存取資料庫。
「Create New」是一個超連結,它指向了「/employees/create」的 URL,這個連結用於導覽到建立新員工的「Create.razor」頁面。
在「QuickGrid」元件中,我們設定了一個 CSS 類別為「table」,這個類別來自於「Bootstrap」套件。將「Items」屬性設定為「DB.Employees」,這表示我們將使用表格來顯示「DB.Employees」中的資料。
在「QuickGrid」元件中,我們使用了「PropertyColumn」元件來定義資料表格的欄位。每個 「PropertyColumn」元件都有一個「Property」屬性,用於指定要顯示的屬性。在這個例子中,我們顯示了員工的姓名(EmployeeName)、生日(BirthDay)、婚姻狀態(IsMarried)和部門(Department)。
最後,使用了「TemplateColumn」元件來定義一個自訂的欄位。在這個欄位中,我們使用了 Razor 語法來動態產生超連結,這些連結分別指向編輯(Edit)、詳細資料(Details)和刪除(Delete)員工的頁面。每個連結都使用查詢字串傳遞了員工編號(EmployeeId)。
Create頁面
點選「Index」頁面上的「Create New」連結會顯示「Create.razor」以便新增資料,程式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58  | @page "/employees/create"@inject BlazorScaffold.Data.BlazorScaffoldContext DB@using BlazorScaffold.Models@inject NavigationManager NavigationManager<PageTitle> Create </PageTitle><h1> Create </h1><h4> Employee </h4><hr /><div class = "row">    <div class = "col-md-4">        <EditForm method = "post" Model = "Employee" OnValidSubmit = "AddEmployee" FormName = "create" Enhance>            <DataAnnotationsValidator />            <ValidationSummary class = "text-danger" />            <div class = "mb-3">                <label for = "employeename" class = "form-label"> EmployeeName: </label>                 <InputText id = "employeename" @bind-Value = "Employee.EmployeeName" class = "form-control" />                 <ValidationMessage For = "() => Employee.EmployeeName" class = "text-danger" />             </div>                    <div class = "mb-3">                <label for = "birthday" class = "form-label"> BirthDay: </label>                 <InputDate id = "birthday" @bind-Value = "Employee.BirthDay" class = "form-control" />                 <ValidationMessage For = "() => Employee.BirthDay" class = "text-danger" />             </div>                    <div class = "mb-3">                <label for = "ismarried" class = "form-label"> IsMarried: </label>                 <InputCheckbox id = "ismarried" @bind-Value = "Employee.IsMarried" class = "form-check-input" />                 <ValidationMessage For = "() => Employee.IsMarried" class = "text-danger" />             </div>                    <div class = "mb-3">                <label for = "department" class = "form-label"> Department: </label>                 <InputText id = "department" @bind-Value = "Employee.Department" class = "form-control" />                 <ValidationMessage For = "() => Employee.Department" class = "text-danger" />             </div>                    <button type = "submit" class = "btn btn-primary"> Create </button>        </EditForm>    </div></div><div>    <a href = "/employees"> Back to List </a></div>@code {    [SupplyParameterFromForm]    public Employee Employee { get; set; } = new();    // To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD    public async Task AddEmployee()    {        DB.Employees.Add( Employee );        await DB.SaveChangesAsync();        NavigationManager.NavigateTo( "/employees" );    }} | 
「Create」元件於建立新的員工資料。它包含了一個表單(EditForm),讓使用者輸入員工的相關資訊,並在提交表單後將資料新增到資料庫中。
和「Index」元件一樣使用「@inject」指示詞透過相依性插入取得「BlazorScaffold.Data.BlazorScaffoldContext」用於存取資料庫;而「NavigationManager」用於導覽到其他頁面。
「EditForm」是 Blazor 的內建元件,用於建立表單。它的「Model」屬性繫結到「Employee」物件,表示表單的資料模型是「Employee」型別。「OnValidSubmit」屬性指定了當表單驗證通過且提交時要執行的方法,這裡是「AddEmployee」方法。「FormName」屬性設定了表單的名稱為「create」。
「EditForm」的「Enhance」屬性的值設定為「True」,這表示表單提交時,不會完全重新載入頁面,但這個設定只適用於伺服器端轉譯 (SSR)的情境。
在「EditForm」元件內部,使用「label」顯示欄位的標籤,「InputText」、「InputDate」 和 「InputCheckbox」元件用於輸入文字、日期、布林值等不同類型的輸入方塊。「@bind-Value」屬性用於將輸入方塊的值繫結到「Employee」物件的對應屬性。「ValidationMessage」元件用於顯示驗證錯誤訊息。
最後,「button」按鈕用於提交表單,並叫用「AddEmployee」方法新增資料到資料庫中。
在「@code」程式區塊中,定義了「Employee」屬性,並使用 「SupplyParameterFromForm」屬性來指示 Blazor 從表單中將欄位值填到「Employee」物件對應的屬性。「AddEmployee」方法則利用Entity Framework Core將「Employee」物件新增到資料庫中,再透過「NavigationManager」導「NavigationManager」到員工列表頁面。
這個元件的執行結果請參考下圖所示:

圖 10:資料新增。
新增資料後回到員工列表頁面,請參考下圖所示:

圖 11:新增資料後回到員工列表。
Details頁面
點選員工列表(Index)中任一筆資料的「Details」連結會導覽到「Details.razor」元件,程式參考如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51  | @page "/employees/details"@inject BlazorScaffold.Data.BlazorScaffoldContext DB@using BlazorScaffold.Models@inject NavigationManager NavigationManager@using Microsoft.EntityFrameworkCore<PageTitle> Details </PageTitle><h1> Details </h1><div>    <h4> Employee </h4>    <hr />    @if ( employee is null )    {        <p><em> Loading... </em></p>    }    else {        <dl class = "row">            <dt class = "col-sm-2"> EmployeeName </dt>            <dd class = "col-sm-10"> @employee.EmployeeName </dd>            <dt class = "col-sm-2"> BirthDay </dt>            <dd class = "col-sm-10"> @employee.BirthDay </dd>            <dt class = "col-sm-2"> IsMarried</dt>            <dd class = "col-sm-10"> @employee.IsMarried </dd>            <dt class = "col-sm-2"> Department </dt>            <dd class = "col-sm-10"> @employee.Department </dd>        </dl>        <div>            <a href = "@($"/employees/edit?employeeid={employee.EmployeeId}")"> Edit </a> |            <a href = "@($"/employees")"> Back to List </a>        </div>    }</div>@code {    Employee? employee;    [SupplyParameterFromQuery]    public int EmployeeId { get; set; }    protected override async Task OnInitializedAsync()    {        employee = await DB.Employees.FirstOrDefaultAsync( m => m.EmployeeId == EmployeeId );        if ( employee is null )        {            NavigationManager.NavigateTo( "notfound" );        }    }} | 
「「Details.razor」元件用於顯示員工詳細資訊的頁面。元件使用了條件語句「@if (employee is null)」來檢查員工是否為空值。如果員工為空值,則顯示「Loading」的訊息;否則,顯示員工的詳細資訊。
元件使用了「dl」和「dt」、「dd」 HTML元素來顯示員工的姓名(EmployeeName)、生日(BirthDay)、婚姻狀態(IsMarried)和部門(Department)。
「@code」程式碼區塊包含了元件的程式邏輯。它定義了一個可為空值的「employee」變數,並使用 「SupplyParameterFromQuery」 屬性將「EmployeeId」參數從查詢字串繫結到這個變數。
「OnInitializedAsync」方法使用「DB.Employees.FirstOrDefaultAsync」方法從資料庫中查詢員工資訊並將結果放到「employee」變數。如果員工為空值,則使用「NavigationManager.NavigateTo」方法導覽到「notfound」頁面。
這個元件執行結果請參考下圖所示:

圖 12:「Details」元件。
Edit頁面
「Edit.razor」元件用來編輯員工資料,程式參考如下:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101  | @page "/Employees/edit"@inject BlazorScaffold.Data.BlazorScaffoldContext DB@using BlazorScaffold.Models@inject NavigationManager NavigationManager@using Microsoft.EntityFrameworkCore<PageTitle> Edit </PageTitle><h1> Edit </h1><h4> Employee </h4><hr />@if ( Employee is null ){    <p><em> Loading... </em></p>}else{    <div class = "row">        <div class = "col-md-4">            <EditForm method = "post" Model = "Employee" OnValidSubmit = "UpdateEmployee" FormName = "edit" Enhance>                <DataAnnotationsValidator />                <ValidationSummary />                <input type = "hidden" name = "Employee.EmployeeId" value = "@Employee.EmployeeId" />                <div class = "mb-3">                    <label for = "employeename" class = "form-label"> EmployeeName: </label>                    <InputText id = "employeename" @bind-Value = "Employee.EmployeeName" class = "form-control" />                    <ValidationMessage For = "() => Employee.EmployeeName" class = "text-danger" />                </div>                <div class = "mb-3">                    <label for = "birthday" class = "form-label"> BirthDay: </label>                    <InputDate id = "birthday" @bind-Value = "Employee.BirthDay" class = "form-control" />                    <ValidationMessage For = "() => Employee.BirthDay" class = "text-danger" />                </div>                <div class = "mb-3">                    <label for = "ismarried" class = "form-label"> IsMarried: </label>                    <InputCheckbox id = "ismarried" @bind-Value="Employee.IsMarried" class = "form-check-input" />                    <ValidationMessage For = "() => Employee.IsMarried" class = "text-danger" />                </div>                <div class = "mb-3">                    <label for = "department" class = "form-label"> Department: </label>                    <InputText id = "department" @bind-Value = "Employee.Department" class = "form-control" />                    <ValidationMessage For = "() => Employee.Department" class = "text-danger" />                </div>                <button type = "submit" class = "btn btn-primary"> Save </button>            </EditForm>        </div>    </div>}<div>    <a href = "/employees">Back to List</a></div>@code {    [SupplyParameterFromQuery]    public int EmployeeId { get; set; }    [SupplyParameterFromForm]    public Employee? Employee { get; set; }    protected override async Task OnInitializedAsync()    {        Employee ??= await DB.Employees.FirstOrDefaultAsync( m => m.EmployeeId == EmployeeId );        if ( Employee is null )        {            NavigationManager.NavigateTo( "notfound" );        }    }    // To protect from overposting attacks, enable the specific properties you want to bind to.    // For more details, see https://aka.ms/RazorPagesCRUD.    public async Task UpdateEmployee()    {        DB.Attach( Employee! ).State = EntityState.Modified;        try        {            await DB.SaveChangesAsync();        }        catch (DbUpdateConcurrencyException)        {            if ( !EmployeeExists( Employee!.EmployeeId ) )            {                NavigationManager.NavigateTo( "notfound" );            }            else            {                throw;            }        }        NavigationManager.NavigateTo( "/employees" );    }    bool EmployeeExists( int employeeid )    {        return DB.Employees.Any( e => e.EmployeeId == employeeid );    }} | 
「Edit.razor」元件用於編輯員工資料,程式的HTML樣板結構和「Create」元件雷同,包含了一個表單,可以顯示和編輯員工的姓名(EmployeeName)、生日(BirthDay)、婚姻狀態(IsMarried)和部門(Department)。
在「@code」程式碼區塊中定義了一個「EmployeeId」屬性,並使用 「SupplyParameterFromQuery」 屬性來從查詢參數中獲取員工的 ID。同樣地定義了一個可為空值的「Employee」屬性,並使用 「SupplyParameterFromForm」屬性來從表單中獲取員工的資料。
在「OnInitializedAsync」方法中使用「DB.Employees.FirstOrDefaultAsync」方法從資料庫中獲取指定 ID 的員工資料,並將其放到「Employee」屬性。如果找不到員工,則導覽到「notfound」頁面。
在表單的「OnValidSubmit」事件叫用「UpdateEmployee」方法,將「Employee」物件的狀態設定為「EntityState.Modified」,表示該員工資料已被修改。然後使用「DB.SaveChangesAsync」方法將修改後的員工資料保存到資料庫之中。如果在儲存資料的過程中發生「DbUpdateConcurrencyException」例外錯誤,便檢查員工是否仍存在於資料庫之中,如果不存在,則導覽到「notfound」頁面。最後,無論保存是否成功,它都導覽回到「/employees」頁面。
這個元件執行結果請參考下圖所示:

圖 13:資料修改。
Delete頁面
「Delete.razor」元件用來確認刪除員工資料,程式參考如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65  | @page "/employees/delete"@inject BlazorScaffold.Data.BlazorScaffoldContext DB@using BlazorScaffold.Models@inject NavigationManager NavigationManager@using Microsoft.EntityFrameworkCore<PageTitle> Delete </PageTitle><h1> Delete </h1><h3> Are you sure you want to delete this? </h3><div>    <h4> Employee </h4>    <hr />    @if ( employee is null )    {        <p><em> Loading... </em></p>    }    else {        <dl class = "row">            <dt class = "col-sm-2"> EmployeeName </dt>            <dd class = "col-sm-10"> @employee.EmployeeName </dd>        </dl>        <dl class = "row">            <dt class = "col-sm-2"> BirthDay </dt>            <dd class = "col-sm-10"> @employee.BirthDay </dd>        </dl>        <dl class = "row">            <dt class = "col-sm-2"> IsMarried</dt>            <dd class = "col-sm-10"> @employee.IsMarried </dd>        </dl>        <dl class = "row">            <dt class = "col-sm-2"> Department </dt>            <dd class = "col-sm-10"> @employee.Department </dd>        </dl>        <EditForm method = "post" Model = "employee" OnValidSubmit = "DeleteEmployee" FormName = "delete" Enhance>            <button type = "submit" class = "btn btn-danger" disabled = "@(employee is null)"> Delete </button> |            <a href = "/employees"> Back to List </a>        </EditForm>    }</div>@code {    Employee? employee;    [SupplyParameterFromQuery]    public int EmployeeId { get; set; }    protected override async Task OnInitializedAsync()    {        employee = await DB.Employees.FirstOrDefaultAsync( m => m.EmployeeId == EmployeeId );        if ( employee is null )        {            NavigationManager.NavigateTo( "notfound" );        }    }    public async Task DeleteEmployee()    {        DB.Employees.Remove( employee! );        await DB.SaveChangesAsync();        NavigationManager.NavigateTo( "/employees" );    }} | 
「Delete.razor」元件用刪除資料的操作,它提供了一個用於刪除員工的介面,並使用「BlazorScaffoldContext」類別來存取資料庫。這個元件上半部程式碼顯示了員工的詳細資訊,包含姓名(EmployeeName)、生日(BirthDay)、婚姻狀態(IsMarried)和部門(Department)。
「Delete.razor」元件下半段程式碼則使用「EditForm」元件來建立一個表單,該表單的「Model」屬性繫結到「employee」變數,並在提交表單時叫用「DeleteEmployee」方法從資料庫中刪除員工,並在刪除完成後導覽回 "/employees" 頁面。
這個元件執行結果請參考下圖所示:

圖 14:刪除員工資料。
0 意見:
張貼留言