共用方式為


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

注意

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

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .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 資料夾。

  • 選取 [新增]。

  • 選取 [使用 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("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), 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)。
  • 參考 GetTodoItem 動作以建立 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.NET YouTube 影片文章,以取得從頭建立新式、可靠、效能強、可測試、具成本效益及可調整的 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 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 資料夾。

  • 選取 [新增]。

  • 選取 [使用 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("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), 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)。
  • 參考 GetTodoItem 動作以建立 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.NET YouTube 影片文章,以取得從頭建立新式、可靠、效能強、可測試、具成本效益及可調整的 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 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 資料夾。

  • 選取 [新增]。

  • 選取 [使用 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("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), 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)。
  • 參考 GetTodoItem 動作以建立 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.NET YouTube 影片文章,以取得從頭建立新式、可靠、效能強、可測試、具成本效益及可調整的 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 應用程式

其他資源

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

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