原文:Building Your First Web API with ASP.NET Core MVC and Visual Studio
作者:Mike Wasson 和 Rick Anderson
翻譯:謝煬(kiler)
校對:何鎮汐、劉怡(AlexLEWIS)、后知后覺
HTTP 協議不僅僅提供網頁服務。它也是一個構建公開服務和數據 API 的強大平台。HTTP 協議是簡單、靈活、無處不在的。幾乎你能想到的任何平台上都有 HTTP 支持,所以 HTTP 服務能夠發送到多種客戶端, 包括瀏覽器,移動設備和傳統的桌面應用程序。
在本教程中,你將創建一個簡單的 Web API 來管理一個 "to-do" 列表。在本教程中你無需編寫任何 UI 代碼。
ASP.NET Core 已經內置了用 MVC 架構 構建 Web API 的支持。統一了兩個框架使得它易於構建應用程序,包括用戶界面(HTML)和 API,因為現在它們共享相同的代碼庫和管道。
注意
如果你想把一個老的 Web API 應用程序遷移到 ASP.NET Core, 參考從 ASP.NET Web API 遷移
總覽
這是你需要創建的 API :
API | 描述 | 請求正文 | 響應正文 |
---|---|---|---|
GET /api/todo | 獲取所有的to-do items | 無 | Array of to-do items |
GET /api/todo/{id} | 通過ID獲取item | 無 | To-do item |
POST /api/todo | 添加一個新的item | To-do item | To-do item |
PUT /api/todo/{id} | 更新已經存在的item | To-do item | 無 |
DELETE /api/todo/{id} | 刪除指定的item | 無 | 無 |
下面的圖表展示了應用程序的基本設計:
- 不管是哪個調用 API 的客戶端(瀏覽器、移動應用等)。我們不會在本教程編寫客戶端。
- model 是一個代表你應用程序數據的類。在本案例中,只有一個模型 to-do 項。模型表現為簡單 C# 類型 (POCOs),
- controller 是一個處理 HTTP 請求並返回 HTTP 響應的對象。這個示例程序將只會有一個 controller。
- 為了保證教程簡單我們不使用數據庫。作為替代,我們會把 to-do 項存入內存。但是我們依然包含了一個數據訪問層(不重要的),用來隔離 Web API和數據層。如果想使用數據庫,參考用 Visual Studio 創建 ASP.NET Core MVC 應用程序。
安裝 Fiddler
我們不創建客戶端,我們使用 Fiddler 來測試 API。Fiddler 是一個 Web 調試工具可以讓您撰寫的 HTTP 請求進行發送並查看原始的 HTTP 響應。
創建項目
啟動 Visual Studio。從 File 菜單, 選擇 New > Project。
選擇 ASP.NET Core Web Application 項目模版。項目命名為 TodoApi
並且點擊 OK。
在 New ASP.NET Core Web Application (.NET Core) - TodoApi 對話框中,選擇 Web API 模版。點擊 OK。
添加模型類
模型表示應用程序中的數據對象。在本示例中,唯一使用到的模型是一個 to-do 項。
添加一個名為 "Models" 的目錄。在解決方案瀏覽器中, 右擊項目。選擇 Add > New Folder。把目錄名命名為 Models。
注意
你可以把模型類放到項目的任何地方,但是 Models 是約定的默認目錄。
下一步,添加一個 TodoItem
類。右擊 Models 目錄並選擇 Add > New Item。
在 Add New Item 對話框中,選擇 Class 模版。命名類為 TodoItem
並點擊 OK。
將生成代碼替換為:
namespace TodoApi.Models
{
public class TodoItem
{
public string Key { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
添加倉儲類
repository 類是一個封裝了數據層的類,包含了獲取數據並映射到實體模型類的業務邏輯。 盡管本例中不使用數據庫,但依舊值得去思考 Repository 是如何注入到我們的 Controller 的。在 Models 目錄下創建 repository 代碼。
定義一個名為 ITodoRepository
的 repository 接口. 通過類模版 (Add New Item > Class)。
using System.Collections.Generic;
namespace TodoApi.Models
{
public interface ITodoRepository
{
void Add(TodoItem item);
IEnumerable<TodoItem> GetAll();
TodoItem Find(string key);
TodoItem Remove(string key);
void Update(TodoItem item);
}
}
接口定義了基本的 CRUD 操作。
下一步,添加一個實現 ITodoRepository 接口的 TodoRepository 類:
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
namespace TodoApi.Models
{
public class TodoRepository : ITodoRepository
{
static ConcurrentDictionary<string, TodoItem> _todos =
new ConcurrentDictionary<string, TodoItem>();
public TodoRepository()
{
Add(new TodoItem { Name = "Item1" });
}
public IEnumerable<TodoItem> GetAll()
{
return _todos.Values;
}
public void Add(TodoItem item)
{
item.Key = Guid.NewGuid().ToString();
_todos[item.Key] = item;
}
public TodoItem Find(string key)
{
TodoItem item;
_todos.TryGetValue(key, out item);
return item;
}
public TodoItem Remove(string key)
{
TodoItem item;
_todos.TryGetValue(key, out item);
_todos.TryRemove(key, out item);
return item;
}
public void Update(TodoItem item)
{
_todos[item.Key] = item;
}
}
}
生成應用程序確保沒有任何編譯錯誤。
注冊倉儲
定義 repository 接口, 我們可以從使用它的 MVC Controller 解耦倉儲類,而不是直接在 Controller 里面實例化 TodoRepository
,我們將會用 ASP.NET Core 內置功能注入 ITodoRepository
,更多請參考依賴注入。
這種方式可以更容易地對你的 Controller 進行單元測試。單元測試應該注入一個 Mock 或 stub 的 ITodoRepository。通過這樣的方式測試范圍可以限制在業務邏輯層而非數據訪問層。
為了注入 repository 到 controller,我們必須注冊DI容器。打開 Startup.cs 文件。添加以下指令:
using TodoApi.Models;
在 ConfigureServices
方法中,添加高亮方法:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Add our repository type,下行高亮
services.AddSingleton<ITodoRepository, TodoRepository>();
}
添加控制器
在解決方案瀏覽器中,右擊 Controllers 目錄。選擇 Add > New Item。在 Add New Item 對話框中,選擇 Web API Controller Class 模版。命名為 TodoController
。
將生成的代碼替換為如下代碼:
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using TodoApi.Models;
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
public TodoController(ITodoRepository todoItems)
{
TodoItems = todoItems;
}
public ITodoRepository TodoItems { get; set; }
}
}
這里定義了一個空的 controller 類。下一個章節,我們將添加代碼來實現 API。
獲取 to-do 列表
為了獲取 to-do 項,添加下列方法到 TodoController
類。
public IEnumerable<TodoItem> GetAll()
{
return TodoItems.GetAll();
}
[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)
{
var item = TodoItems.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
以下是 GetAll
方法 HTTP 響應:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/10.0
Date: Thu, 18 Jun 2015 20:51:10 GMT
Content-Length: 82
[{"Key":"4f67d7c5-a2a9-4aae-b030-16003dd829ae","Name":"Item1","IsComplete":false}]
在后面的教程中,我將會告訴你如何使用 Fiddler 工具查看 HTTP 響應。
路由和 URL 路徑
[HttpGet] 特性標明這些方法支持 HTTP GET,對 controller 中的方法標注 url 路徑,有以下步驟:
- 在 controller 特性中標注模板:
[Route("api/[controller]")]
; - 替換模板中的
[Controller]
為實際 controller 名稱(在這里和以前一樣,約定優於配置,所有 controller 都必須以Controller
結尾,模板中則可以省略Controller
后綴,譯者注)。比如本例中的TodoController
,把{controller}
換成todo
即可。在 ASP.NET MVC Core 是大小寫不敏感的。 - 如果
[HttpGet]
也有一個模板,可以把它附加到路徑后面,本例中不使用模板字符串。
對於 GetById
方法,在實際 HTTP 請求中 {id}
是一個占位符,客戶端運行時使用 todo
的 ID 屬性;當 MVC 調用 GetById
時,會把 {id}
占位符分配到 Url 方法的 id
參數中。
更換 "api/todo" 的啟動 Url
- 右擊項目 > Properties
- 選擇 Debug 選項卡,將 Launch URL 的值設置為
api/todo
:
了解更多有關請求路由的信息請參考路由到控制器 Action。
返回值
GetAll
方法返回 CLR 對象。MVC 自動把對象序列化為 JSON 並把 JSON 對象寫入響應消息正文。響應狀態碼為 200,假設沒有未處理異常的情況下。(未處理異常一般會被轉化為 5xx 錯誤。)
相反,GetById
將會返回 IActionResult
類型,它代表了更加通用的結果對象。因為 GetById
有兩個不同的返回值:
- 如果沒有數據項可以匹配 ID,方法會返回 404 錯誤,並最終以返回 NotFound 告終。
- 否則方法會返回 200 以及 JSON 響應正文。並最終以返回 ObjectResult 告終。
使用 Fiddler 調用 API
這一步是可選的,但是有助於我們查看 Web API 返回的原始 HTTP 響應。
在 Visual Studio 中,點擊 ^F5
啟動項目。Visual Studio 啟動瀏覽器並導航到 http://localhost:port/api/todo
,port 是一個隨機數。如果你使用 Chrome、Edge 或者 Firefox 瀏覽器,todo 數據將會被顯示。如果你使用 IE,IE 將會彈出窗口提示要求打開或者保存 todo.json 文件。
啟動 Fiddler,從 File 菜單,取消選擇 Capture Traffic 選項。這個會關閉捕獲 HTTP traffic。
選擇 Composer 頁面。在 Parsed 選項卡中,輸入 http://localhost:port/api/todo
,port 是實際的端口號。點擊 Execute 發送請求。
結果會顯示在 sessions 列表中,響應碼是200。使用 Inspectors 選項卡來查看響應內容,包括請求正文。
實現其他的 CRUD 操作
最后一步是為 Controller 增加 Create
、Update
以及 Delete
這三個方法。這些方法都是圍繞着一個主題,所以我將只列出代碼以及標注出主要的區別。
Create
[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
TodoItems.Add(item);
return CreatedAtRoute("GetTodo", new { controller = "Todo", id = item.Key }, item);
}
這是一個 HTTP POST 方法,用 [HttpPost] 特性聲明。[FromBody] 特性告訴 MVC 從 HTTP 請求的正文中獲取 to-do 項的值。
當通過客戶端向服務器發出 POST 請求來創建新資源時,CreatedAtRoute 方法將返回標准的 201 響應。CreateAtRoute
還把 Location 頭信息加入到了響應。Location 頭信息指定新創建的 todo 項的 URI。查看 10.2.2 201 Created。
我們使用 Fiddler 來創建和發送一個請求:
- 在 Composer 頁面,從下拉框選擇 POST。
- 在請求頭的文本框中, 添加
Content-Type: application/json
,意思是Content-Type
類型的頭信息值為application/json
。Fiddler 會自動添加 Content-Length 頭信息。 - 在請求正文的文本框,輸入以下內容:
{"Name":"<你的 to-do 項目>"}
- 點擊 Execute。
這是一個簡單的 HTTP 會話. 使用 Raw 選項卡查看會話數據.
Request:
POST http://localhost:29359/api/todo HTTP/1.1
User-Agent: Fiddler
Host: localhost:29359
Content-Type: application/json
Content-Length: 33
{"Name":"Alphabetize paperclips"}
Response:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: http://localhost:29359/api/Todo/8fa2154d-f862-41f8-a5e5-a9a3faba0233
Server: Microsoft-IIS/10.0
Date: Thu, 18 Jun 2015 20:51:55 GMT
Content-Length: 97
{"Key":"8fa2154d-f862-41f8-a5e5-a9a3faba0233","Name":"Alphabetize paperclips","IsComplete":false}
Update
[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody] TodoItem item)
{
if (item == null || item.Key != id)
{
return BadRequest();
}
var todo = TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
TodoItems.Update(item);
return new NoContentResult();
}
Update
類似於 Create
,但是使用 HTTP PUT。響應是 204 (No Content)。根據 HTTP 規范,PUT 請求要求客戶端發送整個實體更新,而不僅僅是增量。為了支持局部更新,請使用 HTTP PATCH。
Delete
[HttpDelete("{id}")]
public void Delete(string id)
{
TodoItems.Remove(id);
}
方法返回 204 (無內容) 響應。這意味着客戶端會收到 204 響應即使該項目已被刪除,或者根本不存在。有兩種方法來處理請求刪除不存在資源的問題:
- "Delete" 代表「刪除一個已存在的項」,如果不存在返回 404。
- "Delete" 代表「確保該項不在集合中」,如果項目不在集合中返回 204。
無論哪種方法是合理的。如果收到 404 錯誤,客戶端將需要處理這種情況。
下一步
- 關於如何為原生移動 App 創建后端, 請參考為原生移動應用創建后台服務。
- 更多關於 API 部署的問題, 請參考發布與部署。
- 查看或者下載示例代碼