教學課程:使用 ASP.NET Core 建立基本 API
注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
作者:Rick Anderson 與 Ryan Nowak
基本 API 的架構是要建立具有最低相依性的 HTTP API。 它們很適合只要包含 ASP.NET Core 中基本檔案、功能和相依性的微服務及應用程式。
本教學課程將教導您使用 ASP.NET Core 建立基本 API 的基本概念。 在 ASP.NET Core 中建立 API 的另一種方法是使用控制器。 如需在基本 API 與控制器型 API 之間選擇的說明,請參閱 API 概觀。 如需根據包含更多功能的控制器建立 API 專案的教學課程,請參閱建立 Web API。
概觀
本教學課程會建立以下 API:
API | 描述 | 要求本文 | 回應本文 |
---|---|---|---|
GET /todoitems |
取得所有待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/complete |
取得已完成的待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/{id} |
依識別碼取得項目 | 無 | 待辦事項 |
POST /todoitems |
新增記錄 | 待辦事項 | 待辦事項 |
PUT /todoitems/{id} |
更新現有的項目 | 待辦事項 | 無 |
DELETE /todoitems/{id} |
刪除項目 | 無 | 無 |
必要條件
Visual Studio 2022 預覽版以及 ASP.NET 與 Web 開發工作負載。
建立 API 專案
啟動 Visual Studio 2022 並選取 [建立新專案]。
在 [建立新專案] 對話方塊中:
- 在 [搜尋範本] 搜尋方塊中輸入
Empty
。 - 選取 [ASP.NET Core 空白] 範本,然後選取 [下一步]。
- 在 [搜尋範本] 搜尋方塊中輸入
將專案命名為 TodoApi,然後選取 [下一步]。
在 [其他資訊] 對話方塊中:
- 選取 [.NET 9.0 (預覽)]。
- 取消勾選 [不要使用最上層陳述式]
- 選取 [建立]
檢查程式碼
Program.cs
檔案包含下列程式碼:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
上述 程式碼:
- 使用預先設定的預設值建立 WebApplicationBuilder 和 WebApplication。
- 建立傳回
Hello World!
的 HTTP GET 端點/
:
執行應用程式
按 Ctrl+F5 即可執行而不使用偵錯工具。
Visual Studio 會顯示下列對話方塊:
如果您信任 IIS Express SSL 憑證,請選取 [是]。
此時會顯示下列對話方塊:
若您同意信任開發憑證,請選取 [是]。
如需關於信任 Firefox 瀏覽器的資訊,請參閱 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 憑證錯誤。
Visual Studio 會啟動 Kestrel 網頁伺服器,並開啟瀏覽器視窗。
Hello World!
隨即在瀏覽器中顯示。 Program.cs
檔案包含最基本但完整的應用程式。
關閉瀏覽器視窗。
新增 NuGet 套件
必須新增 NuGet 封裝,以支援本教學課程中使用的資料庫和診斷。
- 在 [工具] 功能表上,選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]。
- 選取 [瀏覽] 索引標籤。
- 選取 [包含發行前版本]。
- 在搜尋方塊中輸入 Microsoft.EntityFrameworkCore.InMemory,然後選取
Microsoft.EntityFrameworkCore.InMemory
。 - 選取右窗格中的 [專案] 核取方塊,然後選取 [安裝]。
- 遵循上述指示來新增
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
封裝。
模型和資料庫內容類別
- 在專案資料夾中,使用下列程式碼建立名為
Todo.cs
的檔案:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
上述程式碼會為此應用程式建立模型。 模型是一個類別,其代表應用程式管理的資料。
- 使用下列程式碼建立名為
TodoDb.cs
的檔案:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
上述程式碼會定義資料庫內容,這是協調資料模型 Entity Framework 功能的主要類別。 此類別衍生自 Microsoft.EntityFrameworkCore.DbContext 類別。
新增 API 程式碼
- 以下列程式碼來取代
Program.cs
檔案的內容:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
下列反白顯示的程式碼會將資料庫內容新增至相依性插入 (DI) 容器,並啟用顯示資料庫相關的例外狀況:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI 容器可讓您存取資料庫內容和其他服務。
本教學課程使用端點總管和 .http 檔案來測試 API。
測試張貼資料
Program.cs
中的下列程式碼會建立 HTTP POST 端點 /todoitems
,以將資料新增至記憶體內部資料庫:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
執行應用程式。 瀏覽器會顯示 404 錯誤,因為不再有 /
端點。
會使用 POST 端點將資料新增至應用程式。
選取 [檢視] > [其他視窗] > [端點總管]。
以滑鼠右鍵按一下 [POST] 端點,然後選取 [產生要求]。
隨即會在專案資料夾中建立名為
TodoApi.http
的新檔案,其內容類別似下列範例:@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- 第一行會建立用於所有端點的變數。
- 下一行會定義 POST 要求。
- 三重主題標籤 (
###
) 行是要求分隔符號:其之後則用於不同的要求。
POST 要求需要標頭和本文。 若要定義要求的這些部分,請緊接在 POST 要求行後面新增下列幾行:
Content-Type: application/json { "name":"walk dog", "isComplete":true }
上述程式碼會新增 Content-Type 標頭和 JSON 要求本文。 TodoApi.http 檔案現在看起來應該類似下列範例,但有您的連接埠號碼:
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
執行應用程式。
選取
POST
要求行上方的 [傳送要求] 連結。POST 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。
檢查 GET 端點
範例應用程式會藉由呼叫 MapGet
來實作數個 GET 端點:
API | 描述 | 要求本文 | 回應本文 |
---|---|---|---|
GET /todoitems |
取得所有待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/complete |
取得所有已完成的待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/{id} |
依識別碼取得項目 | 無 | 待辦事項 |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
測試 GET 端點
從瀏覽器或使用端點總管呼叫 GET
端點,以測試應用程式。 下列步驟適用端點總管。
在 [端點總管] 中,以滑鼠右鍵按一下第一個 GET 端點,然後選取 [產生要求]。
下列內容會新增至
TodoApi.http
檔案:Get {{TodoApi_HostAddress}}/todoitems ###
選取新
GET
要求行上方的 [傳送要求] 連結。GET 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。
回應主體類似以下 JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
在 [端點總管] 中,以滑鼠右鍵按一下
/todoitems/{id}
GET 端點,然後選取 [產生要求]。 下列內容會新增至TodoApi.http
檔案:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
把
{id}
替換為1
。選取新 GET 要求行上方的 [傳送要求] 連結。
GET 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。
回應主體類似以下 JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
這個應用程式會使用記憶體內部資料庫。 如果應用程式重新啟動,GET 要求不會傳回任何資料。 如果未傳回任何資料,請將 POST 資料傳送至應用程式,然後再試一次 GET 要求。
傳回值
ASP.NET Core 會自動將物件序列化為 JSON,並將 JSON 寫入至回應訊息的本文。 此傳回型別的回應碼為 200 OK,假設沒有任何未處理的例外狀況。 未處理的例外狀況會轉譯成 5xx 錯誤。
傳回型別可代表各種 HTTP 狀態碼。 例如,GET /todoitems/{id}
可傳回兩個不同的狀態值:
檢查 PUT 端點
範例應用程式會使用 MapPut
實作單一 PUT 端點:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
此方法與 MapPost
方法類似,除了它使用 HTTP PUT。 成功的回應會傳回 204 (無內容)。 根據 HTTP 規格,PUT 要求需要用戶端傳送整個更新的實體,而不只是變更。 若要支援部分更新,請使用 HTTP PATCH。
測試 PUT 端點
此範例使用在每次應用程式啟動都必須初始化的記憶體內部資料庫。 資料庫中必須有項目,您才能進行 PUT 呼叫。 在發出 PUT 呼叫之前,呼叫 GET 以確保資料庫中有項目。
更新 Id = 1
的待辦事項,並將其名稱設定為 "feed fish"
。
在 [端點總管] 中,以滑鼠右鍵按一下 PUT 端點,然後選取 [產生要求]。
下列內容會新增至
TodoApi.http
檔案:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
在 PUT 要求行中,將
{id}
取代為1
。緊接在 PUT 要求行後面新增下列幾行:
Content-Type: application/json { "name": "feed fish", "isComplete": false }
上述程式碼會新增 Content-Type 標頭和 JSON 要求本文。
選取新 PUT 要求行上方的 [傳送要求] 連結。
PUT 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。 回應本文是空白,而狀態碼為 204。
檢查及測試 DELETE 端點
範例應用程式會使用 MapDelete
實作單一 DELETE 端點:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
在 [端點總管] 中,以滑鼠右鍵按一下 DELETE 端點,然後選取 [產生要求]。
DELETE 要求會新增至
TodoApi.http
。將 DELETE 要求行中的
{id}
取代為1
。 DELETE 要求看起來應該類似下列範例:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
選取 DELETE 要求的 [傳送要求] 連結。
DELETE 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。 回應本文是空白,而狀態碼為 204。
使用 MapGroup API
範例應用程式程式碼會在每次設定端點時重複 todoitems
URL 前置詞。 API 通常會有通用 URL 前置詞的端點群組,而且 MapGroup 方法可用來協助組織這類群組。 其可減少重複的程式碼,並允許使用對方法 (例如 RequireAuthorization 和 WithMetadata) 的單一呼叫來自訂整個端點群組。
以下列程式碼取代 Program.cs
的內容:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
上述程式碼有下列變更:
- 新增
var todoItems = app.MapGroup("/todoitems");
以使用 URL 前置詞/todoitems
來設定群組。 - 將所有
app.Map<HttpVerb>
方法變更為todoItems.Map<HttpVerb>
。 - 從
Map<HttpVerb>
方法呼叫中移除 URL 前置詞/todoitems
。
測試端點以驗證其運作方式相同。
使用 TypedResults API
傳回 TypedResults 而不是 Results 有幾個優點,包括可測試性,並自動傳回 OpenAPI 的回應型別中繼資料來描述端點。 如需詳細資訊,請參閱 TypedResults 與 Results。
Map<HttpVerb>
方法可以呼叫路由處理常式方法,而不使用 Lambda。 若要查看範例,請使用下列程式碼更新 Program.cs:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Map<HttpVerb>
程式碼現在會呼叫方法,而不是 Lambda:
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
這些方法會傳回實作 IResult 的物件,且由 TypedResults 定義:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
單元測試可以呼叫這些方法,並測試它們傳回正確的型別。 例如,如果方法為 GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
單元測試程式碼可以驗證型別 Ok<Todo[]> 的物件是從處理常式方法傳回。 例如:
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
防止過度張貼
目前範例應用程式會公開整個 Todo
物件。 生產應用程式 在生產應用程式中,通常會使用模型子集來限制可輸入和傳回的資料。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本文中會使用 DTO。
DTO 可用來:
- 防止過度張貼。
- 隱藏用戶端不應該檢視的屬性。
- 省略一些屬性,以減少承載大小。
- 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。
若要示範 DTO 方法,請更新 Todo
類別以包含祕密欄位:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
祕密欄位必須從此應用程式隱藏,但系統管理應用程式可以選擇公開它。
驗證您可以張貼並取得祕密欄位。
使用下列程式碼建立名為 TodoItemDTO.cs
的檔案:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
以下列程式碼取代 Program.cs
檔案的內容,以使用此 DTO 模型:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
RouteGroupBuilder todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db) {
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
todoItemDTO = new TodoItemDTO(todoItem);
return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
驗證您可以張貼並取得祕密欄位以外的所有欄位。
使用已完成的範例進行疑難排解
若您遇到無法解決的問題,請將您的程式碼與已完成的專案進行比較。 檢視或下載已完成的專案 (如何下載)。
下一步
- 設定 JSON 序列化選項。
- 處理錯誤和例外狀況:開發人員例外狀況頁面預設會在針對基本 API 應用程式的開發環境中啟用。 如需如何處理錯誤和例外狀況的相關資訊,請參閱在 ASP.NET Core API 中處理錯誤。
- 如需測試基本 API 應用程式的範例,請參閱此 GitHub 範例。
- 基本 API 中的 OpenAPI 支援。
- 快速入門:發佈至 Azure。
- 組織 ASP.NET Core 基本 API。
深入了解
請參閱基本 API 快速參考
基本 API 的架構是要建立具有最低相依性的 HTTP API。 它們非常適合想要只包含 ASP.NET Core 中基本檔案、功能和相依性的微服務和應用程式。
本教學課程將教導您使用 ASP.NET Core 建立基本 API 的基本概念。 在 ASP.NET Core 中建立 API 的另一種方法是使用控制器。 如需在基本 API 與控制器型 API 之間選擇的說明,請參閱 API 概觀。 如需根據包含更多功能的控制器建立 API 專案的教學課程,請參閱建立 Web API。
概觀
本教學課程會建立以下 API:
API | 描述 | 要求本文 | 回應本文 |
---|---|---|---|
GET /todoitems |
取得所有待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/complete |
取得已完成的待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/{id} |
依識別碼取得項目 | 無 | 待辦事項 |
POST /todoitems |
新增記錄 | 待辦事項 | 待辦事項 |
PUT /todoitems/{id} |
更新現有的項目 | 待辦事項 | 無 |
DELETE /todoitems/{id} |
刪除項目 | 無 | 無 |
必要條件
Visual Studio 2022 和 ASP.NET 與 Web 開發工作負載。
建立 API 專案
啟動 Visual Studio 2022 並選取 [建立新專案]。
在 [建立新專案] 對話方塊中:
- 在 [搜尋範本] 搜尋方塊中輸入
Empty
。 - 選取 [ASP.NET Core 空白] 範本,然後選取 [下一步]。
- 在 [搜尋範本] 搜尋方塊中輸入
將專案命名為 TodoApi,然後選取 [下一步]。
在 [其他資訊] 對話方塊中:
- 選取 [.NET 7.0]
- 取消勾選 [不要使用最上層陳述式]
- 選取 [建立]
檢查程式碼
Program.cs
檔案包含下列程式碼:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
上述 程式碼:
- 使用預先設定的預設值建立 WebApplicationBuilder 和 WebApplication。
- 建立傳回
Hello World!
的 HTTP GET 端點/
:
執行應用程式
按 Ctrl+F5 即可執行而不使用偵錯工具。
Visual Studio 會顯示下列對話方塊:
如果您信任 IIS Express SSL 憑證,請選取 [是]。
此時會顯示下列對話方塊:
若您同意信任開發憑證,請選取 [是]。
如需關於信任 Firefox 瀏覽器的資訊,請參閱 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 憑證錯誤。
Visual Studio 會啟動 Kestrel 網頁伺服器,並開啟瀏覽器視窗。
Hello World!
隨即在瀏覽器中顯示。 Program.cs
檔案包含最基本但完整的應用程式。
新增 NuGet 套件
必須新增 NuGet 封裝,以支援本教學課程中使用的資料庫和診斷。
- 在 [工具] 功能表上,選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]。
- 選取 [瀏覽] 索引標籤。
- 在搜尋方塊中輸入 Microsoft.EntityFrameworkCore.InMemory,然後選取
Microsoft.EntityFrameworkCore.InMemory
。 - 選取右窗格中的 [專案] 核取方塊。
- 在 [版本] 下拉式清單中,選取可用的最新版本 7 (例如
7.0.17
),然後選取 [安裝]。 - 遵循上述指示,以可用的最新版本 7 新增
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
套件。
模型和資料庫內容類別
在專案資料夾中,使用下列程式碼建立名為 Todo.cs
的檔案:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
上述程式碼會為此應用程式建立模型。 模型是一個類別,其代表應用程式管理的資料。
使用下列程式碼建立名為 TodoDb.cs
的檔案:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
上述程式碼會定義資料庫內容,這是協調資料模型 Entity Framework 功能的主要類別。 此類別衍生自 Microsoft.EntityFrameworkCore.DbContext 類別。
新增 API 程式碼
以下列程式碼來取代 Program.cs
檔案的內容:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
下列反白顯示的程式碼會將資料庫內容新增至相依性插入 (DI) 容器,並啟用顯示資料庫相關的例外狀況:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI 容器可讓您存取資料庫內容和其他服務。
使用 Swagger 建立 API 測試 UI
有許多可用的 Web API 測試工具可供選擇,而且您可以使用您自己慣用的工具來遵循本教學課程的簡介 API 測試步驟。
本教學課程會使用 .NET 套件 NSwag.AspNetCore,其整合了 Swagger 工具以產生符合 OpenAPI 規格的測試 UI:
- NSwag:.NET 連結庫,可將 Swagger 直接整合到 ASP.NET Core 應用程式,並提供中介軟體和設定。
- Swagger:一組開放原始碼工具,例如 OpenAPIGenerator 和 SwaggerUI,可產生遵循 OpenAPI 規格的 API 測試頁面。
- OpenAPI 規格:根據控制器和模型內的 XML 和屬性註釋來描述 API 功能的文件。
如需搭配 ASP.NET 使用 OpenAPI 和 NSwag 的詳細資訊,請參閱使用 Swagger/OpenAPI 的 ASP.NET Core Web API 文件。
安裝 Swagger 工具
執行以下命令:
dotnet add package NSwag.AspNetCore
上面的命令會新增 NSwag.AspNetCore 套件,其中包含用來產生 Swagger 文件和 UI 的工具。
設定 Swagger 中介軟體
在內嵌
var app = builder.Build();
中定義app
之前,新增下列醒目提示的程式碼using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList")); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(config => { config.DocumentName = "TodoAPI"; config.Title = "TodoAPI v1"; config.Version = "v1"; }); var app = builder.Build();
在先前的程式碼中:
builder.Services.AddEndpointsApiExplorer();
:啟用 API 總管,這是提供 HTTP API 相關中繼資料的服務。 Swagger 會使用 API 總管來產生 Swagger 文件。builder.Services.AddOpenApiDocument(config => {...});
:將 Swagger OpenAPI 文件產生器新增至應用程式服務,並將其設定為提供有關 API 的詳細資訊,例如其標題和版本。 如需有關如何提供更健全 API 詳細資料的資訊,請參閱開始使用 NSwag 和 ASP.NET Core在內嵌
var app = builder.Build();
中定義app
之後,將下列醒目提示的程式碼新增到下一行var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi(config => { config.DocumentTitle = "TodoAPI"; config.Path = "/swagger"; config.DocumentPath = "/swagger/{documentName}/swagger.json"; config.DocExpansion = "list"; }); }
前面的程式碼可讓 Swagger 中介軟體提供所產生的 JSON 文件和 Swagger UI。 Swagger 只會在開發環境中啟用。 在生產環境中啟用 Swagger 可能會公開關於 API 結構和實作的潛在敏感性詳細資料。
測試張貼資料
Program.cs
中的下列程式碼會建立 HTTP POST 端點 /todoitems
,以將資料新增至記憶體內部資料庫:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
執行應用程式。 瀏覽器會顯示 404 錯誤,因為不再有 /
端點。
會使用 POST 端點將資料新增至應用程式。
當應用程式仍在執行時,在瀏覽器中瀏覽至
https://localhost:<port>/swagger
以顯示 Swagger 所產生的 API 測試頁面。在 Swagger 的 API 測試頁面上,選取 [Post /todoitems]>[試用]。
請注意,[要求本文] 欄位包含所產生的範例格式,且會反映 API 的參數。
在要求本文中,針對待辦事項輸入 JSON,而不指定選擇性的
id
:{ "name":"walk dog", "isComplete":true }
選取 [執行]。
Swagger 會在 [執行] 按鈕下方提供 [回應] 窗格。
請記下一些實用的詳細資料:
- cURL:Swagger 提供 Unix/Linux 語法中的範例 cURL 命令,其可在命令列中搭配任何使用 Unix/Linux 語法的 Bash 殼層 (包括來自 Git for Windows 的 Git Bash) 來執行。
- 要求 URL:Swagger UI 的 JavaScript 程式碼針對 API 呼叫所發出 HTTP 要求的簡化表示法。 實際要求可能包含標頭和查詢參數與要求本文等詳細資料。
- 伺服器回應:包含回應本文和標頭。 回應本文顯示
id
已設定為1
。 - 回應碼:傳回 201
HTTP
狀態碼,指出已成功處理要求,並導致建立新的資源。
檢查 GET 端點
範例應用程式會藉由呼叫 MapGet
來實作數個 GET 端點:
API | 描述 | 要求本文 | 回應本文 |
---|---|---|---|
GET /todoitems |
取得所有待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/complete |
取得所有已完成的待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/{id} |
依識別碼取得項目 | 無 | 待辦事項 |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
測試 GET 端點
從瀏覽器或 Swagger 呼叫端點來測試應用程式。
在 Swagger 中,選取 [GET /todoitems]>[試用]>[執行]。
或者,從瀏覽器輸入 URI
http://localhost:<port>/todoitems
來呼叫GET /todoitems。 例如,http://localhost:5001/todoitems
對 GET /todoitems
的呼叫會產生類似下列的回應:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
在 Swagger 中呼叫 GET /todoitems/{id} 以從特定識別碼傳回資料:
- 選取 [GET /todoitems]>[試用]。
- 將 [識別碼] 欄位設定為
1
,然後選取 [執行]。
或者,從瀏覽器輸入 URI
https://localhost:<port>/todoitems/1
來呼叫GET /todoitems。 例如,https://localhost:5001/todoitems/1
回應類似以下:
{ "id": 1, "name": "walk dog", "isComplete": true }
這個應用程式會使用記憶體內部資料庫。 如果應用程式重新啟動,GET 要求不會傳回任何資料。 如果未傳回任何資料,請將 POST 資料傳送至應用程式,然後再試一次 GET 要求。
傳回值
ASP.NET Core 會自動將物件序列化為 JSON,並將 JSON 寫入至回應訊息的本文。 此傳回型別的回應碼為 200 OK,假設沒有任何未處理的例外狀況。 未處理的例外狀況會轉譯成 5xx 錯誤。
傳回型別可代表各種 HTTP 狀態碼。 例如,GET /todoitems/{id}
可傳回兩個不同的狀態值:
檢查 PUT 端點
範例應用程式會使用 MapPut
實作單一 PUT 端點:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
此方法與 MapPost
方法類似,除了它使用 HTTP PUT。 成功的回應會傳回 204 (無內容)。 根據 HTTP 規格,PUT 要求需要用戶端傳送整個更新的實體,而不只是變更。 若要支援部分更新,請使用 HTTP PATCH。
測試 PUT 端點
此範例使用在每次應用程式啟動都必須初始化的記憶體內部資料庫。 資料庫中必須有項目,您才能進行 PUT 呼叫。 在發出 PUT 呼叫之前,呼叫 GET 以確保資料庫中有項目。
更新 Id = 1
的待辦事項,並將其名稱設定為 "feed fish"
。
使用 Swagger 來傳送 PUT 要求:
選取 [Put /todoitems/{id}]>[試用]。
將 [識別碼] 欄位設定為
1
。將要求本文設定為下列 JSON:
{ "name": "feed fish", "isComplete": false }
選取 [執行]。
檢查及測試 DELETE 端點
範例應用程式會使用 MapDelete
實作單一 DELETE 端點:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
使用 Swagger 來傳送 DELETE 要求:
選取 [DELETE /todoitems/{id}]>[試用]。
將 [識別碼] 欄位設定為
1
,然後選取 [執行]。DELETE 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。 回應本文是空的,而伺服器回應狀態碼是 204。
使用 MapGroup API
範例應用程式程式碼會在每次設定端點時重複 todoitems
URL 前置詞。 API 通常會有通用 URL 前置詞的端點群組,而且 MapGroup 方法可用來協助組織這類群組。 其可減少重複的程式碼,並允許使用對方法 (例如 RequireAuthorization 和 WithMetadata) 的單一呼叫來自訂整個端點群組。
以下列程式碼取代 Program.cs
的內容:
using NSwag.AspNetCore;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
config.DocumentName = "TodoAPI";
config.Title = "TodoAPI v1";
config.Version = "v1";
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseOpenApi();
app.UseSwaggerUi(config =>
{
config.DocumentTitle = "TodoAPI";
config.Path = "/swagger";
config.DocumentPath = "/swagger/{documentName}/swagger.json";
config.DocExpansion = "list";
});
}
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
上述程式碼有下列變更:
- 新增
var todoItems = app.MapGroup("/todoitems");
以使用 URL 前置詞/todoitems
來設定群組。 - 將所有
app.Map<HttpVerb>
方法變更為todoItems.Map<HttpVerb>
。 - 從
Map<HttpVerb>
方法呼叫中移除 URL 前置詞/todoitems
。
測試端點以驗證其運作方式相同。
使用 TypedResults API
傳回 TypedResults 而不是 Results 有幾個優點,包括可測試性,並自動傳回 OpenAPI 的回應型別中繼資料來描述端點。 如需詳細資訊,請參閱 TypedResults 與 Results。
Map<HttpVerb>
方法可以呼叫路由處理常式方法,而不使用 Lambda。 若要查看範例,請使用下列程式碼更新 Program.cs:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Map<HttpVerb>
程式碼現在會呼叫方法,而不是 Lambda:
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
這些方法會傳回實作 IResult 的物件,且由 TypedResults 定義:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
單元測試可以呼叫這些方法,並測試它們傳回正確的型別。 例如,如果方法為 GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
單元測試程式碼可以驗證型別 Ok<Todo[]> 的物件是從處理常式方法傳回。 例如:
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
防止過度張貼
目前範例應用程式會公開整個 Todo
物件。 生產應用程式 在生產應用程式中,通常會使用模型子集來限制可輸入和傳回的資料。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本文中會使用 DTO。
DTO 可用來:
- 防止過度張貼。
- 隱藏用戶端不應該檢視的屬性。
- 省略一些屬性,以減少承載大小。
- 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。
若要示範 DTO 方法,請更新 Todo
類別以包含祕密欄位:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
祕密欄位必須從此應用程式隱藏,但系統管理應用程式可以選擇公開它。
驗證您可以張貼並取得祕密欄位。
使用下列程式碼建立名為 TodoItemDTO.cs
的檔案:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
以下列程式碼取代 Program.cs
檔案的內容,以使用此 DTO 模型:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});
app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
驗證您可以張貼並取得祕密欄位以外的所有欄位。
使用已完成的範例進行疑難排解
若您遇到無法解決的問題,請將您的程式碼與已完成的專案進行比較。 檢視或下載已完成的專案 (如何下載)。
下一步
- 設定 JSON 序列化選項。
- 處理錯誤和例外狀況:開發人員例外狀況頁面預設會在針對基本 API 應用程式的開發環境中啟用。 如需如何處理錯誤和例外狀況的相關資訊,請參閱在 ASP.NET Core API 中處理錯誤。
- 如需測試基本 API 應用程式的範例,請參閱此 GitHub 範例。
- 基本 API 中的 OpenAPI 支援。
- 快速入門:發佈至 Azure。
- 組織 ASP.NET Core 基本 API。
深入了解
請參閱基本 API 快速參考
基本 API 的架構是要建立具有最低相依性的 HTTP API。 它們非常適合想要只包含 ASP.NET Core 中基本檔案、功能和相依性的微服務和應用程式。
本教學課程將教導您使用 ASP.NET Core 建立基本 API 的基本概念。 在 ASP.NET Core 中建立 API 的另一種方法是使用控制器。 如需在基本 API 與控制器型 API 之間選擇的說明,請參閱 API 概觀。 如需根據包含更多功能的控制器建立 API 專案的教學課程,請參閱建立 Web API。
概觀
本教學課程會建立以下 API:
API | 描述 | 要求本文 | 回應本文 |
---|---|---|---|
GET /todoitems |
取得所有待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/complete |
取得已完成的待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/{id} |
依識別碼取得項目 | 無 | 待辦事項 |
POST /todoitems |
新增記錄 | 待辦事項 | 待辦事項 |
PUT /todoitems/{id} |
更新現有的項目 | 待辦事項 | 無 |
DELETE /todoitems/{id} |
刪除項目 | 無 | 無 |
必要條件
- Visual Studio 2022 和 ASP.NET 與 Web 開發工作負載。
- .NET 6.0 SDK
建立 API 專案
啟動 Visual Studio 2022 並選取 [建立新專案]。
在 [建立新專案] 對話方塊中:
- 在 [搜尋範本] 搜尋方塊中輸入
Empty
。 - 選取 [ASP.NET Core 空白] 範本,然後選取 [下一步]。
- 在 [搜尋範本] 搜尋方塊中輸入
將專案命名為 TodoApi,然後選取 [下一步]。
在 [其他資訊] 對話方塊中:
- 選取 [.NET 6.0]
- 取消勾選 [不要使用最上層陳述式]
- 選取 [建立]
檢查程式碼
Program.cs
檔案包含下列程式碼:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
上述 程式碼:
- 使用預先設定的預設值建立 WebApplicationBuilder 和 WebApplication。
- 建立傳回
Hello World!
的 HTTP GET 端點/
:
執行應用程式
按 Ctrl+F5 即可執行而不使用偵錯工具。
Visual Studio 會顯示下列對話方塊:
如果您信任 IIS Express SSL 憑證,請選取 [是]。
此時會顯示下列對話方塊:
若您同意信任開發憑證,請選取 [是]。
如需關於信任 Firefox 瀏覽器的資訊,請參閱 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 憑證錯誤。
Visual Studio 會啟動 Kestrel 網頁伺服器,並開啟瀏覽器視窗。
Hello World!
隨即在瀏覽器中顯示。 Program.cs
檔案包含最基本但完整的應用程式。
新增 NuGet 套件
必須新增 NuGet 封裝,以支援本教學課程中使用的資料庫和診斷。
- 在 [工具] 功能表上,選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]。
- 選取 [瀏覽] 索引標籤。
- 在搜尋方塊中輸入 Microsoft.EntityFrameworkCore.InMemory,然後選取
Microsoft.EntityFrameworkCore.InMemory
。 - 選取右窗格中的 [專案] 核取方塊。
- 在 [版本] 下拉式清單中,選取可用的最新版本 7 (例如
6.0.28
),然後選取 [安裝]。 - 遵循上述指示,以可用的最新版本 7 新增
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
套件。
模型和資料庫內容類別
在專案資料夾中,使用下列程式碼建立名為 Todo.cs
的檔案:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
上述程式碼會為此應用程式建立模型。 模型是一個類別,其代表應用程式管理的資料。
使用下列程式碼建立名為 TodoDb.cs
的檔案:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
上述程式碼會定義資料庫內容,這是協調資料模型 Entity Framework 功能的主要類別。 此類別衍生自 Microsoft.EntityFrameworkCore.DbContext 類別。
新增 API 程式碼
以下列程式碼來取代 Program.cs
檔案的內容:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
下列反白顯示的程式碼會將資料庫內容新增至相依性插入 (DI) 容器,並啟用顯示資料庫相關的例外狀況:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI 容器可讓您存取資料庫內容和其他服務。
使用 Swagger 建立 API 測試 UI
有許多可用的 Web API 測試工具可供選擇,而且您可以使用您自己慣用的工具來遵循本教學課程的簡介 API 測試步驟。
本教學課程會使用 .NET 套件 NSwag.AspNetCore,其整合了 Swagger 工具以產生符合 OpenAPI 規格的測試 UI:
- NSwag:.NET 連結庫,可將 Swagger 直接整合到 ASP.NET Core 應用程式,並提供中介軟體和設定。
- Swagger:一組開放原始碼工具,例如 OpenAPIGenerator 和 SwaggerUI,可產生遵循 OpenAPI 規格的 API 測試頁面。
- OpenAPI 規格:根據控制器和模型內的 XML 和屬性註釋來描述 API 功能的文件。
如需搭配 ASP.NET 使用 OpenAPI 和 NSwag 的詳細資訊,請參閱使用 Swagger/OpenAPI 的 ASP.NET Core Web API 文件。
安裝 Swagger 工具
執行以下命令:
dotnet add package NSwag.AspNetCore
上面的命令會新增 NSwag.AspNetCore 套件,其中包含用來產生 Swagger 文件和 UI 的工具。
設定 Swagger 中介軟體
在 Program.cs 的頂端新增下列
using
陳述式:using NSwag.AspNetCore;
在內嵌
var app = builder.Build();
中定義app
之前,新增下列醒目提示的程式碼using NSwag.AspNetCore; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList")); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(config => { config.DocumentName = "TodoAPI"; config.Title = "TodoAPI v1"; config.Version = "v1"; }); var app = builder.Build();
在先前的程式碼中:
builder.Services.AddEndpointsApiExplorer();
:啟用 API 總管,這是提供 HTTP API 相關中繼資料的服務。 Swagger 會使用 API 總管來產生 Swagger 文件。builder.Services.AddOpenApiDocument(config => {...});
:將 Swagger OpenAPI 文件產生器新增至應用程式服務,並將其設定為提供有關 API 的詳細資訊,例如其標題和版本。 如需有關如何提供更健全 API 詳細資料的資訊,請參閱開始使用 NSwag 和 ASP.NET Core在內嵌
var app = builder.Build();
中定義app
之後,將下列醒目提示的程式碼新增到下一行var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi(config => { config.DocumentTitle = "TodoAPI"; config.Path = "/swagger"; config.DocumentPath = "/swagger/{documentName}/swagger.json"; config.DocExpansion = "list"; }); }
前面的程式碼可讓 Swagger 中介軟體提供所產生的 JSON 文件和 Swagger UI。 Swagger 只會在開發環境中啟用。 在生產環境中啟用 Swagger 可能會公開關於 API 結構和實作的潛在敏感性詳細資料。
測試張貼資料
Program.cs
中的下列程式碼會建立 HTTP POST 端點 /todoitems
,以將資料新增至記憶體內部資料庫:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
執行應用程式。 瀏覽器會顯示 404 錯誤,因為不再有 /
端點。
會使用 POST 端點將資料新增至應用程式。
當應用程式仍在執行時,在瀏覽器中瀏覽至
https://localhost:<port>/swagger
以顯示 Swagger 所產生的 API 測試頁面。在 Swagger 的 API 測試頁面上,選取 [Post /todoitems]>[試用]。
請注意,[要求本文] 欄位包含所產生的範例格式,且會反映 API 的參數。
在要求本文中,針對待辦事項輸入 JSON,而不指定選擇性的
id
:{ "name":"walk dog", "isComplete":true }
選取 [執行]。
Swagger 會在 [執行] 按鈕下方提供 [回應] 窗格。
請記下一些實用的詳細資料:
- cURL:Swagger 提供 Unix/Linux 語法中的範例 cURL 命令,其可在命令列中搭配任何使用 Unix/Linux 語法的 Bash 殼層 (包括來自 Git for Windows 的 Git Bash) 來執行。
- 要求 URL:Swagger UI 的 JavaScript 程式碼針對 API 呼叫所發出 HTTP 要求的簡化表示法。 實際要求可能包含標頭和查詢參數與要求本文等詳細資料。
- 伺服器回應:包含回應本文和標頭。 回應本文顯示
id
已設定為1
。 - 回應碼:傳回 201
HTTP
狀態碼,指出已成功處理要求,並導致建立新的資源。
檢查 GET 端點
範例應用程式會藉由呼叫 MapGet
來實作數個 GET 端點:
API | 描述 | 要求本文 | 回應本文 |
---|---|---|---|
GET /todoitems |
取得所有待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/complete |
取得所有已完成的待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/{id} |
依識別碼取得項目 | 無 | 待辦事項 |
app.MapGet("/", () => "Hello World!");
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
測試 GET 端點
從瀏覽器或 Swagger 呼叫端點來測試應用程式。
在 Swagger 中,選取 [GET /todoitems]>[試用]>[執行]。
或者,從瀏覽器輸入 URI
http://localhost:<port>/todoitems
來呼叫GET /todoitems。 例如,http://localhost:5001/todoitems
對 GET /todoitems
的呼叫會產生類似下列的回應:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
在 Swagger 中呼叫 GET /todoitems/{id} 以從特定識別碼傳回資料:
- 選取 [GET /todoitems]>[試用]。
- 將 [識別碼] 欄位設定為
1
,然後選取 [執行]。
或者,從瀏覽器輸入 URI
https://localhost:<port>/todoitems/1
來呼叫GET /todoitems。 例如,例如https://localhost:5001/todoitems/1
回應類似以下:
{ "id": 1, "name": "walk dog", "isComplete": true }
這個應用程式會使用記憶體內部資料庫。 如果應用程式重新啟動,GET 要求不會傳回任何資料。 如果未傳回任何資料,請將 POST 資料傳送至應用程式,然後再試一次 GET 要求。
傳回值
ASP.NET Core 會自動將物件序列化為 JSON,並將 JSON 寫入至回應訊息的本文。 此傳回型別的回應碼為 200 OK,假設沒有任何未處理的例外狀況。 未處理的例外狀況會轉譯成 5xx 錯誤。
傳回型別可代表各種 HTTP 狀態碼。 例如,GET /todoitems/{id}
可傳回兩個不同的狀態值:
檢查 PUT 端點
範例應用程式會使用 MapPut
實作單一 PUT 端點:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
此方法與 MapPost
方法類似,除了它使用 HTTP PUT。 成功的回應會傳回 204 (無內容)。 根據 HTTP 規格,PUT 要求需要用戶端傳送整個更新的實體,而不只是變更。 若要支援部分更新,請使用 HTTP PATCH。
測試 PUT 端點
此範例使用在每次應用程式啟動都必須初始化的記憶體內部資料庫。 資料庫中必須有項目,您才能進行 PUT 呼叫。 在發出 PUT 呼叫之前,呼叫 GET 以確保資料庫中有項目。
更新 Id = 1
的待辦事項,並將其名稱設定為 "feed fish"
。
使用 Swagger 來傳送 PUT 要求:
選取 [Put /todoitems/{id}]>[試用]。
將 [識別碼] 欄位設定為
1
。將要求本文設定為下列 JSON:
{ "name": "feed fish", "isComplete": false }
選取 [執行]。
檢查及測試 DELETE 端點
範例應用程式會使用 MapDelete
實作單一 DELETE 端點:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
使用 Swagger 來傳送 DELETE 要求:
選取 [DELETE /todoitems/{id}]>[試用]。
將 [識別碼] 欄位設定為
1
,然後選取 [執行]。DELETE 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。 回應本文是空的,而伺服器回應狀態碼是 204。
防止過度張貼
目前範例應用程式會公開整個 Todo
物件。 生產應用程式 在生產應用程式中,通常會使用模型子集來限制可輸入和傳回的資料。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本文中會使用 DTO。
DTO 可用來:
- 防止過度張貼。
- 隱藏用戶端不應該檢視的屬性。
- 省略一些屬性,以減少承載大小。
- 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。
若要示範 DTO 方法,請更新 Todo
類別以包含祕密欄位:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
祕密欄位必須從此應用程式隱藏,但系統管理應用程式可以選擇公開它。
驗證您可以張貼並取得祕密欄位。
使用下列程式碼建立名為 TodoItemDTO.cs
的檔案:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
以下列程式碼取代 Program.cs
檔案的內容,以使用此 DTO 模型:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});
app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
驗證您可以張貼並取得祕密欄位以外的所有欄位。
測試基本 API
如需測試基本 API 應用程式的範例,請參閱此 GitHub 範例。
發佈至 Azure
如需部署至 Azure 的資訊,請參閱 快速入門:部署 ASP.NET Web 應用程式。
其他資源
基本 API 的架構是要建立具有最低相依性的 HTTP API。 它們很適合只要包含 ASP.NET Core 中基本檔案、功能和相依性的微服務及應用程式。
本教學課程將教導您使用 ASP.NET Core 建立基本 API 的基本概念。 在 ASP.NET Core 中建立 API 的另一種方法是使用控制器。 如需在基本 API 與控制器型 API 之間選擇的說明,請參閱 API 概觀。 如需根據包含更多功能的控制器建立 API 專案的教學課程,請參閱建立 Web API。
概觀
本教學課程會建立以下 API:
API | 描述 | 要求本文 | 回應本文 |
---|---|---|---|
GET /todoitems |
取得所有待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/complete |
取得已完成的待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/{id} |
依識別碼取得項目 | 無 | 待辦事項 |
POST /todoitems |
新增記錄 | 待辦事項 | 待辦事項 |
PUT /todoitems/{id} |
更新現有的項目 | 待辦事項 | 無 |
DELETE /todoitems/{id} |
刪除項目 | 無 | 無 |
必要條件
Visual Studio 2022 和 ASP.NET 與 Web 開發工作負載。
建立 API 專案
啟動 Visual Studio 2022 並選取 [建立新專案]。
在 [建立新專案] 對話方塊中:
- 在 [搜尋範本] 搜尋方塊中輸入
Empty
。 - 選取 [ASP.NET Core 空白] 範本,然後選取 [下一步]。
- 在 [搜尋範本] 搜尋方塊中輸入
將專案命名為 TodoApi,然後選取 [下一步]。
在 [其他資訊] 對話方塊中:
- 選取 [.NET 8.0 (長期支援)]
- 取消勾選 [不要使用最上層陳述式]
- 選取 [建立]
檢查程式碼
Program.cs
檔案包含下列程式碼:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
上述 程式碼:
- 使用預先設定的預設值建立 WebApplicationBuilder 和 WebApplication。
- 建立傳回
Hello World!
的 HTTP GET 端點/
:
執行應用程式
按 Ctrl+F5 即可執行而不使用偵錯工具。
Visual Studio 會顯示下列對話方塊:
如果您信任 IIS Express SSL 憑證,請選取 [是]。
此時會顯示下列對話方塊:
若您同意信任開發憑證,請選取 [是]。
如需關於信任 Firefox 瀏覽器的資訊,請參閱 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 憑證錯誤。
Visual Studio 會啟動 Kestrel 網頁伺服器,並開啟瀏覽器視窗。
Hello World!
隨即在瀏覽器中顯示。 Program.cs
檔案包含最基本但完整的應用程式。
關閉瀏覽器視窗。
新增 NuGet 套件
必須新增 NuGet 封裝,以支援本教學課程中使用的資料庫和診斷。
- 在 [工具] 功能表上,選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]。
- 選取 [瀏覽] 索引標籤。
- 在搜尋方塊中輸入 Microsoft.EntityFrameworkCore.InMemory,然後選取
Microsoft.EntityFrameworkCore.InMemory
。 - 選取右窗格中的 [專案] 核取方塊,然後選取 [安裝]。
- 遵循上述指示來新增
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
封裝。
模型和資料庫內容類別
- 在專案資料夾中,使用下列程式碼建立名為
Todo.cs
的檔案:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
上述程式碼會為此應用程式建立模型。 模型是一個類別,其代表應用程式管理的資料。
- 使用下列程式碼建立名為
TodoDb.cs
的檔案:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
上述程式碼會定義資料庫內容,這是協調資料模型 Entity Framework 功能的主要類別。 此類別衍生自 Microsoft.EntityFrameworkCore.DbContext 類別。
新增 API 程式碼
- 以下列程式碼來取代
Program.cs
檔案的內容:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
下列反白顯示的程式碼會將資料庫內容新增至相依性插入 (DI) 容器,並啟用顯示資料庫相關的例外狀況:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI 容器可讓您存取資料庫內容和其他服務。
本教學課程使用端點總管和 .http 檔案來測試 API。
測試張貼資料
Program.cs
中的下列程式碼會建立 HTTP POST 端點 /todoitems
,以將資料新增至記憶體內部資料庫:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
執行應用程式。 瀏覽器會顯示 404 錯誤,因為不再有 /
端點。
會使用 POST 端點將資料新增至應用程式。
選取 [檢視] > [其他視窗] > [端點總管]。
以滑鼠右鍵按一下 [POST] 端點,然後選取 [產生要求]。
隨即會在專案資料夾中建立名為
TodoApi.http
的新檔案,其內容類別似下列範例:@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- 第一行會建立用於所有端點的變數。
- 下一行會定義 POST 要求。
- 三重主題標籤 (
###
) 行是要求分隔符號:其之後則用於不同的要求。
POST 要求需要標頭和本文。 若要定義要求的這些部分,請緊接在 POST 要求行後面新增下列幾行:
Content-Type: application/json { "name":"walk dog", "isComplete":true }
上述程式碼會新增 Content-Type 標頭和 JSON 要求本文。 TodoApi.http 檔案現在看起來應該類似下列範例,但有您的連接埠號碼:
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
執行應用程式。
選取
POST
要求行上方的 [傳送要求] 連結。POST 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。
檢查 GET 端點
範例應用程式會藉由呼叫 MapGet
來實作數個 GET 端點:
API | 描述 | 要求本文 | 回應本文 |
---|---|---|---|
GET /todoitems |
取得所有待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/complete |
取得所有已完成的待辦事項 | 無 | 待辦事項的陣列 |
GET /todoitems/{id} |
依識別碼取得項目 | 無 | 待辦事項 |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
測試 GET 端點
從瀏覽器或使用端點總管呼叫 GET
端點,以測試應用程式。 下列步驟適用端點總管。
在 [端點總管] 中,以滑鼠右鍵按一下第一個 GET 端點,然後選取 [產生要求]。
下列內容會新增至
TodoApi.http
檔案:Get {{TodoApi_HostAddress}}/todoitems ###
選取新
GET
要求行上方的 [傳送要求] 連結。GET 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。
回應主體類似以下 JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
在 [端點總管] 中,以滑鼠右鍵按一下
/todoitems/{id}
GET 端點,然後選取 [產生要求]。 下列內容會新增至TodoApi.http
檔案:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
把
{id}
替換為1
。選取新 GET 要求行上方的 [傳送要求] 連結。
GET 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。
回應主體類似以下 JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
這個應用程式會使用記憶體內部資料庫。 如果應用程式重新啟動,GET 要求不會傳回任何資料。 如果未傳回任何資料,請將 POST 資料傳送至應用程式,然後再試一次 GET 要求。
傳回值
ASP.NET Core 會自動將物件序列化為 JSON,並將 JSON 寫入至回應訊息的本文。 此傳回型別的回應碼為 200 OK,假設沒有任何未處理的例外狀況。 未處理的例外狀況會轉譯成 5xx 錯誤。
傳回型別可代表各種 HTTP 狀態碼。 例如,GET /todoitems/{id}
可傳回兩個不同的狀態值:
檢查 PUT 端點
範例應用程式會使用 MapPut
實作單一 PUT 端點:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
此方法與 MapPost
方法類似,除了它使用 HTTP PUT。 成功的回應會傳回 204 (無內容)。 根據 HTTP 規格,PUT 要求需要用戶端傳送整個更新的實體,而不只是變更。 若要支援部分更新,請使用 HTTP PATCH。
測試 PUT 端點
此範例使用在每次應用程式啟動都必須初始化的記憶體內部資料庫。 資料庫中必須有項目,您才能進行 PUT 呼叫。 在發出 PUT 呼叫之前,呼叫 GET 以確保資料庫中有項目。
更新 Id = 1
的待辦事項,並將其名稱設定為 "feed fish"
。
在 [端點總管] 中,以滑鼠右鍵按一下 PUT 端點,然後選取 [產生要求]。
下列內容會新增至
TodoApi.http
檔案:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
在 PUT 要求行中,將
{id}
取代為1
。緊接在 PUT 要求行後面新增下列幾行:
Content-Type: application/json { "name": "feed fish", "isComplete": false }
上述程式碼會新增 Content-Type 標頭和 JSON 要求本文。
選取新 PUT 要求行上方的 [傳送要求] 連結。
PUT 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。 回應本文是空白,而狀態碼為 204。
檢查及測試 DELETE 端點
範例應用程式會使用 MapDelete
實作單一 DELETE 端點:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
在 [端點總管] 中,以滑鼠右鍵按一下 DELETE 端點,然後選取 [產生要求]。
DELETE 要求會新增至
TodoApi.http
。將 DELETE 要求行中的
{id}
取代為1
。 DELETE 要求看起來應該類似下列範例:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
選取 DELETE 要求的 [傳送要求] 連結。
DELETE 要求會傳送至應用程式,而回應會顯示在 [回應] 窗格中。 回應本文是空白,而狀態碼為 204。
使用 MapGroup API
範例應用程式程式碼會在每次設定端點時重複 todoitems
URL 前置詞。 API 通常會有通用 URL 前置詞的端點群組,而且 MapGroup 方法可用來協助組織這類群組。 其可減少重複的程式碼,並允許使用對方法 (例如 RequireAuthorization 和 WithMetadata) 的單一呼叫來自訂整個端點群組。
以下列程式碼取代 Program.cs
的內容:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
上述程式碼有下列變更:
- 新增
var todoItems = app.MapGroup("/todoitems");
以使用 URL 前置詞/todoitems
來設定群組。 - 將所有
app.Map<HttpVerb>
方法變更為todoItems.Map<HttpVerb>
。 - 從
Map<HttpVerb>
方法呼叫中移除 URL 前置詞/todoitems
。
測試端點以驗證其運作方式相同。
使用 TypedResults API
傳回 TypedResults 而不是 Results 有幾個優點,包括可測試性,並自動傳回 OpenAPI 的回應型別中繼資料來描述端點。 如需詳細資訊,請參閱 TypedResults 與 Results。
Map<HttpVerb>
方法可以呼叫路由處理常式方法,而不使用 Lambda。 若要查看範例,請使用下列程式碼更新 Program.cs:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Map<HttpVerb>
程式碼現在會呼叫方法,而不是 Lambda:
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
這些方法會傳回實作 IResult 的物件,且由 TypedResults 定義:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
單元測試可以呼叫這些方法,並測試它們傳回正確的型別。 例如,如果方法為 GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
單元測試程式碼可以驗證型別 Ok<Todo[]> 的物件是從處理常式方法傳回。 例如:
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
防止過度張貼
目前範例應用程式會公開整個 Todo
物件。 生產應用程式 在生產應用程式中,通常會使用模型子集來限制可輸入和傳回的資料。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本文中會使用 DTO。
DTO 可用來:
- 防止過度張貼。
- 隱藏用戶端不應該檢視的屬性。
- 省略一些屬性,以減少承載大小。
- 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。
若要示範 DTO 方法,請更新 Todo
類別以包含祕密欄位:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
祕密欄位必須從此應用程式隱藏,但系統管理應用程式可以選擇公開它。
驗證您可以張貼並取得祕密欄位。
使用下列程式碼建立名為 TodoItemDTO.cs
的檔案:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
以下列程式碼取代 Program.cs
檔案的內容,以使用此 DTO 模型:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
RouteGroupBuilder todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db) {
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
todoItemDTO = new TodoItemDTO(todoItem);
return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
驗證您可以張貼並取得祕密欄位以外的所有欄位。
使用已完成的範例進行疑難排解
若您遇到無法解決的問題,請將您的程式碼與已完成的專案進行比較。 檢視或下載已完成的專案 (如何下載)。
下一步
- 設定 JSON 序列化選項。
- 處理錯誤和例外狀況:開發人員例外狀況頁面預設會在針對基本 API 應用程式的開發環境中啟用。 如需如何處理錯誤和例外狀況的相關資訊,請參閱在 ASP.NET Core API 中處理錯誤。
- 如需測試基本 API 應用程式的範例,請參閱此 GitHub 範例。
- 基本 API 中的 OpenAPI 支援。
- 快速入門:發佈至 Azure。
- 組織 ASP.NET Core 基本 API。
深入了解
請參閱基本 API 快速參考