2024年8月8日 星期四

使用Scaffolding功能建立Web API - 2



 .NET Magazine國際中文電子雜誌

作 者:許薰尹
審 稿:張智凱
文章編號:N240826401
出刊日期: 2024/8/7

本篇文章將延續本站《使用Scaffolding功能建立Web API - 1》一文的情境,介紹如何使用Visual Studio 2022的Scaffolding功能建立Web API來開發ASP.NET Core Web API,利用工具的圖型介面進行資料移轉以及測試Web API。


管理連結服務


Visual Studio 2022現在內建管理連結服務的功能,從「Solution Explorer」視窗,專案名稱下的「Connected Services」項目上方按滑鼠右鍵,從快捷選單選擇「Manage Connected Services」項目,請參考下圖所示:




圖 1:「Manage Connected Services」項目。


使用資料移轉功能


接下來會開啟一個畫面,讓你選擇要連線的服務,點選「SQL Server Express LocalDb (Local)」右方的「...」按鈕,從快捷選單選擇「Add migration」項目,請參考下圖所示:




圖 2:「Add migration」項目。


接下來會看到「Entity Framework Migrations」視窗,要求取一個名稱,例如本例中的「Initial」,並選取「MyAPIDemoContext」類別,然後按下「Finish」按鈕,請參考下圖所示:




圖 3:「Entity Framework Migrations」視窗。


接下來按下「Entity Framework Migrations」視窗「Finish」按鈕,關閉「Entity Framework Migrations」視窗,請參考下圖所示:




圖 4:關閉「Entity Framework Migrations」視窗。


接著Visual Studio 2022會在專案中「Migrations」資料夾內,產生一個「xxx_ Initial.cs」檔案,描述即將要對資料庫結構的異動,請參考以下程式碼:

 

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
using System;
using Microsoft.EntityFrameworkCore.Migrations;
 
#nullable disable
 
namespace MyAPIDemo.Migrations
{
    /// <inheritdoc />
    public partial class Initial : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Book",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Title = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
                    Price = table.Column<int>(type: "int", nullable: false),
                    PublishDate = table.Column<DateTime>(type: "datetime2", nullable: false),
                    InStock = table.Column<bool>(type: "bit", nullable: false),
                    Description = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Book", x => x.Id);
                });
        }
 
        /// <inheritdoc />
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Book");
        }
    }
}

 





此外專案根目錄下「MyAPIDemoContextModelSnapshot.cs」檔案內則包含了模型快照(Model Snapshot)程式碼,它記錄了資料庫模型的結構和配置。當你進行資料庫移轉時,Entity Framework Core 會根據這個模型快照來生成相應的 SQL 陳述句,以使資料庫的結構與應用程式的模型保持同步。

 

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
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MyAPIDemo.Data;
 
#nullable disable
 
namespace MyAPIDemo.Migrations
{
    [DbContext(typeof(MyAPIDemoContext))]
    partial class MyAPIDemoContextModelSnapshot : ModelSnapshot
    {
        protected override void BuildModel(ModelBuilder modelBuilder)
        {
#pragma warning disable 612, 618
            modelBuilder
                .HasAnnotation("ProductVersion", "8.0.4")
                .HasAnnotation("Relational:MaxIdentifierLength", 128);
 
            SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
 
            modelBuilder.Entity("MyAPIDemo.Book", b =>
                {
                    b.Property<int>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("int");
 
                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
 
                    b.Property<string>("Description")
                        .HasMaxLength(50)
                        .HasColumnType("nvarchar(50)");
 
                    b.Property<bool>("InStock")
                        .HasColumnType("bit");
 
                    b.Property<int>("Price")
                        .HasColumnType("int");
 
                    b.Property<DateTime>("PublishDate")
                        .HasColumnType("datetime2");
 
                    b.Property<string>("Title")
                        .IsRequired()
                        .HasMaxLength(50)
                        .HasColumnType("nvarchar(50)");
 
                    b.HasKey("Id");
 
                    b.ToTable("Book");
                });
#pragma warning restore 612, 618
        }
    }
}

 


專案中的「XXX_Initial.Designer.cs」檔案也是工具自動生成的,給開發工具使用,參考以下程式碼:

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
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MyAPIDemo.Data;
 
#nullable disable
 
namespace MyAPIDemo.Migrations
{
    [DbContext(typeof(MyAPIDemoContext))]
    [Migration("20240621074423_Initial")]
    partial class Initial
    {
        /// <inheritdoc />
        protected override void BuildTargetModel(ModelBuilder modelBuilder)
        {
#pragma warning disable 612, 618
            modelBuilder
                .HasAnnotation("ProductVersion", "8.0.4")
                .HasAnnotation("Relational:MaxIdentifierLength", 128);
 
            SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
 
            modelBuilder.Entity("MyAPIDemo.Book", b =>
                {
                    b.Property<int>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("int");
 
                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
 
                    b.Property<string>("Description")
                        .HasMaxLength(50)
                        .HasColumnType("nvarchar(50)");
 
                    b.Property<bool>("InStock")
                        .HasColumnType("bit");
 
                    b.Property<int>("Price")
                        .HasColumnType("int");
 
                    b.Property<DateTime>("PublishDate")
                        .HasColumnType("datetime2");
 
                    b.Property<string>("Title")
                        .IsRequired()
                        .HasMaxLength(50)
                        .HasColumnType("nvarchar(50)");
 
                    b.HasKey("Id");
 
                    b.ToTable("Book");
                });
#pragma warning restore 612, 618
        }
    }
}

 

 



更新資料庫


回到「Manage Connected Services」畫面,點選「SQL Server Express LocalDb (Local)」右方的「...」按鈕,從快捷選單選擇「Update database」項目以更新資料庫,請參考下圖所示:




圖 5: 更新資料庫。


下一步在「Entity Framework Migrations」視窗「DbContext class names」項目選取「MyAPIDemoContext」類別,然後按下「Finish」按鈕,請參考下圖所示:




圖 6:選取「MyAPIDemoContext」類別。


接下來按下「Entity Framework Migrations」視窗「Finish」按鈕,關閉「Entity Framework Migrations」視窗,請參考下圖所示:





圖 7:關閉「Entity Framework Migrations」視窗。


使用Swagger測試Web API


除了利用「MyAPIDemo.http」測試Web API的功能之外,預設專案也支援Swagger,只要執行網站,就會自動啟動瀏覽器,開啟Swagger UI讓你測試Web API,請參考下圖所示:




圖 8:使用Swagger測試Web API。



測試Web API功能


以下分別說明如何利用Swagger來測試Web API功能:


Post



使用Swagger送出HTTP Post請求,提供以下JSON格式的資料:

1
2
3
4
5
6
7
8
{
  "id": 0,
  "title": "Programming",
  "price": 123,
  "publishDate": "2024-06-21",
  "inStock": true,
  "description": "Programming book"
}



執行結果請參考下圖所示:




圖 9:測試Post功能。



參考下圖是送出HTTP Post請求新增一筆「Book」資料的執行結果:




圖 10:新增一筆「Book」資料。



Get


參考下圖是使用Swagger測試頁面送出HTTP Get請求取回所有圖書資料的執行結果:





圖 11:取回所有圖書資料。



參考下圖是送出HTTP Get請求取回「id」為「1」的「Book」資料之執行結果:




圖 12:取回「id」為「1」的「Book」資料。



Put


送出HTTP Put請求時,我們提供以下JSON格式的資料來修改「id」為「1」的「Book」資料:

1
2
3
4
5
6
7
8
{
  "id": 1,
  "title": "Programming Web API",
  "price": 1230,
  "publishDate": "2024-06-21",
  "inStock": true,
  "description": "Programming Web API book"
}



執行結果請參考下圖所示:




圖 13:送出HTTP Put請求。


當送出HTTP Put請求時,會得到一個500號例外錯誤,錯誤訊息如下:

Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot update identity column 'Id'.
at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)

例外錯誤的資訊,請參考下圖所示:




圖 14:HTTP Put產生500號例外錯誤。


從錯誤訊息中得知,當進行修改動作時,不允許修改「id」,讓我們修改工具產生的「BookEndpoints.cs」程式碼,將「SetProperty(m => m.Id, book.Id)」這行程式刪除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
group.MapPut("/{id}", async Task<Results<Ok, NotFound>> (int id, Book book, MyAPIDemoContext db) =>
        {
            var affected = await db.Book
                .Where(model => model.Id == id)
                .ExecuteUpdateAsync(setters => setters
                    .SetProperty(m => m.Id, book.Id)
                    .SetProperty(m => m.Title, book.Title)
                    .SetProperty(m => m.Price, book.Price)
                    .SetProperty(m => m.PublishDate, book.PublishDate)
                    .SetProperty(m => m.InStock, book.InStock)
                    .SetProperty(m => m.Description, book.Description)
                    );
            return affected == 1 ? TypedResults.Ok() : TypedResults.NotFound();
        })
        .WithName("UpdateBook")
        .WithOpenApi();

目前「MapPut」方法的程式碼看起來如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
group.MapPut("/{id}", async Task<Results<Ok, NotFound>> (int id, Book book, MyAPIDemoContext db) =>
        {
            var affected = await db.Book
                .Where(model => model.Id == id)
                .ExecuteUpdateAsync(setters => setters              
                    .SetProperty(m => m.Title, book.Title)
                    .SetProperty(m => m.Price, book.Price)
                    .SetProperty(m => m.PublishDate, book.PublishDate)
                    .SetProperty(m => m.InStock, book.InStock)
                    .SetProperty(m => m.Description, book.Description)
                    );
            return affected == 1 ? TypedResults.Ok() : TypedResults.NotFound();
        })
       .WithName("UpdateBook")
       .WithOpenApi();



完成之後,再使用Swagger測試,這次可以正確修改圖書資料,參考下圖是送出HTTP Put請求修改圖書資料的執行結果:




圖 15::Put請求執行結果。



Delete


參考下圖是送出HTTP Delete請求刪除「Book」資料的執行結果:




圖 16:送出HTTP Delete請求刪除圖書資料。



使用Endpoint explorer瀏覽與測試Web API


Visual Studio 2022還有一個小功能,能讓你很容易找尋、測試Web API端點。只要選取「View」>「Endpoints Explorer」開啟視窗,請參考下圖所示:




圖 17:開啟「Endpoints Explorer」視窗。



「Endpoints Explorer」視窗會自動偵測並列出中專案中的Web API端點,請參考下圖所示:




圖 18:自動偵測並列出中專案中的Web API端點。


你可以點選「Endpoints Explorer」視窗其中的請求項目,從快捷選單選擇「Generate Request」項目,就會自動開啟「*.http」檔案讓你進行測試,請參考下圖所示:





圖 19:「Generate Request」項目。



在開啟的「*.http」檔案中,會自動生成送出請求的指令,請參考以下程式碼:

1
2
3
4
5
6
7
8
9
10
@MyAPIDemo_HostAddress = https://localhost:7005
 
GET {{MyAPIDemo_HostAddress}}/weatherforecast/
Accept: application/json
 
###
 
GET {{MyAPIDemo_HostAddress}}/api/Book/
 
###



接著便可以點選指令上方的「Send Request」連結進行測試,請參考下圖所示:



圖 20:測試Web API。









👉👉👉索取微軟網站開發技術系列 優惠訊息與課程資料

0 意見:

張貼留言