使用Scaffolding功能建立Web API - 1
作 者:許薰尹
審 稿:張智凱
文章編號:N240726302
出刊日期: 2024/7/24
這篇文章將介紹Visual Studio 2022 開發ASP.NET Core Web API提供的一些功能,例如*.http檔案、Swagger、Scaffolding功能與Endpoint explorer瀏覽與測試Web API。
建立「ASP.NET Core Web API」專案
首先使用Visual Studio 2022開發工具建立一個「ASP.NET Core Web API」專案,建立步驟如下:啟動Visual Studio 2022開發環境,從「開始」視窗選取「Create a new project」選項。
圖 1:建立「ASP.NET Core Web API」專案。
從Visual Studio 2022開發工具的「Create a new project」對話盒中,選取 使用C# 語法的「ASP.NET Core Web API」項目,然後按一下「Next」按鈕,請參考下圖所示:
圖 2:「ASP.NET Core Web API」項目。
下一步,在「Configure your new project」視窗中,設定ASP.NET Core Web API專案名稱與專案存放路徑,然後按下「Next」按鈕,請參考下圖所示:
圖 3:設定ASP.NET Core Web API專案名稱與專案存放路徑。
下一步,在「Additional information」視窗中,設定「Target Framework」為「NET 8.0 (Long Term Support)」;選擇「Configure for HTTPS」與「Enable OpenAI Support」,然後按下「Create」按鈕,請參考下圖所示:
圖 4:設定「Target Framework」為「NET 8.0 (Long Term Support)」。
當專案建立完成之後,檢視「Program.cs」檔案,包含以下範例程式碼,其中有一個「weatherforecast」服務可以取得天氣狀況,並支援「Swagger」以產生文件和服務測試頁面:
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 | var builder = WebApplication.CreateBuilder( args ); // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer( ); builder.Services.AddSwaggerGen( ); var app = builder.Build( ); // Configure the HTTP request pipeline. if ( app.Environment.IsDevelopment( ) ) { app.UseSwagger( ); app.UseSwaggerUI( ); } app.UseHttpsRedirection( ); var summaries = new [ ] { "Freezing" , "Bracing" , "Chilly" , "Cool" , "Mild" , "Warm" , "Balmy" , "Hot" , "Sweltering" , "Scorching" }; app.MapGet( "/weatherforecast" , () => { var forecast = Enumerable.Range( 1, 5 ).Select( index => new WeatherForecast ( DateOnly.FromDateTime( DateTime.Now.AddDays( index ) ), Random.Shared.Next( -20, 55 ), summaries[ Random.Shared.Next( summaries.Length ) ] ) ) .ToArray( ); return forecast; } ) .WithName( "GetWeatherForecast" ) .WithOpenApi( ); app.Run( ); internal record WeatherForecast( DateOnly Date, int TemperatureC, string ? Summary ) { public int TemperatureF => 32 + ( int ) ( TemperatureC / 0.5556 ); } |
專案根目錄中也包含一個「專案名稱.http」檔案,例如本文範例的檔案名稱為「MyAPIDemo.http」,參考程式碼如下,它是一種在IDE或支援REST客戶端功能的程式開發工具中,使用來送出HTTP請求的檔案。這類檔案允許開發者直接從他們的程式開發工具定義和發送HTTP請求,這樣可以方便地測試Web API,無需使用類似「Postman」這樣的應用程式,或在終端中使用「curl」命令送出請求。
1 2 3 4 5 6 | @MyAPIDemo_HostAddress = http: //localhost:5260 GET {{MyAPIDemo_HostAddress}}/weatherforecast/ Accept: application/json ### |
我們需先執行網站,才能透過「MyAPIDemo.http」檔案來測試Web API。在Visual Studio 2022開發工具,按CTRL+F5執行網站,然後點選「MyAPIDemo.http」檔案中「GET」這行程式上方的「Send request」連結,就會自動送出HTTP GET請求,視窗右方將會顯示請求執行的結果,請參考下圖所示:
圖 5:使用「專案名稱.http」檔案送出HTTP GET請求。
若點選執行結果畫面的「Raw」連結,可以檢視HTTP回應的原始資料格式,請參考下圖所示:
圖 6:HTTP回應的原始資料格式。
點選執行結果畫面的「Headers」連結,可以檢視HTTP回應的表頭資訊,請參考下圖所示:
圖 7:HTTP回應的表頭資訊。
點選執行結果畫面的「Request」連結,可以檢視HTTP請求的資訊,如表頭、內文等,請參考下圖所示:
圖 8:HTTP請求的資訊。
加入模型類別
預設Visual Studio 2022提供Scaffolding功能,我們先加入一個模型類別描述圖書資料。從「Solution Explorer」視窗專案名稱資料夾上方按滑鼠右鍵,從快捷選單選擇「Add」>「Class」項目,選取「Class」,將名稱設定為「Book」,然後按下「Add 」按鈕,請參考下圖所示:
圖 9:加入「Book」類別。
在「Book」類別之中加入以下屬性,描述圖書資料:
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 | using System.ComponentModel.DataAnnotations; namespace MyAPIDemo { public class Book { [Display( Name = "圖書編號" )] public int Id { get ; set ; } [Display( Name = "圖書名稱" )] [Required( ErrorMessage = "圖書名稱不可為空白" )] [MaxLength( 50, ErrorMessage = "長度不可超過 {1}" )] public string ? Title { get ; set ; } = null !; [Display( Name = "價格" )] [Range( 1, int .MaxValue, ErrorMessage = "{0} 有效範圍在 {1} 與 {2} 之間" )] public int Price { get ; set ; } [Display( Name = "出版日期" )] [DataType( DataType.Date )] public DateTime PublishDate { get ; set ; } [Display( Name = "庫存" )] public bool InStock { get ; set ; } [Display( Name = "說明" )] [MaxLength( 50, ErrorMessage = "長度不可超過 {1}" )] public string ? Description { get ; set ; } } } |
選取Visual Stuido 「Build」>「Build Solution」項目編譯程式碼,確定專案程式無誤,因為接下來的動作需要專案正確編譯才能進行。
使用Scaffold功能產生程式碼
接著透過Visual Studio Scaffold功能產生使用Entity Framework Core 存取資料庫的Web API程式��,從「Solution Explorer」視窗專案名稱資料夾上方按滑鼠右鍵,從快捷選單選擇「Add」>「New Scaffolded Item」項目,請參考下圖所示:
圖 10:使用Scaffold功能產生程式碼。
在「Add New Scaffolded Item」對話盒中選取「API with read / write endpoints , using Entity Framework」項目,然後按下「Add 」按鈕,請參考下圖所示:
圖 11:選取「API with read / write endpoints , using Entity Framework」項目。
在「Add API with read / write endpoints , using Entity Framework」視窗,從「Model class」右方的下拉式清單方塊中選取「Book」;點選「Endpoints class」右方的「+」按鈕,請參考下圖所示:
圖 12:加入Endpoints class。
在「Add Endpoints Class」視窗中輸入名稱,然後按「Add」按鈕,請參考下圖所示:
圖 13:加入Endpoints class。
接下來會回到「Add API with read / write endpoints , using Entity Framework」視窗,點選「DbContext class」右方的「+」按鈕,請參考下圖所示:
圖 14:加入「DbContext」類別。
在「Add Data Context」視窗中輸入名稱,然後按「Add」按鈕,請參考下圖所示:
圖 15:加入「DbContext」類別。
接下來會回到「Add API with read / write endpoints , using Entity Framework」視窗,點選「Database provider」右方的下拉式清單方塊,選取「SQL Server」,然後按下「Add」按鈕,請參考下圖所示:
圖 16:選取「Database provider」。
接著Visual Studio會在專案中加入一個「BookEndpoints.cs」類別,此類別用於集中定義和對應操作圖書資料相關的 HTTP 端點,其中的「MapBookEndpoints」方法擴充了「IEndpointRouteBuilder」介面,允許你直接在方法內配置路由,程式參考如下:
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 | using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.OpenApi; using MyAPIDemo.Data; namespace MyAPIDemo; public static class BookEndpoints { public static void MapBookEndpoints ( this IEndpointRouteBuilder routes) { var group = routes.MapGroup( "/api/Book" ).WithTags(nameof(Book)); group.MapGet( "/" , async (MyAPIDemoContext db) => { return await db.Book.ToListAsync(); }) .WithName( "GetAllBooks" ) .WithOpenApi(); group.MapGet( "/{id}" , async Task<Results<Ok<Book>, NotFound>> ( int id, MyAPIDemoContext db) => { return await db.Book.AsNoTracking() .FirstOrDefaultAsync(model => model.Id == id) is Book model ? TypedResults.Ok(model) : TypedResults.NotFound(); }) .WithName( "GetBookById" ) .WithOpenApi(); 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(); group.MapPost( "/" , async (Book book, MyAPIDemoContext db) => { db.Book.Add(book); await db.SaveChangesAsync(); return TypedResults.Created($ "/api/Book/{book.Id}" ,book); }) .WithName( "CreateBook" ) .WithOpenApi(); group.MapDelete( "/{id}" , async Task<Results<Ok, NotFound>> ( int id, MyAPIDemoContext db) => { var affected = await db.Book .Where(model => model.Id == id) .ExecuteDeleteAsync(); return affected == 1 ? TypedResults.Ok() : TypedResults.NotFound(); }) .WithName( "DeleteBook" ) .WithOpenApi(); } } |
以下分別說明「BookEndpoints」靜態類別的「MapBookEndpoints」靜態方法中的程式碼。首先,程式碼建立了一個「group」變數,使用「routes.MapGroup」方法將API端點對應到「/api/Book」路徑,並指定了「Book」作為標籤:
1 | var group = routes.MapGroup( "/api/Book" ).WithTags( nameof(Book) ); |
接下來,程式碼使用「group.MapGet」方法對應了一個GET請求的端點,該端點的路徑為「/」。這個端點使用了一個非同步的委派,接受一個「MyAPIDemoContext」物件作為參數,並從資料庫中取得所有的書籍資料。最後,將書籍資料以「List」的形式回傳。
1 2 3 4 5 6 | group.MapGet( "/" , async (MyAPIDemoContext db) => { return await db.Book.ToListAsync(); }) .WithName( "GetAllBooks" ) .WithOpenApi(); |
接著使用「group.MapGet」方法對應了另一個GET請求的端點,該端點的路徑為「/{id}」,其中「id」是一個整數參數。這個端點也使用了一個非同步的委派,接受一個整數型別的「id」與「MyAPIDemoContext」物件作為參數。在這個端點中,從資料庫中查詢「id」相符的書籍資料,並將結果回傳。如果找到了書籍資料,則叫用「Ok」方法並將書籍資料作為內容回傳;如果找不到書籍資料,則回傳「NotFound」。
1 2 3 4 5 6 7 8 9 10 | group.MapGet( "/{id}" , async Task<Results<Ok<Book>, NotFound>> ( int id, MyAPIDemoContext db) => { return await db.Book.AsNoTracking() .FirstOrDefaultAsync(model => model.Id == id) is Book model ? TypedResults.Ok(model) : TypedResults.NotFound(); }) .WithName( "GetBookById" ) .WithOpenApi(); |
然後使用「group.MapPut」方法對應了一個PUT請求的端點,該端點的路徑為「/{id}」,其中「id」是一個整數參數。這個端點也使用了一個非同步的委派,接受一個整數型別的「id」、一個「Book」物件與「MyAPIDemoContext」物件作為參數。在這個端點中,程式碼根據指定的「id」在資料庫中更新對應的書籍資料。如果更新成功,叫用「Ok」方法並將書籍資料作為內容回傳;如果找不到書籍資料,則回傳「NotFound」:
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.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( ); |
接著,程式碼使用「group.MapPost」方法對應一個POST請求的端點,該端點的路徑為「/」。這個端點也使用了一個非同步的委派,接受一個「Book」物件和一個「MyAPIDemoContext」物件作為參數。在這個端點中,將接收到的書籍資料新增到資料庫中,並叫用「Created」方法回傳結果,其中包含了新增書籍的路徑和書籍的內容:
1 2 3 4 5 6 7 8 | group.MapPost( "/" , async (Book book, MyAPIDemoContext db) => { db.Book.Add(book); await db.SaveChangesAsync(); return TypedResults.Created($ "/api/Book/{book.Id}" ,book); }) .WithName( "CreateBook" ) .WithOpenApi(); |
最後,程式碼使用「group.MapDelete」方法對應了一個「DELETE」請求的端點,該端點的路徑為「/{id}」,其中「id」是一個整數參數。這個端點也使用了一個非同步的委派,接受一個整數型別的「id」與一個「MyAPIDemoContext」物件作為參數。在這個端點中,程式碼根據指定的「id」從資料庫中刪除對應的書籍資料。如果刪除成功,則叫用「Ok」方法回傳結果;如果找不到對應的書籍資料,則叫用「NotFound」方法回傳結果。
1 2 3 4 5 6 7 8 9 | group.MapDelete( "/{id}" , async Task<Results<Ok, NotFound>> ( int id, MyAPIDemoContext db) => { var affected = await db.Book .Where(model => model.Id == id) .ExecuteDeleteAsync(); return affected == 1 ? TypedResults.Ok() : TypedResults.NotFound(); }) .WithName( "DeleteBook" ) .WithOpenApi(); |
同時工具也會在專案中加入一個「MyAPIDemoContext.cs」檔案,參考程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using MyAPIDemo; namespace MyAPIDemo.Data { public class MyAPIDemoContext : DbContext { public MyAPIDemoContext (DbContextOptions<MyAPIDemoContext> options) : base (options) { } public DbSet<MyAPIDemo.Book> Book { get ; set ; } = default !; } } |
「MyAPIDemoContext」類別繼承自「DbContext」類別。「DbContext」是Entity Framework Core中的一個類別,用於處理與資料庫的互動。在「MyAPIDemoContext」類別中包含一個建構函式「MyAPIDemoContext」,它接受一個「DbContextOptions
」型別的參數,並呼叫基底類別的建構函式「base(options)」。這個建構函式用於設定資料庫連線和其他相關的選項。
「MyAPIDemoContext」類別中包含一個「DbSet」屬性,它表示資料庫中的資料表資料合。這個屬性的名稱是「Book」。這個「DbSet」屬性用於對資料庫進行CRUD操作,例如查詢、新增、更新和刪除。
資料庫的連結資訊儲存在專案中的「appsettings.json」檔案之中,參考程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 | { "Logging" : { "LogLevel" : { "Default" : "Information" , "Microsoft.AspNetCore" : "Warning" } }, "AllowedHosts" : "*" , "ConnectionStrings" : { "MyAPIDemoContext" : "Server=(localdb)\\mssqllocaldb;Database=MyAPIDemoContext-42d5550a-9624-486b-ba91-906be590fb34;Trusted_Connection=True;MultipleActiveResultSets=true" } } |
預設工具使用SQL Server localdb資料庫來存放資料,我們可以根據需求變更資料庫,例如修改「appsettings.json」檔案的內容,改用SQL Server Express來存放資料,參考程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 | { "Logging" : { "LogLevel" : { "Default" : "Information" , "Microsoft.AspNetCore" : "Warning" } }, "AllowedHosts" : "*" , "ConnectionStrings" : { "MyAPIDemoContext" : "Server=.\\sqlexpress;Database=MyAPIDemoDb;Trusted_Connection=True;TrustServerCertificate=True;MultipleActiveResultSets=true" } } |
最後工具會在專案中的「Program.cs」檔案中註冊「MyAPIDemoContext」服務,參考程式碼如下,這個方法告訴應用程式使用 SQL Server 資料庫作為資料庫提供者,並從組態檔案讀取名為「MyAPIDemoContext」的連接字串來連接到資料庫:
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 | using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using MyAPIDemo.Data; using MyAPIDemo; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<MyAPIDemoContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString( "MyAPIDemoContext" ) ?? throw new InvalidOperationException( "Connection string 'MyAPIDemoContext' not found." ))); // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); var summaries = new [] { "Freezing" , "Bracing" , "Chilly" , "Cool" , "Mild" , "Warm" , "Balmy" , "Hot" , "Sweltering" , "Scorching" }; app.MapGet( "/weatherforecast" , () => { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }) .WithName( "GetWeatherForecast" ) .WithOpenApi(); app.MapBookEndpoints(); app.Run(); internal record WeatherForecast(DateOnly Date, int TemperatureC, string ? Summary) { public int TemperatureF => 32 + ( int )(TemperatureC / 0.5556); } |
「Program.cs」檔案最後將一個叫用「MapBookEndpoints」方法來設定Web API應用程式的路由。
0 意見:
張貼留言