在 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 來管理產品清單。 每個產品都會包含名稱、價格和類別 (,例如「玩具」或「硬體」) ,加上產品識別碼。

產品 API 將會公開下列方法。

動作 HTTP method 相對 URI
取得所有產品的清單 GET /api/products
依照識別碼取得產品 GET /api/products/id
依類別取得產品 GET /api/products?category=category
建立新的產品 POST /api/products
更新產品 PUT /api/products/id
刪除產品 刪除 /api/products/id

請注意,某些 URI 包含路徑中的產品識別碼。 例如,若要取得識別碼為 28 的產品,用戶端會傳送 的 http://hostname/api/products/28 GET 要求。

資源

產品 API 會定義兩種資源類型的 URI:

資源 URI
所有產品的清單。 /api/products
個別產品。 /api/products/id

方法

(GET、PUT、POST 和 DELETE) 四個主要 HTTP 方法可以對應至 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」,然後按一下 [ 確定]。

新專案視窗的螢幕擷取畫面,其中顯示功能表選項,並醒目提示建立 A S P 點 NET M V C 4 Web 應用程式的路徑。

在 [ 新增 ASP.NET MVC 4 專案 ] 對話方塊中,選取 [Web API ],然後按一下 [ 確定]。

新 A P 點 NET 專案的螢幕擷取畫面,其中顯示可用範本的 Boxed 影像,並以藍色醒目提示 Web A P I 範本。

新增模型

「模型」 是代表您應用程式中資料的物件。 在 ASP.NET Web API中,您可以使用強型別 CLR 物件作為模型,而且它們會自動序列化為用戶端的 XML 或 JSON。

針對 ProductStore API,我們的資料是由產品所組成,因此我們將建立名為 Product 的新類別。

如果沒有顯示 [方案總管],請按一下 [檢視] 功能表,然後選取 [方案總管]。 在 [方案總管] 中,以滑鼠右鍵按一下Models資料夾。 從操作功能表中,選取 [ 新增],然後選取 [ 類別]。 將類別命名為 「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; }
    }
}

加入儲存機制

我們需要儲存產品集合。 最好將集合與我們的服務實作分開。 如此一來,我們可以變更備份存放區,而不需重寫服務類別。 這種類型的設計稱為存放 模式。 首先,定義存放庫的泛型介面。

在 [方案總管] 中,以滑鼠右鍵按一下Models資料夾。 選取 [新增],然後選取 [新增專案]。

方案總管功能表的螢幕擷取畫面,其中醒目提示 [模型] 選項,並顯示功能表以新增專案。

在 [ 範本] 窗格中,選取 [ 已安裝的範本 ],然後展開 [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 控制器]。 然後按一下 [ 新增]。

[新增控制器] 視窗的螢幕擷取畫面,其中顯示要輸入名稱的控制器名稱欄位,以及 Scaffolding 選項底下的下拉式清單。

注意

您不需要將控制器放入名為 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 相對 URI
取得所有產品的清單 GET /api/products
依照識別碼取得產品 GET /api/products/id
依類別取得產品 GET /api/products?category=category

若要取得所有產品的清單,請將這個方法新增至 ProductsController 類別:

public class ProductsController : ApiController
{
    public IEnumerable<Product> GetAllProducts()
    {
        return repository.GetAll();
    }
    // ....
}

方法名稱以 「Get」 開頭,因此依慣例它會對應至 GET 要求。 此外,因為 方法沒有參數,所以它會對應至路徑中不包含 「id」 區段的 URI。

若要依識別碼取得產品,請將這個方法新增至 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架構會自動將識別碼轉換成參數的正確資料類型 (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 (OK) 。 但根據 HTTP/1.1 通訊協定,當 POST 要求產生資源建立時,伺服器應該回復狀態為 201 (建立) 。
  • 位置: 當伺服器建立資源時,它應該會在回應的 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 回應訊息的詳細資料,包括狀態碼和位置標頭。

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參數取自 URI 路徑,且產品參數會從要求本文還原序列化。 根據預設,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 ([沒有內容]) 。