2024年10月17日 星期四

使用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 意見:

張貼留言