在 ASP.NET Web API 1 中啟用 CRUD 操作
演講者:Mike Wasson
本教學課程示範如何使用 ASP.NET 4.x 的 ASP.NET Web API 在 HTTP 服務中支援 CRUD 操作。
教學課程中使用的軟體版本
- Visual Studio 2012
- Web API 1 (也適用於 Web API 2)
CRUD 代表「建立、讀取、更新和刪除」,這是四種基本的資料庫操作。 許多 HTTP 服務也透過 REST 或類似 REST 的 API 對 CRUD 操作進行建模。
在本教學課程中,您將建立一個非常簡單的 Web API 來管理產品清單。 每個產品將包含名稱、價格和類別 (例如「玩具」或「硬體」) 以及產品 ID。
產品 API 將公開以下方法。
動作 | HTTP method (HTTP 方法) | Relative URI |
---|---|---|
取得所有產品的清單 | GET | /api/產品 |
依照識別碼取得產品 | GET | /api/產品/id |
按類別取得產品 | GET | /api/products?category=category |
建立新的產品 | POST | /api/產品 |
更新產品 | PUT | /api/產品/id |
刪除產品 | DELETE | /api/產品/id |
請注意,某些 URI 在路徑中包含產品 ID。 例如,要取得 ID 為28的產品,用戶端傳送 http://hostname/api/products/28
的 GET 請求。
資源
產品 API 定義了兩種資源類型的 URI:
資源 | URI |
---|---|
所有產品的清單。 | /api/產品 |
一個單獨的產品。 | /api/產品/id |
方法
四種主要的 HTTP 方法 (GET、PUT、POST 和 DELETE) 可以對應到 CRUD 操作,如下所示:
- GET 檢索指定 URI 處資源的表示形式。 GET 應該對伺服器沒有副作用。
- PUT 更新指定 URI 處的資源。 如果伺服器允許用戶端指定新的 URI,PUT 也可用於在指定的 URI 處建立新資源。 對於本教學課程,API 將不支援透過 PUT 建立。
- POST 建立新資源。 伺服器為新物件指派 URI,並將該 URI 作為回應訊息的一部分傳回。
- DELETE 刪除指定 URI 處的資源。
注意:PUT 方法取代整個產品實體。 也就是說,客戶需要傳送更新產品的完整表示。 如果要支援部分更新,首選 PATCH 方法。 本教學課程未實作 PATCH。
建立新的 Web API 項目
首先執行 Visual Studio,然後從開始頁面選擇新專案。 或者,從「檔案」功能表中選擇「新增」,然後選擇「專案」。
在範本窗格中,選擇已安裝的範本並展開Visual C# 節點。 在「Visual C#」下,選擇「Web」。 在專案範本清單中,選擇 ASP.NET MVC 4 Web 應用程式。 將項目命名為「ProductStore」並按一下 確定。
在新 ASP.NET MVC 4 專案對話方塊中,選擇 Web API 並按一下確定。
新增模型
「模型」是代表應用程式中資料的物件。 在 ASP.NET Web API 中,您可以使用強型別 CLR 物件作為模型,它們將自動序列化為用戶端的 XML 或 JSON。
對於 ProductStore API,我們的資料由產品組成,因此我們將建立一個名為 Product
的新類別。
如果方案總管尚未可見,請按一下檢視功能表並選擇方案總管。 在方案總管中,以滑鼠右鍵按一下模型資料夾。 從上下文功能表中選擇新增,然後選擇類別。 將類別命名為「產品」。
將以下屬性加入到 Product
類別中。
namespace ProductStore.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
}
新增儲存庫
我們需要儲存一系列產品。 將集合與我們的服務實作分開是個好主意。 這樣,我們就可以更改後備儲存而無需重寫服務類別。 這種類型的設計稱為儲存庫模式。 首先為儲存庫定義一個通用介面。
在方案總管中,以滑鼠右鍵按一下模型資料夾。 選擇新增,然後選擇新項目。
在範本窗格中,選擇已安裝的範本並展開「C#」節點。 在 C# 下,選擇程式碼。 在程式碼範本清單中,選擇介面。 將介面命名為「IProductRepository」。
新增以下實作:
namespace ProductStore.Models
{
public interface IProductRepository
{
IEnumerable<Product> GetAll();
Product Get(int id);
Product Add(Product item);
void Remove(int id);
bool Update(Product item);
}
}
現在將另一個類別加入 Models 資料夾中,名為「ProductRepository」。該類別將實作 IProductRepository
介面。 新增以下實作:
namespace ProductStore.Models
{
public class ProductRepository : IProductRepository
{
private List<Product> products = new List<Product>();
private int _nextId = 1;
public ProductRepository()
{
Add(new Product { Name = "Tomato soup", Category = "Groceries", Price = 1.39M });
Add(new Product { Name = "Yo-yo", Category = "Toys", Price = 3.75M });
Add(new Product { Name = "Hammer", Category = "Hardware", Price = 16.99M });
}
public IEnumerable<Product> GetAll()
{
return products;
}
public Product Get(int id)
{
return products.Find(p => p.Id == id);
}
public Product Add(Product item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
item.Id = _nextId++;
products.Add(item);
return item;
}
public void Remove(int id)
{
products.RemoveAll(p => p.Id == id);
}
public bool Update(Product item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
int index = products.FindIndex(p => p.Id == item.Id);
if (index == -1)
{
return false;
}
products.RemoveAt(index);
products.Add(item);
return true;
}
}
}
儲存庫將清單保存在本地記憶體中。 這對於教學課程來說是可以的,但在實際應用程式中,您可以將資料儲存在外部 (資料庫或雲端儲存中)。 儲存庫模式將使以後更改實作變得更加容易。
新增 Web API 控制器
如果您使用過 ASP.NET MVC,那麼您已經熟悉控制器。 在 ASP.NET Web API 中,控制器是處理來自用戶端的 HTTP 請求的類別。 新建專案精靈在建立專案時為您建立了兩個控制器。 若要查看它們,請展開方案總管中的控制器資料夾。
- HomeController 是一個傳統的 ASP.NET MVC 控制器。 它負責為網站提供 HTML 頁面,與我們的 Web API 沒有直接關係。
- ValuesController 是一個範例 WebAPI 控制器。
繼續刪除 ValuesController,方法是在方案總管中以滑鼠右鍵按一下該檔案並選擇刪除。現在新增一個新的控制器,如下:
在方案總管中,以滑鼠右鍵按一下 Controllers 資料夾。 選擇「新增」,然後選擇「控制器」。
在新增控制器精靈中,將控制器命名為「ProductsController」。 在範本下拉式清單中,選擇空 API 控制器。 然後按一下 [加入] 。
注意
無需將控制器放入名為 Controllers 的資料夾中。 資料夾名稱並不重要;它只是組織原始檔的一種便捷方式。
新增控制器精靈將在 Controllers 資料夾中建立一個名為 ProductsController.cs 的檔案。 如果該文件尚未打開,請按兩下該文件將其打開。 加入以下 using 陳述式:
using ProductStore.Models;
新增一個包含 IProductRepository 執行個體的欄位。
public class ProductsController : ApiController
{
static readonly IProductRepository repository = new ProductRepository();
}
注意
呼叫 new ProductRepository()
控制器並不是最好的設計,因為它將控制器與 IProductRepository
繫結。 有關更好的方法,請參閱使用 Web API 依賴關係解析器。
獲取資源
ProductStore API 將公開多個「讀取」操作作為 HTTP GET 方法。 每個操作都對應於 ProductsController
類別中的一個方法。
動作 | HTTP method (HTTP 方法) | Relative URI |
---|---|---|
取得所有產品的清單 | GET | /api/產品 |
依照識別碼取得產品 | GET | /api/產品/id |
按類別取得產品 | GET | /api/products?category=category |
若要取得所有產品的清單,請將此方法新增至 ProductsController
類別:
public class ProductsController : ApiController
{
public IEnumerable<Product> GetAllProducts()
{
return repository.GetAll();
}
// ....
}
方法名稱以「Get」開頭,因此按照慣例,它會對應到 GET 請求。 此外,由於方法沒有參數,因此它會對應到路徑中不包含「id」區段的 URI。
若要透過 ID 取得產品,請將此方法新增至 ProductsController
類別:
public Product GetProduct(int id)
{
Product item = repository.Get(id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return item;
}
該方法名稱也以「Get」開頭,但該方法有一個名為 id 的參數。此參數會對應到 URI 路徑的 id 區段。 ASP.NET Web API 架構會自動將 ID 轉換為參數的正確資料型別 (int)。
如果 id 無效,則 GetProduct 方法將引發 HttpResponseException 類型的例外。 該異常將被架構轉換為 404 (找不到) 錯誤。
最後新增一個按類別尋找產品的方法:
public IEnumerable<Product> GetProductsByCategory(string category)
{
return repository.GetAll().Where(
p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}
如果請求 URI 具有查詢字串,Web API 會嘗試將查詢參數與控制器方法上的參數進行比對。 因此,「api/products?category=category」形式的 URI 將會對應到此方法。
建立資源
接下來,我們將向 ProductsController
類別新增一個方法來建立新產品。 以下是該方法的簡單實作:
// Not the final implementation!
public Product PostProduct(Product item)
{
item = repository.Add(item);
return item;
}
請注意有關此方法的兩件事:
- 方法名稱以「Post…」開頭。 若要建立新產品,用戶端會傳送 HTTP POST 請求。
- 此方法採用 Product 類型的參數。 在Web API中,複雜類型的參數是從請求體中反序列化的。 因此,我們希望用戶端以 XML 或 JSON 格式傳送產品物件的序列化表示。
這個實作可以工作,但還不是很完整。 理想情況下,我們希望 HTTP 回應包含以下內容:
- 回應程式碼:預設情況下,Web API 架構將回應狀態代碼設定為 200 (正常)。 但根據 HTTP/1.1 協議,當 POST 請求導致資源建立時,伺服器應回复狀態 201 (已建立)。
- Location:伺服器建立資源時,應在回應的 Location 標頭中包含新資源的 URI。
ASP.NET Web API 讓操作 HTTP 回應訊息變得容易。 這是改進的實現:
public HttpResponseMessage PostProduct(Product item)
{
item = repository.Add(item);
var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);
string uri = Url.Link("DefaultApi", new { id = item.Id });
response.Headers.Location = new Uri(uri);
return response;
}
請注意,方法傳回類型現在是 HttpResponseMessage。 透過傳回 HttpResponseMessage 而不是 Product,我們可以控制 HTTP 回應訊息的詳細資訊,包括狀態碼和 Location 標頭。
CreateResponse 方法建立 HttpResponseMessage 並自動將 Product 物件的序列化表示形式寫入回應訊息的本文中。
注意
此範例不驗證 Product
。 有關模型驗證的資訊,請參閱 ASP.NET Web API 中的模型驗證。
更新資源
使用 PUT 更新產品非常簡單:
public void PutProduct(int id, Product product)
{
product.Id = id;
if (!repository.Update(product))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
方法名稱以「Put...」開頭,因此 Web API 將其與 PUT 請求相符。 此方法採用兩個參數:產品 ID 和更新的產品。 id 參數取自 URI 路徑,product 參數從請求本文反序列化。 預設情況下,ASP.NET Web API 架構會從路由中取得簡單參數類型,從請求本文中取得複雜類型。
刪除資源
若要刪除資源,請定義「刪除...」方法。
public void DeleteProduct(int id)
{
Product item = repository.Get(id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
repository.Remove(id);
}
如果 DELETE 請求成功,它可以傳回狀態 200 (OK) 以及描述該狀態的實體主體;狀態 202 (已接受) 如果刪除仍待處理;或狀態 204 (無內容),沒有實體本文。 在本例中,DeleteProduct
方法具有 void
傳回類型,因此 ASP.NET Web API 會自動將其轉換為狀態碼 204 (無內容)。