共用方式為


在 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 M V C 4 Web 應用程式的路徑。

新 ASP.NET MVC 4 專案對話方塊中,選擇 Web API 並按一下確定

新 ASP.NET 專案的螢幕擷取畫面,顯示可用範本的盒裝影像,並以藍色醒目顯示 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 (無內容)。