教學課程:使用 ASP.NET Core 建立 Web API

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前版本,請參閱本文的 .NET 8 版本

作者:Rick AndersonKirk Larkin

本教學課程將教導建置使用資料庫之控制器型 Web API 的基本概念。 在 ASP.NET Core 中建立 API 的另一種方法是建立最小的 API。 如需在最小 API 與控制器型 API 之間選擇的說明,請參閱 API 概觀。 如需建立最小 API 的教學課程,請參閱教學課程:使用 ASP.NET Core 建立最小 API

概觀

本教學課程會建立以下 API:

API 描述 要求本文 回應本文
GET /api/todoitems 取得所有待辦事項 待辦事項的陣列
GET /api/todoitems/{id} 依識別碼取得項目 待辦事項
POST /api/todoitems 新增記錄 待辦事項 待辦事項
PUT /api/todoitems/{id} 更新現有的項目 待辦事項
DELETE /api/todoitems/{id}     刪除項目

下圖顯示應用程式的設計。

左側方塊代表用戶端。它會送出要求並接收來自應用程式 (右側繪製的方塊) 的回應。在應用程式方塊中,三個方塊代表控制器、模型以及資料存取層。要求進入應用程式的控制器,而在控制器與資料存取層之間進行讀取/寫入作業。模型會序列化並在回應中傳回至用戶端。

必要條件

建立 Web 專案

  • 從 [檔案] 功能表選取 [新增] >[專案] 。
  • 在搜尋方塊中輸入 Web API
  • 選取 ASP.NET Core Web API 範本,然後選取 [下一步]
  • 在 [設定新專案] 對話方塊中,將專案命名為 TodoApi,然後選取 [下一步]
  • 在 [其他資訊] 對話方塊中:
    • 確認 Framework.NET 8.0 (長期支援)
    • 確認已核取 [使用控制器 (取消核取以使用最小 API)] 核取方塊。
    • 確認已核取 [啟用 OpenAPI 支援] 核取方塊。
    • 選取建立

新增 NuGet 套件

必須新增 NuGet 套件,才能支援本教學課程中使用的資料庫。

  • 在 [工具] 功能表上,選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]
  • 選取 [瀏覽] 索引標籤。
  • 在搜尋方塊中輸入 Microsoft.EntityFrameworkCore.InMemory,然後選取 Microsoft.EntityFrameworkCore.InMemory
  • 選取右窗格中的 [專案] 核取方塊,然後選取 [安裝]

注意

如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件)安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。

測試專案

專案範本會建立支援 SwaggerWeatherForecast API。

按 Ctrl+F5 即可執行而不使用偵錯工具。

當專案尚未設定為使用 SSL 時,Visual Studio 會顯示下列對話方塊:

此專案設定為使用 SSL。為避免瀏覽器中的 SSL 警告,您可以選擇信任 IIS Express 產生的自我簽署憑證。您要信任 IIS Express SSL 憑證嗎?

如果您信任 IIS Express SSL 憑證,請選取 [是]

此時會顯示下列對話方塊:

安全性警告對話方塊

若您同意信任開發憑證,請選取 [是]

如需關於信任 Firefox 瀏覽器的資訊,請參閱 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 憑證錯誤

Visual Studio 會啟動預設瀏覽器並瀏覽至 https://localhost:<port>/swagger/index.html,其中 <port> 是專案建立時設定的隨機選擇連接埠號碼。

Swagger 頁面 /swagger/index.html 隨即顯示。 選取 [GET]>[試用]>[執行]。 頁面會顯示:

  • 用來測試 WeatherForecast API 的 Curl 命令。
  • 用來測試 WeatherForecast API 的 URL。
  • 回應碼、本文和標頭。
  • 具有媒體類型與範例值和架構的下拉式清單方塊。

如果 Swagger 頁面未出現,請參閱這個 GitHub 問題

Swagger 可用來為 Web API 產生有用的文件和說明頁面。 本教學課程會使用 Swagger 來測試應用程式。 如需 Swagger 的詳細資訊,請參閱使用 ASP.NET Core Web API 文件搭配 Swagger / OpenAPI

複製並貼上瀏覽器中的要求 URLhttps://localhost:<port>/weatherforecast

系統會傳回類似下列範例的 JSON:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

新增模型類別

「模型」是代表應用程式所管理資料的一組類別。 此應用程式的模型是 TodoItem 類別。

  • 在 [方案總管] 中,以滑鼠右鍵按一下專案。 選取 [新增]>[新增資料夾]。 將資料夾命名為 Models註冊免費試用帳戶。
  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoItem,然後選取 [新增]
  • 以下列項目取代範本程式碼:
namespace TodoApi.Models;

public class TodoItem
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Id 屬性的功能相當於關聯式資料庫中的唯一索引鍵。

模型類別可位於專案中的任何位置,但依照慣例會使用 Models 資料夾。

新增資料庫內容

「資料庫內容」是為資料模型協調 Entity Framework 功能的主要類別。 此類別是透過衍生自 Microsoft.EntityFrameworkCore.DbContext 類別來建立。

  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoContext,然後按一下 [新增]
  • 輸入下列程式碼:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models;
    
    public class TodoContext : DbContext
    {
        public TodoContext(DbContextOptions<TodoContext> options)
            : base(options)
        {
        }
    
        public DbSet<TodoItem> TodoItems { get; set; } = null!;
    }
    

登錄資料庫內容

在 ASP.NET Core 中,資料庫內容等服務必須向相依性插入 (DI) 容器註冊。 此容器會將服務提供給控制器。

使用下列醒目提示的程式碼更新 Program.cs

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

上述 程式碼:

  • 加入 using 指示詞。
  • 將資料庫內容新增至 DI 容器。
  • 指定資料庫內容將會使用記憶體內部資料庫。

Scaffold 控制器

  • 以滑鼠右鍵按一下 Controllers 資料夾。

  • 選取 [新增]> [新增 Scaffold 項目]

  • 選取 [使用 Entity Framework 執行動作的 API 控制器],然後選取 [新增]

  • 在 [使用 Entity Framework 執行動作的 API 控制器] 對話方塊中:

    • 在 [模型類別] 中選取 [TodoItem (TodoApi.Models)]
    • 在 [資料內容類別] 中選取 [TodoContext (TodoApi.Models)]
    • 選取新增

    如果 Scaffolding 作業失敗,請選取 [新增] 以嘗試第二次 Scaffolding。

產生的程式碼:

  • 使用 [ApiController] 屬性標記類別。 這個屬性表示控制器會回應 Web API 要求。 如需屬性啟用的特定行為相關資訊,請參閱 使用 ASP.NET Core 建立 Web API
  • 使用 DI 將資料庫內容 (TodoContext) 插入到控制器中。 控制器中的每一個 CRUD 方法都會使用資料庫內容。

ASP.NET Core 範本適用於:

  • 路由範本中檢視包含 [action] 的控制器。
  • API 控制器在路由範本中不包含 [action]

[action] 權杖不在路由範本中時,動作名稱 (方法名稱) 不會包含在端點中。 也就是說,動作的相關聯方法名稱不會用於比對路由。

更新 PostTodoItem 建立方法

更新 PostTodoItem 中的 return 陳述式,以使用 nameof 運算子:

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //    return CreatedAtAction("PostTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(PostTodoItem), new { id = todoItem.Id }, todoItem);
}

上述程式碼是 HTTP POST 方法,以 [HttpPost] 屬性表示。 該方法會從 HTTP 要求本文取得 TodoItem 的值。

如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

CreatedAtAction 方法:

  • 成功時會傳回 HTTP 201 狀態碼。 對於可在伺服器上建立新資源的 HTTP POST 方法,HTTP 201 是標準回應。
  • Location標頭新增到回應。 Location 標頭會指定新建待辦事項的 URI。 如需詳細資訊,請參閱 10.2.2 201 Created (已建立 10.2.2 201)。
  • 參考 PostTodoItem 動作以建立 Location 標頭的 URI。 C# nameof 關鍵字是用來避免在 CreatedAtAction 呼叫中以硬式編碼方式寫入動作名稱。

測試 PostTodoItem

  • 按 Ctrl+F5 執行應用程式。

  • 在 Swagger 瀏覽器視窗中,選取 [POST /api/TodoItems],然後選取 [試用]

  • 在 [要求本文] 輸入視窗中,更新 JSON。 例如,

    {
      "name": "walk dog",
      "isComplete": true
    }
    
  • 選取 [執行]

    Swagger POST

測試位置標頭 URI

在上述 POST 中,Swagger UI 會顯示 [回應] 標頭底下的位置標頭。 例如: location: https://localhost:7260/api/TodoItems/1 。 位置標頭會顯示所建立資源的 URI。

若要測試位置標頭:

  • 在 Swagger 瀏覽器視窗中,選取 [GET /api/TodoItems/{id}],然後選取 [試用]

  • id 輸入方塊中輸入 1,然後選取 [執行]

    Swagger GET

檢查 GET 方法

兩個 GET 端點即會實作:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

上一節顯示 /api/todoitems/{id} 路由的範例。

依照 POST 指示新增另一個待辦事項,然後使用 Swagger 測試 /api/todoitems 路由。

這個應用程式會使用記憶體內部資料庫。 如果應用程式在停止後再啟動,上述 GET 要求將不會傳回任何資料。 如果沒有傳回任何資料,請將資料 POST 到應用程式。

傳送和 URL 路徑

[HttpGet] 屬性代表回應 HTTP GET 要求的方法。 每個方法的 URL 路徑的建構方式如下:

  • 一開始在控制器的 Route 屬性中使用範本字串:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    
  • 以控制器的名稱取代 [controller],也就是將控制器類別名稱減去 "Controller" 字尾。 在此範例中,控制器類別名稱是 TodoItemsController,因此控制器名稱是 "TodoItems"。 ASP.NET Core 路由不區分大小寫。

  • 如果 [HttpGet] 屬性具有路由範本 (例如 [HttpGet("products")]),請將其附加到路徑。 此範例不使用範本。 如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

在下列 GetTodoItem 方法中,"{id}" 是待辦事項唯一識別碼的預留位置變數。 在叫用 GetTodoItem 時,會將 URL 中的 "{id}" 值提供給方法的 id 參數。

[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

傳回值

GetTodoItemsGetTodoItem 方法的傳回型別為 ActionResult<T> 類型。 ASP.NET Core 會自動將物件序列化為 JSON,並將 JSON 寫入至回應訊息的本文。 此傳回型別的回應碼為 200 OK,假設沒有任何未處理的例外狀況。 未處理的例外狀況會轉譯成 5xx 錯誤。

ActionResult 傳回型別可代表各種 HTTP 狀態碼。 例如,GetTodoItem 可傳回兩個不同的狀態值:

  • 如果沒有項目符合所要求的識別碼,方法會傳回 404 狀態NotFound錯誤碼。
  • 否則,方法會傳回 200 與 JSON 回應本文。 傳回 HTTP 200 回應中的 item 結果。

PutTodoItem 方法

檢查 PutTodoItem 方法:

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem 類似於 PostTodoItem,但是會使用 HTTP PUT。 回應是 204 (No Content) (204 (沒有內容))。 根據 HTTP 規格,PUT 要求需要用戶端傳送整個更新的實體,而不只是變更。 若要支援部分更新,請使用 HTTP PATCH

測試 PutTodoItem 方法

此範例使用在每次應用程式啟動都必須初始化的記憶體內部資料庫。 資料庫中必須有項目,您才能進行 PUT 呼叫。 在發出 PUT 呼叫之前,呼叫 GET 以確保資料庫中有項目。

使用 Swagger UI,使用 PUT 按鈕來更新識別碼為 1 的 TodoItem,並將其名稱設定為 "feed fish"。 請注意,回應為 HTTP 204 No Content

DeleteTodoItem 方法

檢查 DeleteTodoItem 方法:

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

測試 DeleteTodoItem 方法

使用 Swagger UI 刪除識別碼為 1 的 TodoItem。 請注意,回應為 HTTP 204 No Content

使用其他工具進行測試

還有其他許多工具可用來測試 Web API,例如:

如需詳細資訊,請參閱

防止過度張貼

目前範例應用程式會公開整個 TodoItem 物件。 生產應用程式通常會限制輸入的資料,並使用模型的子集傳回。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本教學課程中使用 DTO

DTO 可用來:

  • 防止過度張貼。
  • 隱藏用戶端不應該檢視的屬性。
  • 省略一些屬性,以減少承載大小。
  • 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。

若要示範 DTO 方法,請更新 TodoItem 類別以包含祕密欄位:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
        public string? Secret { get; set; }
    }
}

祕密欄位必須從此應用程式隱藏,但系統管理應用程式可以選擇公開它。

確認您可以張貼並取得祕密欄位。

建立 DTO 模型:

namespace TodoApi.Models;

public class TodoItemDTO
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

更新 TodoItemsController 以使用 TodoItemDTO

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
    private readonly TodoContext _context;

    public TodoItemsController(TodoContext context)
    {
        _context = context;
    }

    // GET: api/TodoItems
    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }

    // GET: api/TodoItems/5
    // <snippet_GetByID>
    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        return ItemToDTO(todoItem);
    }
    // </snippet_GetByID>

    // PUT: api/TodoItems/5
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Update>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO todoDTO)
    {
        if (id != todoDTO.Id)
        {
            return BadRequest();
        }

        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        todoItem.Name = todoDTO.Name;
        todoItem.IsComplete = todoDTO.IsComplete;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }

        return NoContent();
    }
    // </snippet_Update>

    // POST: api/TodoItems
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Create>
    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO todoDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoDTO.IsComplete,
            Name = todoDTO.Name
        };

        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }
    // </snippet_Create>

    // DELETE: api/TodoItems/5
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        _context.TodoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool TodoItemExists(long id)
    {
        return _context.TodoItems.Any(e => e.Id == id);
    }

    private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
       new TodoItemDTO
       {
           Id = todoItem.Id,
           Name = todoItem.Name,
           IsComplete = todoItem.IsComplete
       };
}

確認您無法張貼或取得祕密欄位。

使用 JavaScript 呼叫 Web API

請參閱教學課程:使用 JavaScript 呼叫 ASP.NET Core Web API

Web API 影片系列

請參閱影片:初學者系列:Web API

可靠的 Web 應用程式模式

請參閱 The Reliable Web App Pattern for.NETYouTube 影片文章,以取得從頭建立新式、可靠、效能強、可測試、具成本效益及可調整的 ASP.NET Core 應用程式或重構現有應用程式的指導。

將驗證支援新增至 Web API

ASP.NET Core Identity 會將使用者介面 (UI) 登入功能新增至 ASP.NET Core Web 應用程式。 若要保護 Web API 和 SPA,請使用下列其中一項:

Duende Identity 伺服器是適用於 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 架構。 Duende Identity 伺服器會啟用下列安全性功能:

  • 驗證即服務 (AaaS)
  • 多個應用程式類型的單一登入/登出 (SSO)
  • API 的存取控制
  • Federation Gateway

重要

Duende Software 可能會要求您支付授權費用才能在生產環境中使用 Duende Identity 伺服器。 如需詳細資訊,請參閱從 ASP.NET Core 5.0 移轉至 6.0

如需詳細資訊,請參閱 Duende Identity 伺服器文件 (Duende Software 網站)。

發佈至 Azure

如需部署至 Azure 的資訊,請參閱 快速入門:部署 ASP.NET Web 應用程式

其他資源

檢視或下載本教學課程的範例程式碼。 請參閱如何下載

如需詳細資訊,請參閱以下資源:

本教學課程將教導建置使用資料庫之控制器型 Web API 的基本概念。 在 ASP.NET Core 中建立 API 的另一種方法是建立最小的 API。 如需在最小 API 與控制器型 API 之間選擇的說明,請參閱 API 概觀。 如需建立最小 API 的教學課程,請參閱教學課程:使用 ASP.NET Core 建立最小 API

概觀

本教學課程會建立以下 API:

API 描述 要求本文 回應本文
GET /api/todoitems 取得所有待辦事項 待辦事項的陣列
GET /api/todoitems/{id} 依識別碼取得項目 待辦事項
POST /api/todoitems 新增記錄 待辦事項 待辦事項
PUT /api/todoitems/{id} 更新現有的項目 待辦事項
DELETE /api/todoitems/{id}     刪除項目

下圖顯示應用程式的設計。

左側方塊代表用戶端。它會送出要求並接收來自應用程式 (右側繪製的方塊) 的回應。在應用程式方塊中,三個方塊代表控制器、模型以及資料存取層。要求進入應用程式的控制器,而在控制器與資料存取層之間進行讀取/寫入作業。模型會序列化並在回應中傳回至用戶端。

必要條件

建立 Web 專案

  • 從 [檔案] 功能表選取 [新增] >[專案] 。
  • 在搜尋方塊中輸入 Web API
  • 選取 ASP.NET Core Web API 範本,然後選取 [下一步]
  • 在 [設定新專案] 對話方塊中,將專案命名為 TodoApi,然後選取 [下一步]
  • 在 [其他資訊] 對話方塊中:
    • 確認 Framework.NET 7.0 (或更新版本)。
    • 確認已核取 [使用控制器 (取消核取以使用最小 API)] 核取方塊。
    • 選取建立

注意

如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件)安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。

測試專案

專案範本會建立支援 SwaggerWeatherForecast API。

按 Ctrl+F5 即可執行而不使用偵錯工具。

當專案尚未設定為使用 SSL 時,Visual Studio 會顯示下列對話方塊:

此專案設定為使用 SSL。為避免瀏覽器中的 SSL 警告,您可以選擇信任 IIS Express 產生的自我簽署憑證。您要信任 IIS Express SSL 憑證嗎?

如果您信任 IIS Express SSL 憑證,請選取 [是]

此時會顯示下列對話方塊:

安全性警告對話方塊

若您同意信任開發憑證,請選取 [是]

如需關於信任 Firefox 瀏覽器的資訊,請參閱 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 憑證錯誤

Visual Studio 會啟動瀏覽器並瀏覽至 https://localhost:<port>/swagger/index.html,其中 <port> 是隨機選擇的連接埠號碼。

Swagger 頁面 /swagger/index.html 隨即顯示。 選取 [GET]>[試用]>[執行]。 頁面會顯示:

  • 用來測試 WeatherForecast API 的 Curl 命令。
  • 用來測試 WeatherForecast API 的 URL。
  • 回應碼、本文和標頭。
  • 具有媒體類型與範例值和架構的下拉式清單方塊。

如果 Swagger 頁面未出現,請參閱這個 GitHub 問題

Swagger 可用來為 Web API 產生有用的文件和說明頁面。 本教學課程著重於建立 Web API。 如需 Swagger 的詳細資訊,請參閱使用 ASP.NET Core Web API 文件搭配 Swagger / OpenAPI

複製並貼上瀏覽器中的要求 URLhttps://localhost:<port>/weatherforecast

系統會傳回類似下列範例的 JSON:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

新增模型類別

「模型」是代表應用程式所管理資料的一組類別。 此應用程式的模型是 TodoItem 類別。

  • 在 [方案總管] 中,以滑鼠右鍵按一下專案。 選取 [新增]>[新增資料夾]。 將資料夾命名為 Models註冊免費試用帳戶。
  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoItem,然後選取 [新增]
  • 以下列項目取代範本程式碼:
namespace TodoApi.Models;

public class TodoItem
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Id 屬性的功能相當於關聯式資料庫中的唯一索引鍵。

模型類別可位於專案中的任何位置,但依照慣例會使用 Models 資料夾。

新增資料庫內容

「資料庫內容」是為資料模型協調 Entity Framework 功能的主要類別。 此類別是透過衍生自 Microsoft.EntityFrameworkCore.DbContext 類別來建立。

新增 NuGet 套件

  • 在 [工具] 功能表上,選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]
  • 選取 [瀏覽] 索引標籤,然後在搜尋方塊中輸入 Microsoft.EntityFrameworkCore.InMemory
  • 在左窗格中選取 Microsoft.EntityFrameworkCore.InMemory
  • 選取右窗格中的 [專案] 核取方塊,然後選取 [安裝]

新增 TodoCoNtext 資料庫內容

  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoContext,然後按一下 [新增]
  • 輸入下列程式碼:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models;
    
    public class TodoContext : DbContext
    {
        public TodoContext(DbContextOptions<TodoContext> options)
            : base(options)
        {
        }
    
        public DbSet<TodoItem> TodoItems { get; set; } = null!;
    }
    

登錄資料庫內容

在 ASP.NET Core 中,資料庫內容等服務必須向相依性插入 (DI) 容器註冊。 此容器會將服務提供給控制器。

使用下列醒目提示的程式碼更新 Program.cs

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

上述 程式碼:

  • 加入 using 指示詞。
  • 將資料庫內容新增至 DI 容器。
  • 指定資料庫內容將會使用記憶體內部資料庫。

Scaffold 控制器

  • 以滑鼠右鍵按一下 Controllers 資料夾。

  • 選取 [新增]> [新增 Scaffold 項目]

  • 選取 [使用 Entity Framework 執行動作的 API 控制器],然後選取 [新增]

  • 在 [使用 Entity Framework 執行動作的 API 控制器] 對話方塊中:

    • 在 [模型類別] 中選取 [TodoItem (TodoApi.Models)]
    • 在 [資料內容類別] 中選取 [TodoContext (TodoApi.Models)]
    • 選取新增

    如果 Scaffolding 作業失敗,請選取 [新增] 以嘗試第二次 Scaffolding。

產生的程式碼:

  • 使用 [ApiController] 屬性標記類別。 這個屬性表示控制器會回應 Web API 要求。 如需屬性啟用的特定行為相關資訊,請參閱 使用 ASP.NET Core 建立 Web API
  • 使用 DI 將資料庫內容 (TodoContext) 插入到控制器中。 控制器中的每一個 CRUD 方法都會使用資料庫內容。

ASP.NET Core 範本適用於:

  • 路由範本中檢視包含 [action] 的控制器。
  • API 控制器在路由範本中不包含 [action]

[action] 權杖不在路由範本中時,動作名稱 (方法名稱) 不會包含在端點中。 也就是說,動作的相關聯方法名稱不會用於比對路由。

更新 PostTodoItem 建立方法

更新 PostTodoItem 中的 return 陳述式,以使用 nameof 運算子:

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //    return CreatedAtAction("PostTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(PostTodoItem), new { id = todoItem.Id }, todoItem);
}

上述程式碼是 HTTP POST 方法,以 [HttpPost] 屬性表示。 該方法會從 HTTP 要求本文取得 TodoItem 的值。

如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

CreatedAtAction 方法:

  • 成功時會傳回 HTTP 201 狀態碼。 對於可在伺服器上建立新資源的 HTTP POST 方法,HTTP 201 是標準回應。
  • Location標頭新增到回應。 Location 標頭會指定新建待辦事項的 URI。 如需詳細資訊,請參閱 10.2.2 201 Created (已建立 10.2.2 201)。
  • 參考 PostTodoItem 動作以建立 Location 標頭的 URI。 C# nameof 關鍵字是用來避免在 CreatedAtAction 呼叫中以硬式編碼方式寫入動作名稱。

測試 PostTodoItem

  • 按 Ctrl+F5 執行應用程式。

  • 在 Swagger 瀏覽器視窗中,選取 [POST /api/TodoItems],然後選取 [試用]

  • 在 [要求本文] 輸入視窗中,更新 JSON。 例如,

    {
      "name": "walk dog",
      "isComplete": true
    }
    
  • 選取 [執行]

    Swagger POST

測試位置標頭 URI

在上述 POST 中,Swagger UI 會顯示 [回應] 標頭底下的位置標頭。 例如: location: https://localhost:7260/api/TodoItems/1 。 位置標頭會顯示所建立資源的 URI。

若要測試位置標頭:

  • 在 Swagger 瀏覽器視窗中,選取 [GET /api/TodoItems/{id}],然後選取 [試用]

  • id 輸入方塊中輸入 1,然後選取 [執行]

    Swagger GET

檢查 GET 方法

兩個 GET 端點即會實作:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

上一節顯示 /api/todoitems/{id} 路由的範例。

依照 POST 指示新增另一個待辦事項,然後使用 Swagger 測試 /api/todoitems 路由。

這個應用程式會使用記憶體內部資料庫。 如果應用程式在停止後再啟動,上述 GET 要求將不會傳回任何資料。 如果沒有傳回任何資料,請將資料 POST 到應用程式。

傳送和 URL 路徑

[HttpGet] 屬性代表回應 HTTP GET 要求的方法。 每個方法的 URL 路徑的建構方式如下:

  • 一開始在控制器的 Route 屬性中使用範本字串:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    
  • 以控制器的名稱取代 [controller],也就是將控制器類別名稱減去 "Controller" 字尾。 在此範例中,控制器類別名稱是 TodoItemsController,因此控制器名稱是 "TodoItems"。 ASP.NET Core 路由不區分大小寫。

  • 如果 [HttpGet] 屬性具有路由範本 (例如 [HttpGet("products")]),請將其附加到路徑。 此範例不使用範本。 如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

在下列 GetTodoItem 方法中,"{id}" 是待辦事項唯一識別碼的預留位置變數。 在叫用 GetTodoItem 時,會將 URL 中的 "{id}" 值提供給方法的 id 參數。

[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

傳回值

GetTodoItemsGetTodoItem 方法的傳回型別為 ActionResult<T> 類型。 ASP.NET Core 會自動將物件序列化為 JSON,並將 JSON 寫入至回應訊息的本文。 此傳回型別的回應碼為 200 OK,假設沒有任何未處理的例外狀況。 未處理的例外狀況會轉譯成 5xx 錯誤。

ActionResult 傳回型別可代表各種 HTTP 狀態碼。 例如,GetTodoItem 可傳回兩個不同的狀態值:

  • 如果沒有項目符合所要求的識別碼,方法會傳回 404 狀態NotFound錯誤碼。
  • 否則,方法會傳回 200 與 JSON 回應本文。 傳回 HTTP 200 回應中的 item 結果。

PutTodoItem 方法

檢查 PutTodoItem 方法:

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem 類似於 PostTodoItem,但是會使用 HTTP PUT。 回應是 204 (No Content) (204 (沒有內容))。 根據 HTTP 規格,PUT 要求需要用戶端傳送整個更新的實體,而不只是變更。 若要支援部分更新,請使用 HTTP PATCH

測試 PutTodoItem 方法

此範例使用在每次應用程式啟動都必須初始化的記憶體內部資料庫。 資料庫中必須有項目,您才能進行 PUT 呼叫。 在發出 PUT 呼叫之前,呼叫 GET 以確保資料庫中有項目。

使用 Swagger UI,使用 PUT 按鈕來更新識別碼為 1 的 TodoItem,並將其名稱設定為 "feed fish"。 請注意,回應為 HTTP 204 No Content

DeleteTodoItem 方法

檢查 DeleteTodoItem 方法:

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

測試 DeleteTodoItem 方法

使用 Swagger UI 刪除識別碼為 1 的 TodoItem。 請注意,回應為 HTTP 204 No Content

使用 http-repl、Postman 或 curl 進行測試

http-replPostmancurl 通常用於測試 API 的。 Swagger 會使用 curl 並顯示其提交的 curl 命令。

如需這些工具的指示,請參閱下列連結:

如需 http-repl 的詳細資訊,請參閱使用 HttpRepl 測試 Web API

防止過度張貼

目前範例應用程式會公開整個 TodoItem 物件。 生產應用程式通常會限制輸入的資料,並使用模型的子集傳回。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本教學課程中使用 DTO

DTO 可用來:

  • 防止過度張貼。
  • 隱藏用戶端不應該檢視的屬性。
  • 省略一些屬性,以減少承載大小。
  • 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。

若要示範 DTO 方法,請更新 TodoItem 類別以包含祕密欄位:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
        public string? Secret { get; set; }
    }
}

祕密欄位必須從此應用程式隱藏,但系統管理應用程式可以選擇公開它。

確認您可以張貼並取得祕密欄位。

建立 DTO 模型:

namespace TodoApi.Models;

public class TodoItemDTO
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

更新 TodoItemsController 以使用 TodoItemDTO

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
    private readonly TodoContext _context;

    public TodoItemsController(TodoContext context)
    {
        _context = context;
    }

    // GET: api/TodoItems
    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }

    // GET: api/TodoItems/5
    // <snippet_GetByID>
    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        return ItemToDTO(todoItem);
    }
    // </snippet_GetByID>

    // PUT: api/TodoItems/5
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Update>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO todoDTO)
    {
        if (id != todoDTO.Id)
        {
            return BadRequest();
        }

        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        todoItem.Name = todoDTO.Name;
        todoItem.IsComplete = todoDTO.IsComplete;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }

        return NoContent();
    }
    // </snippet_Update>

    // POST: api/TodoItems
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Create>
    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO todoDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoDTO.IsComplete,
            Name = todoDTO.Name
        };

        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }
    // </snippet_Create>

    // DELETE: api/TodoItems/5
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        _context.TodoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool TodoItemExists(long id)
    {
        return _context.TodoItems.Any(e => e.Id == id);
    }

    private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
       new TodoItemDTO
       {
           Id = todoItem.Id,
           Name = todoItem.Name,
           IsComplete = todoItem.IsComplete
       };
}

確認您無法張貼或取得祕密欄位。

使用 JavaScript 呼叫 Web API

請參閱教學課程:使用 JavaScript 呼叫 ASP.NET Core Web API

Web API 影片系列

請參閱影片:初學者系列:Web API

可靠的 Web 應用程式模式

請參閱 The Reliable Web App Pattern for.NETYouTube 影片文章,以取得從頭建立新式、可靠、效能強、可測試、具成本效益及可調整的 ASP.NET Core 應用程式或重構現有應用程式的指導。

將驗證支援新增至 Web API

ASP.NET Core Identity 會將使用者介面 (UI) 登入功能新增至 ASP.NET Core Web 應用程式。 若要保護 Web API 和 SPA,請使用下列其中一項:

Duende Identity 伺服器是適用於 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 架構。 Duende Identity 伺服器會啟用下列安全性功能:

  • 驗證即服務 (AaaS)
  • 多個應用程式類型的單一登入/登出 (SSO)
  • API 的存取控制
  • Federation Gateway

重要

Duende Software 可能會要求您支付授權費用才能在生產環境中使用 Duende Identity 伺服器。 如需詳細資訊,請參閱從 ASP.NET Core 5.0 移轉至 6.0

如需詳細資訊,請參閱 Duende Identity 伺服器文件 (Duende Software 網站)。

發佈至 Azure

如需部署至 Azure 的資訊,請參閱 快速入門:部署 ASP.NET Web 應用程式

其他資源

檢視或下載本教學課程的範例程式碼。 請參閱如何下載

如需詳細資訊,請參閱以下資源:

本教學課程將教導建置使用資料庫之控制器型 Web API 的基本概念。 在 ASP.NET Core 中建立 API 的另一種方法是建立最小的 API。 如需在最小 API 與控制器型 API 之間選擇的說明,請參閱 API 概觀。 如需建立最小 API 的教學課程,請參閱教學課程:使用 ASP.NET Core 建立最小 API

在本教學課程中,您會了解如何:

  • 建立 Web API 專案。
  • 新增模型類別和資料庫內容。
  • 使用 CRUD 方法 Scaffold 控制器。
  • 設定路由、URL 路徑和傳回值。
  • 使用 http-repl 呼叫 Web API。

結束時,您會有一個 Web API,可以管理儲存在資料庫中的「待辦事項」。

概觀

本教學課程會建立以下 API:

API 描述 要求本文 回應本文
GET /api/todoitems 取得所有待辦事項 待辦事項的陣列
GET /api/todoitems/{id} 依識別碼取得項目 待辦事項
POST /api/todoitems 新增記錄 待辦事項 待辦事項
PUT /api/todoitems/{id} 更新現有的項目 待辦事項
DELETE /api/todoitems/{id}     刪除項目

下圖顯示應用程式的設計。

左側方塊代表用戶端。它會送出要求並接收來自應用程式 (右側繪製的方塊) 的回應。在應用程式方塊中,三個方塊代表控制器、模型以及資料存取層。要求進入應用程式的控制器,而在控制器與資料存取層之間進行讀取/寫入作業。模型會序列化並在回應中傳回至用戶端。

必要條件

建立 Web 專案

  • 從 [檔案] 功能表選取 [新增] >[專案] 。
  • 在搜尋方塊中輸入 Web API
  • 選取 ASP.NET Core Web API 範本,然後選取 [下一步]
  • 在 [設定新專案] 對話方塊中,將專案命名為 TodoApi,然後選取 [下一步]
  • 在 [其他資訊] 對話方塊中:
    • 確認 Framework.NET 6.0 (長期支援)
    • 確認已核取 [使用控制器 (取消核取以使用最小 API)] 核取方塊。
    • 選取建立

注意

如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件)安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。

測試專案

專案範本會建立支援 SwaggerWeatherForecast API。

按 Ctrl+F5 即可執行而不使用偵錯工具。

當專案尚未設定為使用 SSL 時,Visual Studio 會顯示下列對話方塊:

此專案設定為使用 SSL。為避免瀏覽器中的 SSL 警告,您可以選擇信任 IIS Express 產生的自我簽署憑證。您要信任 IIS Express SSL 憑證嗎?

如果您信任 IIS Express SSL 憑證,請選取 [是]

此時會顯示下列對話方塊:

安全性警告對話方塊

若您同意信任開發憑證,請選取 [是]

如需關於信任 Firefox 瀏覽器的資訊,請參閱 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 憑證錯誤

Visual Studio 會啟動瀏覽器並瀏覽至 https://localhost:<port>/swagger/index.html,其中 <port> 是隨機選擇的連接埠號碼。

Swagger 頁面 /swagger/index.html 隨即顯示。 選取 [GET]>[試用]>[執行]。 頁面會顯示:

  • 用來測試 WeatherForecast API 的 Curl 命令。
  • 用來測試 WeatherForecast API 的 URL。
  • 回應碼、本文和標頭。
  • 具有媒體類型與範例值和架構的下拉式清單方塊。

如果 Swagger 頁面未出現,請參閱這個 GitHub 問題

Swagger 可用來為 Web API 產生有用的文件和說明頁面。 本教學課程著重於建立 Web API。 如需 Swagger 的詳細資訊,請參閱使用 ASP.NET Core Web API 文件搭配 Swagger / OpenAPI

複製並貼上瀏覽器中的要求 URLhttps://localhost:<port>/weatherforecast

系統會傳回類似下列範例的 JSON:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

更新 launchUrl

Properties\launchSettings.json中,將 launchUrl"swagger" 更新為 "api/todoitems"

"launchUrl": "api/todoitems",

由於系統會移除 Swagger,因此上述標記會將啟動的 URL 變更為下列各節中所新增控制器的 GET 方法。

新增模型類別

「模型」是代表應用程式所管理資料的一組類別。 此應用程式的模型是單一 TodoItem 類別。

  • 在 [方案總管] 中,以滑鼠右鍵按一下專案。 選取 [新增]>[新增資料夾]。 將資料夾命名為 Models註冊免費試用帳戶。

  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoItem,然後選取 [新增]

  • 以下列項目取代範本程式碼:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Id 屬性的功能相當於關聯式資料庫中的唯一索引鍵。

模型類別可位於專案中的任何位置,但依照慣例會使用 Models 資料夾。

新增資料庫內容

「資料庫內容」是為資料模型協調 Entity Framework 功能的主要類別。 此類別是透過衍生自 Microsoft.EntityFrameworkCore.DbContext 類別來建立。

新增 NuGet 套件

  • 在 [工具] 功能表上,選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]
  • 選取 [瀏覽] 索引標籤,然後在搜尋方塊中輸入 Microsoft.EntityFrameworkCore.InMemory
  • 在左窗格中選取 Microsoft.EntityFrameworkCore.InMemory
  • 選取右窗格中的 [專案] 核取方塊,然後選取 [安裝]

新增 TodoCoNtext 資料庫內容

  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoContext,然後按一下 [新增]
  • 輸入下列程式碼:

    using Microsoft.EntityFrameworkCore;
    using System.Diagnostics.CodeAnalysis;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; } = null!;
        }
    }
    

登錄資料庫內容

在 ASP.NET Core 中,資料庫內容等服務必須向相依性插入 (DI) 容器註冊。 此容器會將服務提供給控制器。

使用下列程式碼更新 Program.cs

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;


var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
//builder.Services.AddSwaggerGen(c =>
//{
//    c.SwaggerDoc("v1", new() { Title = "TodoApi", Version = "v1" });
//});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    //app.UseSwagger();
    //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoApi v1"));
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

上述 程式碼:

  • 移除 Swagger 呼叫。
  • 移除未使用的 using 指示詞。
  • 將資料庫內容新增至 DI 容器。
  • 指定資料庫內容將會使用記憶體內部資料庫。

Scaffold 控制器

  • 以滑鼠右鍵按一下 Controllers 資料夾。

  • 選取 [新增]> [新增 Scaffold 項目]

  • 選取 [使用 Entity Framework 執行動作的 API 控制器],然後選取 [新增]

  • 在 [使用 Entity Framework 執行動作的 API 控制器] 對話方塊中:

    • 在 [模型類別] 中選取 [TodoItem (TodoApi.Models)]
    • 在 [資料內容類別] 中選取 [TodoContext (TodoApi.Models)]
    • 選取新增

    如果 Scaffolding 作業失敗,請選取 [新增] 以嘗試第二次 Scaffolding。

產生的程式碼:

  • 使用 [ApiController] 屬性標記類別。 這個屬性表示控制器會回應 Web API 要求。 如需屬性啟用的特定行為相關資訊,請參閱 使用 ASP.NET Core 建立 Web API
  • 使用 DI 將資料庫內容 (TodoContext) 插入到控制器中。 控制器中的每一個 CRUD 方法都會使用資料庫內容。

ASP.NET Core 範本適用於:

  • 路由範本中檢視包含 [action] 的控制器。
  • API 控制器在路由範本中不包含 [action]

[action] 權杖不在路由範本中時,動作名稱會從路由中排除。 也就是說,動作的相關聯方法名稱不會用於比對路由。

更新 PostTodoItem 建立方法

更新 PostTodoItem 中的 return 陳述式,以使用 nameof 運算子:

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("PostTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(PostTodoItem), new { id = todoItem.Id }, todoItem);
}

上述程式碼是 HTTP POST 方法,以 [HttpPost] 屬性表示。 該方法會從 HTTP 要求本文取得待辦事項的值。

如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

CreatedAtAction 方法:

  • 成功時會傳回 HTTP 201 狀態碼。 對於可在伺服器上建立新資源的 HTTP POST 方法,其標準回應是 HTTP 201。
  • Location標頭新增到回應。 Location 標頭會指定新建待辦事項的 URI。 如需詳細資訊,請參閱 10.2.2 201 Created (已建立 10.2.2 201)。
  • 參考 GetTodoItem 動作以建立 Location 標頭的 URI。 C# nameof 關鍵字是用來避免在 CreatedAtAction 呼叫中以硬式編碼方式寫入動作名稱。

安裝 http-repl

本教學課程使用 http-repl 來測試 Web API。

  • 在命令提示字元執行下列命令:

    dotnet tool install -g Microsoft.dotnet-httprepl
    

    注意

    根據預設,要安裝的 .NET 二進位檔架構代表目前執行的 OS 架構。 若要指定不同的 OS 架構,請參閱 dotnet tool install, --arch option。 如需詳細資訊,請參閱 GitHub 問題 dotnet/AspNetCore.Docs #29262

  • 如果您沒有安裝 .NET 6.0 SDK 或執行階段,請安裝 .NET 6.0 執行階段

測試 PostTodoItem

  • 按 Ctrl+F5 執行應用程式。

  • 開啟新的終端機視窗,然後執行下列命令。 如果您的應用程式使用不同的連接埠號碼,請將 httprepl 命令中的 5001 取代為您的連接埠號碼。

    httprepl https://localhost:5001/api/todoitems
    post -h Content-Type=application/json -c "{"name":"walk dog","isComplete":true}"
    

    以下是命令輸出範例:

    HTTP/1.1 201 Created
    Content-Type: application/json; charset=utf-8
    Date: Tue, 07 Sep 2021 20:39:47 GMT
    Location: https://localhost:5001/api/TodoItems/1
    Server: Kestrel
    Transfer-Encoding: chunked
    
    {
      "id": 1,
      "name": "walk dog",
      "isComplete": true
    }
    

測試位置標頭 URI

若要測試位置標頭,請將其複製並貼到 httprepl get 命令中。

下列範例假設您仍在 httprepl 工作階段中。 如果您結束先前的 httprepl 工作階段,請在下列命令中將 connect 取代為 httprepl

connect https://localhost:5001/api/todoitems/1
get

以下是命令輸出範例:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 07 Sep 2021 20:48:10 GMT
Server: Kestrel
Transfer-Encoding: chunked

{
  "id": 1,
  "name": "walk dog",
  "isComplete": true
}

檢查 GET 方法

兩個 GET 端點即會實作:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

您剛才看到 /api/todoitems/{id} 路由的範例。 測試 /api/todoitems 路由:

connect https://localhost:5001/api/todoitems
get

以下是命令輸出範例:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 07 Sep 2021 20:59:21 GMT
Server: Kestrel
Transfer-Encoding: chunked

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]

這次,傳回的 JSON 是一個項目的陣列。

這個應用程式會使用記憶體內部資料庫。 如果應用程式在停止後再啟動,上述 GET 要求將不會傳回任何資料。 如果沒有傳回任何資料,請將資料 POST 到應用程式。

傳送和 URL 路徑

[HttpGet] 屬性代表回應 HTTP GET 要求的方法。 每個方法的 URL 路徑的建構方式如下:

  • 一開始在控制器的 Route 屬性中使用範本字串:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    
  • 以控制器的名稱取代 [controller],也就是將控制器類別名稱減去 "Controller" 字尾。 在此範例中,控制器類別名稱是 TodoItemsController,因此控制器名稱是 "TodoItems"。 ASP.NET Core 路由不區分大小寫。

  • 如果 [HttpGet] 屬性具有路由範本 (例如 [HttpGet("products")]),請將其附加到路徑。 此範例不使用範本。 如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

在下列 GetTodoItem 方法中,"{id}" 是待辦事項唯一識別碼的預留位置變數。 在叫用 GetTodoItem 時,會將 URL 中的 "{id}" 值提供給方法的 id 參數。

[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

傳回值

GetTodoItemsGetTodoItem 方法的傳回型別為 ActionResult<T> 類型。 ASP.NET Core 會自動將物件序列化為 JSON,並將 JSON 寫入至回應訊息的本文。 此傳回型別的回應碼為 200 OK,假設沒有任何未處理的例外狀況。 未處理的例外狀況會轉譯成 5xx 錯誤。

ActionResult 傳回型別可代表各種 HTTP 狀態碼。 例如,GetTodoItem 可傳回兩個不同的狀態值:

  • 如果沒有項目符合所要求的識別碼,方法會傳回 404 狀態NotFound錯誤碼。
  • 否則,方法會傳回 200 與 JSON 回應本文。 傳回 item 會導致 HTTP 200 回應。

PutTodoItem 方法

檢查 PutTodoItem 方法:

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem 類似於 PostTodoItem,但是會使用 HTTP PUT。 回應是 204 (No Content) (204 (沒有內容))。 根據 HTTP 規格,PUT 要求需要用戶端傳送整個更新的實體,而不只是變更。 若要支援部分更新,請使用 HTTP PATCH

如果您在下一節中呼叫 PutTodoItem 時發生錯誤,請呼叫 GET 以確保資料庫中有項目。

測試 PutTodoItem 方法

此範例使用在每次應用程式啟動都必須初始化的記憶體內部資料庫。 資料庫中必須有項目,您才能進行 PUT 呼叫。 在發出 PUT 呼叫之前,呼叫 GET 以確保資料庫中有項目。

更新識別碼為 1 的待辦事項,並將其名稱設定為 "feed fish"

connect https://localhost:5001/api/todoitems/1
put -h Content-Type=application/json -c "{"id":1,"name":"feed fish","isComplete":true}"

以下是命令輸出範例:

HTTP/1.1 204 No Content
Date: Tue, 07 Sep 2021 21:20:47 GMT
Server: Kestrel

DeleteTodoItem 方法

檢查 DeleteTodoItem 方法:

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

測試 DeleteTodoItem 方法

刪除識別碼為 1 的待辦項目:

connect https://localhost:5001/api/todoitems/1
delete

以下是命令輸出範例:

HTTP/1.1 204 No Content
Date: Tue, 07 Sep 2021 21:43:00 GMT
Server: Kestrel

防止過度張貼

目前範例應用程式會公開整個 TodoItem 物件。 生產應用程式通常會限制輸入的資料,並使用模型的子集傳回。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本教學課程中使用 DTO

DTO 可用來:

  • 防止過度張貼。
  • 隱藏用戶端不應該檢視的屬性。
  • 省略一些屬性,以減少承載大小。
  • 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。

若要示範 DTO 方法,請更新 TodoItem 類別以包含祕密欄位:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
        public string? Secret { get; set; }
    }
}

祕密欄位必須從此應用程式隱藏,但系統管理應用程式可以選擇公開它。

確認您可以張貼並取得祕密欄位。

建立 DTO 模型:

namespace TodoApi.Models
{
    public class TodoItemDTO
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

更新 TodoItemsController 以使用 TodoItemDTO

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;

        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }

        // GET: api/TodoItems
        [HttpGet]
        public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
        {
            return await _context.TodoItems
                .Select(x => ItemToDTO(x))
                .ToListAsync();
        }

        // GET: api/TodoItems/5
        [HttpGet("{id}")]
        public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

            if (todoItem == null)
            {
                return NotFound();
            }

            return ItemToDTO(todoItem);
        }
        // PUT: api/TodoItems/5
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
        {
            if (id != todoItemDTO.Id)
            {
                return BadRequest();
            }

            var todoItem = await _context.TodoItems.FindAsync(id);
            if (todoItem == null)
            {
                return NotFound();
            }

            todoItem.Name = todoItemDTO.Name;
            todoItem.IsComplete = todoItemDTO.IsComplete;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
            {
                return NotFound();
            }

            return NoContent();
        }
        // POST: api/TodoItems
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPost]
        public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
        {
            var todoItem = new TodoItem
            {
                IsComplete = todoItemDTO.IsComplete,
                Name = todoItemDTO.Name
            };

            _context.TodoItems.Add(todoItem);
            await _context.SaveChangesAsync();

            return CreatedAtAction(
                nameof(GetTodoItem),
                new { id = todoItem.Id },
                ItemToDTO(todoItem));
        }

        // DELETE: api/TodoItems/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

            if (todoItem == null)
            {
                return NotFound();
            }

            _context.TodoItems.Remove(todoItem);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        private bool TodoItemExists(long id)
        {
            return _context.TodoItems.Any(e => e.Id == id);
        }

        private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
            new TodoItemDTO
            {
                Id = todoItem.Id,
                Name = todoItem.Name,
                IsComplete = todoItem.IsComplete
            };
    }
}

確認您無法張貼或取得祕密欄位。

使用 JavaScript 呼叫 Web API

請參閱教學課程:使用 JavaScript 呼叫 ASP.NET Core Web API

Web API 影片系列

請參閱影片:初學者系列:Web API

將驗證支援新增至 Web API

ASP.NET Core Identity 會將使用者介面 (UI) 登入功能新增至 ASP.NET Core Web 應用程式。 若要保護 Web API 和 SPA,請使用下列其中一項:

Duende Identity 伺服器是適用於 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 架構。 Duende Identity 伺服器會啟用下列安全性功能:

  • 驗證即服務 (AaaS)
  • 多個應用程式類型的單一登入/登出 (SSO)
  • API 的存取控制
  • Federation Gateway

重要

Duende Software 可能會要求您支付授權費用才能在生產環境中使用 Duende Identity 伺服器。 如需詳細資訊,請參閱從 ASP.NET Core 5.0 移轉至 6.0

如需詳細資訊,請參閱 Duende Identity 伺服器文件 (Duende Software 網站)。

發佈至 Azure

如需部署至 Azure 的資訊,請參閱 快速入門:部署 ASP.NET Web 應用程式

其他資源

檢視或下載本教學課程的範例程式碼。 請參閱如何下載

如需詳細資訊,請參閱以下資源:

本教學課程將教導建置使用資料庫之控制器型 Web API 的基本概念。 在 ASP.NET Core 中建立 API 的另一種方法是建立最小的 API。 如需在最小 API 與控制器型 API 之間選擇的說明,請參閱 API 概觀。 如需建立最小 API 的教學課程,請參閱教學課程:使用 ASP.NET Core 建立最小 API

在本教學課程中,您會了解如何:

  • 建立 Web API 專案。
  • 新增模型類別和資料庫內容。
  • 使用 CRUD 方法 Scaffold 控制器。
  • 設定路由、URL 路徑和傳回值。
  • 使用 Postman 呼叫 Web API。

結束時,您會有一個 Web API,可以管理儲存在資料庫中的「待辦事項」。

概觀

本教學課程會建立以下 API:

API 描述 要求本文 回應本文
GET /api/todoitems 取得所有待辦事項 待辦事項的陣列
GET /api/todoitems/{id} 依識別碼取得項目 待辦事項
POST /api/todoitems 新增記錄 待辦事項 待辦事項
PUT /api/todoitems/{id} 更新現有的項目 待辦事項
DELETE /api/todoitems/{id}     刪除項目

下圖顯示應用程式的設計。

左側方塊代表用戶端。它會送出要求並接收來自應用程式 (右側繪製的方塊) 的回應。在應用程式方塊中,三個方塊代表控制器、模型以及資料存取層。要求進入應用程式的控制器,而在控制器與資料存取層之間進行讀取/寫入作業。模型會序列化並在回應中傳回至用戶端。

必要條件

建立 Web 專案

  • 從 [檔案] 功能表選取 [新增] >[專案] 。
  • 選取 ASP.NET Core Web API 範本,然後按一下 [下一步]
  • 將專案命名為 TodoApi,然後按一下 [建立]
  • 在 [建立新的 ASP.NET Core Web 應用程式] 對話方塊中,確認選取 [.NET Core] 和 [ASP.NET Core 5.0]。 選取 API 範本,然後按一下 [建立]

VS 新增專案對話方塊

注意

如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件)安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。

測試專案

專案範本會建立支援 SwaggerWeatherForecast API。

按 Ctrl+F5 即可執行而不使用偵錯工具。

當專案尚未設定為使用 SSL 時,Visual Studio 會顯示下列對話方塊:

此專案設定為使用 SSL。為避免瀏覽器中的 SSL 警告,您可以選擇信任 IIS Express 產生的自我簽署憑證。您要信任 IIS Express SSL 憑證嗎?

如果您信任 IIS Express SSL 憑證,請選取 [是]

此時會顯示下列對話方塊:

安全性警告對話方塊

若您同意信任開發憑證,請選取 [是]

如需關於信任 Firefox 瀏覽器的資訊,請參閱 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 憑證錯誤

Visual Studio 會啟動:

  • IIS Express 網頁伺服器。
  • 預設瀏覽器並瀏覽至 https://localhost:<port>/swagger/index.html,其中 <port> 是隨機選擇的連接埠號碼。

Swagger 頁面 /swagger/index.html 隨即顯示。 選取 [GET]>[試用]>[執行]。 頁面會顯示:

  • 用來測試 WeatherForecast API 的 Curl 命令。
  • 用來測試 WeatherForecast API 的 URL。
  • 回應碼、本文和標頭。
  • 具有媒體類型與範例值和結構描述的下拉式清單方塊。

如果 Swagger 頁面未出現,請參閱這個 GitHub 問題

Swagger 可用來為 Web API 產生有用的文件和說明頁面。 本教學課程著重於建立 Web API。 如需 Swagger 的詳細資訊,請參閱使用 ASP.NET Core Web API 文件搭配 Swagger / OpenAPI

複製並貼上瀏覽器中的要求 URLhttps://localhost:<port>/weatherforecast

隨即會傳回與下列類似的 JSON:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

更新 launchUrl

Properties\launchSettings.json中,將 launchUrl"swagger" 更新為 "api/todoitems"

"launchUrl": "api/todoitems",

由於系統會移除 Swagger,因此上述標記會將啟動的 URL 變更為下列各節中所新增控制器的 GET 方法。

新增模型類別

「模型」是代表應用程式所管理資料的一組類別。 此應用程式的模型是單一 TodoItem 類別。

  • 在 [方案總管] 中,以滑鼠右鍵按一下專案。 選取 [新增]>[新增資料夾]。 將資料夾命名為 Models註冊免費試用帳戶。

  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoItem,然後選取 [新增]

  • 以下列項目取代範本程式碼:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Id 屬性的功能相當於關聯式資料庫中的唯一索引鍵。

模型類別可位於專案中的任何位置,但依照慣例會使用 Models 資料夾。

新增資料庫內容

「資料庫內容」是為資料模型協調 Entity Framework 功能的主要類別。 此類別是透過衍生自 Microsoft.EntityFrameworkCore.DbContext 類別來建立。

新增 NuGet 套件

  • 在 [工具] 功能表上,選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]
  • 選取 [瀏覽] 索引標籤,然後在搜尋方塊中輸入 Microsoft.EntityFrameworkCore.InMemory
  • 在左窗格中選取 Microsoft.EntityFrameworkCore.InMemory
  • 選取右窗格中的 [專案] 核取方塊,然後選取 [安裝]

NuGet 套件管理員

新增 TodoCoNtext 資料庫內容

  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoContext,然後按一下 [新增]
  • 輸入下列程式碼:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

登錄資料庫內容

在 ASP.NET Core 中,資料庫內容等服務必須向相依性插入 (DI) 容器註冊。 此容器會將服務提供給控制器。

使用下列程式碼更新 Startup.cs

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddDbContext<TodoContext>(opt =>
                                               opt.UseInMemoryDatabase("TodoList"));
            //services.AddSwaggerGen(c =>
            //{
            //    c.SwaggerDoc("v1", new OpenApiInfo { Title = "TodoApi", Version = "v1" });
            //});
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                //app.UseSwagger();
                //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoApi v1"));
            }

            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

上述 程式碼:

  • 移除 Swagger 呼叫。
  • 移除未使用的 using 宣告。
  • 將資料庫內容新增至 DI 容器。
  • 指定資料庫內容將會使用記憶體內部資料庫。

Scaffold 控制器

  • 以滑鼠右鍵按一下 Controllers 資料夾。

  • 選取 [新增]> [新增 Scaffold 項目]

  • 選取 [使用 Entity Framework 執行動作的 API 控制器],然後選取 [新增]

  • 在 [使用 Entity Framework 執行動作的 API 控制器] 對話方塊中:

    • 在 [模型類別] 中選取 [TodoItem (TodoApi.Models)]
    • 在 [資料內容類別] 中選取 [TodoContext (TodoApi.Models)]
    • 選取新增

產生的程式碼:

  • 使用 [ApiController] 屬性標記類別。 這個屬性表示控制器會回應 Web API 要求。 如需屬性啟用的特定行為相關資訊,請參閱 使用 ASP.NET Core 建立 Web API
  • 使用 DI 將資料庫內容 (TodoContext) 插入到控制器中。 控制器中的每一個 CRUD 方法都會使用資料庫內容。

ASP.NET Core 範本適用於:

  • 路由範本中檢視包含 [action] 的控制器。
  • API 控制器在路由範本中不包含 [action]

[action] 權杖不在路由範本中時,動作名稱會從路由中排除。 也就是說,動作的相關聯方法名稱不會用於比對路由。

更新 PostTodoItem 建立方法

更新 PostTodoItem 中的 return 陳述式,以使用 nameof 運算子:

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("PostTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(PostTodoItem), new { id = todoItem.Id }, todoItem);
}

上述程式碼是 HTTP POST 方法,以 [HttpPost] 屬性表示。 該方法會從 HTTP 要求本文取得待辦事項的值。

如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

CreatedAtAction 方法:

  • 成功時會傳回 HTTP 201 狀態碼。 對於可在伺服器上建立新資源的 HTTP POST 方法,其標準回應是 HTTP 201。
  • Location標頭新增到回應。 Location 標頭會指定新建待辦事項的 URI。 如需詳細資訊,請參閱 201 已建立
  • 參考 GetTodoItem 動作以建立 Location 標頭的 URI。 C# nameof 關鍵字是用來避免在 CreatedAtAction 呼叫中以硬式編碼方式寫入動作名稱。

安裝 Postman

本教學課程使用 Postman 來測試 Web API。

  • 安裝 Postman
  • 啟動 Web 應用程式。
  • 啟動 Postman。
  • 停用 [SSL 憑證驗證]
    • 適用於 Windows 的 Postman:選取 [檔案]>[設定] ([一般] 索引標籤),停用 [SSL 憑證驗證]
    • 適用於 macOS 的 Postman:選取 [Postman]>[設定] ([一般] 索引標籤),停用 [SSL 憑證驗證]

      警告

      在測試控制器之後,請重新啟用 [SSL certificate verification] \(SSL 憑證驗證\)。

使用 Postman 測試 PostTodoItem

  • 建立新的要求。

  • 將 HTTP 方法設為 POST

  • 將 URI 設定為 https://localhost:<port>/api/todoitems。 例如: https://localhost:5001/api/todoitems

  • 選取 [本文] 索引標籤。

  • 選取 [原始] 選項按鈕。

  • 將類型設定為 JSON (application/json)

  • 在要求本文中,輸入待辦事項的 JSON:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • 請選取傳送

    Postman 與建立要求

測試位置標頭 URI

您可以在瀏覽器中測試位置標頭 URI。 將位置標頭 URI 複製並貼到瀏覽器中。

若要在 Postman 中進行測試:

  • 在 [回應] 窗格中選取 [標頭] 索引標籤。

  • 複製 [位置] 標頭值:

    Postman 主控台的 [標頭] 索引標籤

  • 將 HTTP 方法設為 GET

  • 將 URI 設定為 https://localhost:<port>/api/todoitems/1。 例如: https://localhost:5001/api/todoitems/1

  • 請選取傳送

檢查 GET 方法

兩個 GET 端點即會實作:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

從瀏覽器或 Postman 呼叫這兩個端點來測試應用程式。 例如:

  • https://localhost:5001/api/todoitems
  • https://localhost:5001/api/todoitems/1

GetTodoItems 的呼叫會產生類似下列的回應:

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

使用 Postman 測試 Get

  • 建立新的要求。
  • 將 HTTP 方法設定為 GET
  • 將要求 URI 設定為 https://localhost:<port>/api/todoitems。 例如: https://localhost:5001/api/todoitems
  • 在 Postman 中,設定 [Two pane view] \(雙窗格檢視\)
  • 請選取傳送

這個應用程式會使用記憶體內部資料庫。 如果應用程式在停止後再啟動,上述 GET 要求將不會傳回任何資料。 如果沒有傳回任何資料,請將資料 POST 到應用程式。

傳送和 URL 路徑

[HttpGet] 屬性代表回應 HTTP GET 要求的方法。 每個方法的 URL 路徑的建構方式如下:

  • 一開始在控制器的 Route 屬性中使用範本字串:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • 以控制器的名稱取代 [controller],也就是將控制器類別名稱減去 "Controller" 字尾。 在此範例中,控制器類別名稱是 TodoItemsController,因此控制器名稱是 "TodoItems"。 ASP.NET Core 路由不區分大小寫。

  • 如果 [HttpGet] 屬性具有路由範本 (例如 [HttpGet("products")]),請將其附加到路徑。 此範例不使用範本。 如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

在下列 GetTodoItem 方法中,"{id}" 是待辦事項唯一識別碼的預留位置變數。 在叫用 GetTodoItem 時,會將 URL 中的 "{id}" 值提供給方法的 id 參數。

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

傳回值

GetTodoItemsGetTodoItem 方法的傳回型別為 ActionResult<T> 類型。 ASP.NET Core 會自動將物件序列化為 JSON,並將 JSON 寫入至回應訊息的本文。 此傳回型別的回應碼為 200 OK,假設沒有任何未處理的例外狀況。 未處理的例外狀況會轉譯成 5xx 錯誤。

ActionResult 傳回型別可代表各種 HTTP 狀態碼。 例如,GetTodoItem 可傳回兩個不同的狀態值:

  • 如果沒有項目符合所要求的識別碼,方法會傳回 404 狀態NotFound錯誤碼。
  • 否則,方法會傳回 200 與 JSON 回應本文。 傳回 item 會導致 HTTP 200 回應。

PutTodoItem 方法

檢查 PutTodoItem 方法:

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem 類似於 PostTodoItem,但是會使用 HTTP PUT。 回應是 204 (No Content) (204 (沒有內容))。 根據 HTTP 規格,PUT 要求需要用戶端傳送整個更新的實體,而不只是變更。 若要支援部分更新,請使用 HTTP PATCH

如果在呼叫 PutTodoItem 時發生錯誤,請呼叫 GET 以確保資料庫中有項目。

測試 PutTodoItem 方法

此範例使用在每次應用程式啟動都必須初始化的記憶體內部資料庫。 資料庫中必須有項目,您才能進行 PUT 呼叫。 在發出 PUT 呼叫之前,呼叫 GET 以確保資料庫中有項目。

更新識別碼為 1 的待辦事項,並將其名稱設定為 "feed fish"

  {
    "Id":1,
    "name":"feed fish",
    "isComplete":true
  }

下圖顯示 Postman 更新:

顯示「204 (沒有內容) 回應」的 Postman 主控台

DeleteTodoItem 方法

檢查 DeleteTodoItem 方法:

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

測試 DeleteTodoItem 方法

使用 Postman 刪除待辦事項:

  • 將方法設定為 DELETE
  • 設定要刪除的物件 URI (例如 https://localhost:5001/api/todoitems/1)。
  • 請選取傳送

防止過度張貼

目前範例應用程式會公開整個 TodoItem 物件。 生產應用程式通常會限制輸入的資料,並使用模型的子集傳回。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本文中會使用 DTO

DTO 可用來:

  • 防止過度張貼。
  • 隱藏用戶端不應該檢視的屬性。
  • 省略一些屬性,以減少承載大小。
  • 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。

若要示範 DTO 方法,請更新 TodoItem 類別以包含祕密欄位:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
        public string Secret { get; set; }
    }
}

祕密欄位必須從此應用程式隱藏,但系統管理應用程式可以選擇公開它。

確認您可以張貼並取得祕密欄位。

建立 DTO 模型:

public class TodoItemDTO
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

更新 TodoItemsController 以使用 TodoItemDTO

// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
    return await _context.TodoItems
        .Select(x => ItemToDTO(x))
        .ToListAsync();
}

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return ItemToDTO(todoItem);
}

[HttpPut("{id}")]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
{
    if (id != todoItemDTO.Id)
    {
        return BadRequest();
    }

    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    todoItem.Name = todoItemDTO.Name;
    todoItem.IsComplete = todoItemDTO.IsComplete;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
    {
        return NotFound();
    }

    return NoContent();
}

[HttpPost]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
{
    var todoItem = new TodoItem
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    return CreatedAtAction(
        nameof(GetTodoItem),
        new { id = todoItem.Id },
        ItemToDTO(todoItem));
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

private bool TodoItemExists(long id) =>
     _context.TodoItems.Any(e => e.Id == id);

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
    new TodoItemDTO
    {
        Id = todoItem.Id,
        Name = todoItem.Name,
        IsComplete = todoItem.IsComplete
    };

確認您無法張貼或取得祕密欄位。

使用 JavaScript 呼叫 Web API

請參閱教學課程:使用 JavaScript 呼叫 ASP.NET Core Web API

將驗證支援新增至 Web API

ASP.NET Core Identity 會將使用者介面 (UI) 登入功能新增至 ASP.NET Core Web 應用程式。 若要保護 Web API 和 SPA,請使用下列其中一項:

Duende Identity 伺服器是適用於 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 架構。 Duende Identity 伺服器會啟用下列安全性功能:

  • 驗證即服務 (AaaS)
  • 多個應用程式類型的單一登入/登出 (SSO)
  • API 的存取控制
  • Federation Gateway

重要

Duende Software 可能會要求您支付授權費用才能在生產環境中使用 Duende Identity 伺服器。 如需詳細資訊,請參閱從 ASP.NET Core 5.0 移轉至 6.0

如需詳細資訊,請參閱 Duende Identity 伺服器文件 (Duende Software 網站)。

發佈至 Azure

如需部署至 Azure 的資訊,請參閱 快速入門:部署 ASP.NET Web 應用程式

其他資源

檢視或下載本教學課程的範例程式碼。 請參閱如何下載

如需詳細資訊,請參閱以下資源:

本教學課程將教導建置使用資料庫之控制器型 Web API 的基本概念。 在 ASP.NET Core 中建立 API 的另一種方法是建立最小的 API。 如需在最小 API 與控制器型 API 之間選擇的說明,請參閱 API 概觀。 如需建立最小 API 的教學課程,請參閱教學課程:使用 ASP.NET Core 建立最小 API

在本教學課程中,您會了解如何:

  • 建立 Web API 專案。
  • 新增模型類別和資料庫內容。
  • 使用 CRUD 方法 Scaffold 控制器。
  • 設定路由、URL 路徑和傳回值。
  • 使用 Postman 呼叫 Web API。

結束時,您會有一個 Web API,可以管理儲存在資料庫中的「待辦事項」。

概觀

本教學課程會建立以下 API:

API 描述 要求本文 回應本文
GET /api/todoitems 取得所有待辦事項 待辦事項的陣列
GET /api/todoitems/{id} 依識別碼取得項目 待辦事項
POST /api/todoitems 新增記錄 待辦事項 待辦事項
PUT /api/todoitems/{id} 更新現有的項目 待辦事項
DELETE /api/todoitems/{id}     刪除項目

下圖顯示應用程式的設計。

左側方塊代表用戶端。它會送出要求並接收來自應用程式 (右側繪製的方塊) 的回應。在應用程式方塊中,三個方塊代表控制器、模型以及資料存取層。要求進入應用程式的控制器,而在控制器與資料存取層之間進行讀取/寫入作業。模型會序列化並在回應中傳回至用戶端。

必要條件

建立 Web 專案

  • 從 [檔案] 功能表選取 [新增] >[專案] 。
  • 選取 ASP.NET Core Web 應用程式範本,然後按一下 [下一步]
  • 將專案命名為 TodoApi,然後按一下 [建立]
  • 在 [建立新的 ASP.NET Core Web 應用程式] 對話方塊中,確認選取 [.NET Core] 和 [ASP.NET Core 3.1]。 選取 API 範本,然後按一下 [建立]

VS 新增專案對話方塊

注意

如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件)安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。

測試 API

專案範本會建立 WeatherForecast API。 從瀏覽器呼叫 Get 方法來測試應用程式。

按 Ctrl+F5 執行應用程式。 Visual Studio 會啟動瀏覽器並巡覽至 https://localhost:<port>/weatherforecast,其中 <port> 是隨機選擇的通訊埠編號。

如果出現對話方塊詢問您是否應該信任 IIS Express 憑證,請選取 [是]。 在接著出現的 [安全性警告] 對話方塊中,選取 [是]

隨即會傳回與下列類似的 JSON:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

新增模型類別

「模型」是代表應用程式所管理資料的一組類別。 此應用程式的模型是單一 TodoItem 類別。

  • 在 [方案總管] 中,以滑鼠右鍵按一下專案。 選取 [新增]>[新增資料夾]。 將資料夾命名為 Models註冊免費試用帳戶。

  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoItem,然後選取 [新增]

  • 使用下列程式碼取代範本程式碼:

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

Id 屬性的功能相當於關聯式資料庫中的唯一索引鍵。

模型類別可位於專案中的任何位置,但依照慣例會使用 Models 資料夾。

新增資料庫內容

「資料庫內容」是為資料模型協調 Entity Framework 功能的主要類別。 此類別是透過衍生自 Microsoft.EntityFrameworkCore.DbContext 類別來建立。

新增 NuGet 套件

  • 在 [工具] 功能表上,選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]
  • 選取 [瀏覽] 索引標籤,然後在搜尋方塊中輸入 Microsoft.EntityFrameworkCore.InMemory
  • 在左窗格中選取 [Microsoft.EntityFrameworkCore.InMemory]
  • 選取右窗格中的 [專案] 核取方塊,然後選取 [安裝]

NuGet 套件管理員

新增 TodoCoNtext 資料庫內容

  • 以滑鼠右鍵按一下 Models 資料夾並選取 [新增]>[類別]。 將類別命名為 TodoContext,然後按一下 [新增]
  • 輸入下列程式碼:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

登錄資料庫內容

在 ASP.NET Core 中,資料庫內容等服務必須向相依性插入 (DI) 容器註冊。 此容器會將服務提供給控制器。

使用下列醒目提示的程式碼更新 Startup.cs

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
               opt.UseInMemoryDatabase("TodoList"));
            services.AddControllers();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

上述 程式碼:

  • 移除未使用的 using 宣告。
  • 將資料庫內容新增至 DI 容器。
  • 指定資料庫內容將會使用記憶體內部資料庫。

Scaffold 控制器

  • 以滑鼠右鍵按一下 Controllers 資料夾。

  • 選取 [新增]> [新增 Scaffold 項目]

  • 選取 [使用 Entity Framework 執行動作的 API 控制器],然後選取 [新增]

  • 在 [使用 Entity Framework 執行動作的 API 控制器] 對話方塊中:

    • 在 [模型類別] 中選取 [TodoItem (TodoApi.Models)]
    • 在 [資料內容類別] 中選取 [TodoContext (TodoApi.Models)]
    • 選取新增

產生的程式碼:

  • 使用 [ApiController] 屬性標記類別。 這個屬性表示控制器會回應 Web API 要求。 如需屬性啟用的特定行為相關資訊,請參閱 使用 ASP.NET Core 建立 Web API
  • 使用 DI 將資料庫內容 (TodoContext) 插入到控制器中。 控制器中的每一個 CRUD 方法都會使用資料庫內容。

ASP.NET Core 範本適用於:

  • 路由範本中檢視包含 [action] 的控制器。
  • API 控制器在路由範本中不包含 [action]

[action] 權杖不在路由範本中時,動作名稱會從路由中排除。 也就是說,動作的相關聯方法名稱不會用於比對路由。

檢查 PostTodoItem 建立方法

取代 PostTodoItem 中的 return 陳述式,以使用 nameof 運算子:

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("PostTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(PostTodoItem), new { id = todoItem.Id }, todoItem);
}

上述程式碼是 HTTP POST 方法,以 [HttpPost] 屬性表示。 該方法會從 HTTP 要求本文取得待辦事項的值。

如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

CreatedAtAction 方法:

  • 成功時會傳回 HTTP 201 狀態碼。 對於可在伺服器上建立新資源的 HTTP POST 方法,其標準回應是 HTTP 201。
  • Location標頭新增到回應。 Location 標頭會指定新建待辦事項的 URI。 如需詳細資訊,請參閱 201 已建立
  • 參考 GetTodoItem 動作以建立 Location 標頭的 URI。 C# nameof 關鍵字是用來避免在 CreatedAtAction 呼叫中以硬式編碼方式寫入動作名稱。

安裝 Postman

本教學課程使用 Postman 來測試 Web API。

  • 安裝 Postman
  • 啟動 Web 應用程式。
  • 啟動 Postman。
  • 停用 [SSL 憑證驗證]
    • 適用於 Windows 的 Postman:適用於 Windows 的 Postman [檔案]>[設定] ([一般] 索引標籤),停用 [SSL 憑證驗證]
    • 適用於 macOS 的 Postman:適用於 Windows 的 Postman [Postman]>[設定] ([一般] 索引標籤),停用 [SSL 憑證驗證]

      警告

      在測試控制器之後,請重新啟用 [SSL certificate verification] \(SSL 憑證驗證\)。

使用 Postman 測試 PostTodoItem

  • 建立新的要求。

  • 將 HTTP 方法設為 POST

  • 將 URI 設定為 https://localhost:<port>/api/todoitems。 例如: https://localhost:5001/api/todoitems

  • 選取 [本文] 索引標籤。

  • 選取 [原始] 選項按鈕。

  • 將類型設定為 JSON (application/json)

  • 在要求本文中,輸入待辦事項的 JSON:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • 請選取傳送

    Postman 與建立要求

使用 Postman 測試位置標頭 URI

  • 在 [回應] 窗格中選取 [標頭] 索引標籤。

  • 複製 [位置] 標頭值:

    Postman 主控台的 [標頭] 索引標籤

  • 將 HTTP 方法設為 GET

  • 將 URI 設定為 https://localhost:<port>/api/todoitems/1。 例如: https://localhost:5001/api/todoitems/1

  • 請選取傳送

檢查 GET 方法

這些方法會實作兩個 GET 端點:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

從瀏覽器或 Postman 呼叫這兩個端點來測試應用程式。 例如:

  • https://localhost:5001/api/todoitems
  • https://localhost:5001/api/todoitems/1

GetTodoItems 的呼叫會產生類似下列的回應:

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

使用 Postman 測試 Get

  • 建立新的要求。
  • 將 HTTP 方法設定為 GET
  • 將要求 URI 設定為 https://localhost:<port>/api/todoitems。 例如: https://localhost:5001/api/todoitems
  • 在 Postman 中,設定 [Two pane view] \(雙窗格檢視\)
  • 請選取傳送

這個應用程式會使用記憶體內部資料庫。 如果應用程式在停止後再啟動,上述 GET 要求將不會傳回任何資料。 如果沒有傳回任何資料,請將資料 POST 到應用程式。

傳送和 URL 路徑

[HttpGet] 屬性代表回應 HTTP GET 要求的方法。 每個方法的 URL 路徑的建構方式如下:

  • 一開始在控制器的 Route 屬性中使用範本字串:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • 以控制器的名稱取代 [controller],也就是將控制器類別名稱減去 "Controller" 字尾。 在此範例中,控制器類別名稱是 TodoItemsController,因此控制器名稱是 "TodoItems"。 ASP.NET Core 路由不區分大小寫。

  • 如果 [HttpGet] 屬性具有路由範本 (例如 [HttpGet("products")]),請將其附加到路徑。 此範例不使用範本。 如需詳細資訊,請參閱使用 Http[Verb] 屬性的屬性路由

在下列 GetTodoItem 方法中,"{id}" 是待辦事項唯一識別碼的預留位置變數。 在叫用 GetTodoItem 時,會將 URL 中的 "{id}" 值提供給方法的 id 參數。

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

傳回值

GetTodoItemsGetTodoItem 方法的傳回型別為 ActionResult<T> 類型。 ASP.NET Core 會自動將物件序列化為 JSON,並將 JSON 寫入至回應訊息的本文。 此傳回型別的回應碼為 200,假設沒有任何未處理的例外狀況。 未處理的例外狀況會轉譯成 5xx 錯誤。

ActionResult 傳回型別可代表各種 HTTP 狀態碼。 例如,GetTodoItem 可傳回兩個不同的狀態值:

  • 如果沒有項目符合所要求的識別碼,方法會傳回 404 NotFound 錯誤碼。
  • 否則,方法會傳回 200 與 JSON 回應本文。 傳回 item 會導致 HTTP 200 回應。

PutTodoItem 方法

檢查 PutTodoItem 方法:

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem 類似於 PostTodoItem,但是會使用 HTTP PUT。 回應是 204 (No Content) (204 (沒有內容))。 根據 HTTP 規格,PUT 要求需要用戶端傳送整個更新的實體,而不只是變更。 若要支援部分更新,請使用 HTTP PATCH

如果在呼叫 PutTodoItem 時發生錯誤,請呼叫 GET 以確保資料庫中有項目。

測試 PutTodoItem 方法

此範例使用在每次應用程式啟動都必須初始化的記憶體內部資料庫。 資料庫中必須有項目,您才能進行 PUT 呼叫。 在發出 PUT 呼叫之前,呼叫 GET 以確保資料庫中有項目。

更新識別碼為 1 的待辦事項,並將其名稱設定為 "feed fish":

  {
    "id":1,
    "name":"feed fish",
    "isComplete":true
  }

下圖顯示 Postman 更新:

顯示「204 (沒有內容) 回應」的 Postman 主控台

DeleteTodoItem 方法

檢查 DeleteTodoItem 方法:

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return todoItem;
}

測試 DeleteTodoItem 方法

使用 Postman 刪除待辦事項:

  • 將方法設定為 DELETE
  • 設定要刪除的物件 URI (例如 https://localhost:5001/api/todoitems/1)。
  • 請選取傳送

防止過度張貼

目前範例應用程式會公開整個 TodoItem 物件。 生產應用程式通常會限制輸入的資料,並使用模型的子集傳回。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本文中會使用 DTO

DTO 可用來:

  • 防止過度張貼。
  • 隱藏用戶端不應該檢視的屬性。
  • 省略一些屬性,以減少承載大小。
  • 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。

若要示範 DTO 方法,請更新 TodoItem 類別以包含祕密欄位:

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
    public string Secret { get; set; }
}

祕密欄位必須從此應用程式隱藏,但系統管理應用程式可以選擇公開它。

確認您可以張貼並取得祕密欄位。

建立 DTO 模型:

public class TodoItemDTO
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

更新 TodoItemsController 以使用 TodoItemDTO

    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        return ItemToDTO(todoItem);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
    {
        if (id != todoItemDTO.Id)
        {
            return BadRequest();
        }

        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        todoItem.Name = todoItemDTO.Name;
        todoItem.IsComplete = todoItemDTO.IsComplete;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }

        return NoContent();
    }

    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoItemDTO.IsComplete,
            Name = todoItemDTO.Name
        };

        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        _context.TodoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool TodoItemExists(long id) =>
         _context.TodoItems.Any(e => e.Id == id);

    private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
        new TodoItemDTO
        {
            Id = todoItem.Id,
            Name = todoItem.Name,
            IsComplete = todoItem.IsComplete
        };       
}

確認您無法張貼或取得祕密欄位。

使用 JavaScript 呼叫 Web API

請參閱教學課程:使用 JavaScript 呼叫 ASP.NET Core Web API

將驗證支援新增至 Web API

ASP.NET Core Identity 會將使用者介面 (UI) 登入功能新增至 ASP.NET Core Web 應用程式。 若要保護 Web API 和 SPA,請使用下列其中一項:

Duende Identity 伺服器是適用於 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 架構。 Duende Identity 伺服器會啟用下列安全性功能:

  • 驗證即服務 (AaaS)
  • 多個應用程式類型的單一登入/登出 (SSO)
  • API 的存取控制
  • Federation Gateway

重要

Duende Software 可能會要求您支付授權費用才能在生產環境中使用 Duende Identity 伺服器。 如需詳細資訊,請參閱從 ASP.NET Core 5.0 移轉至 6.0

如需詳細資訊,請參閱 Duende Identity 伺服器文件 (Duende Software 網站)。

發佈至 Azure

如需部署至 Azure 的資訊,請參閱 快速入門:部署 ASP.NET Web 應用程式

其他資源

檢視或下載本教學課程的範例程式碼。 請參閱如何下載

如需詳細資訊,請參閱以下資源: