ASP.NET Web API 1 での CRUD 操作の有効化
作成者: Mike Wasson
このチュートリアルでは、ASP.NET Web API for ASP.NET 4.x を使用して HTTP サービスで CRUD 操作をサポートする方法について説明します。
このチュートリアルで使用するソフトウェアのバージョン
- Visual Studio 2012
- Web API 1 (Web API 2 でも機能)
CRUD は、4 つの基本的なデータベース操作である "作成、読み取り、更新、および削除" を意味します。 多くの HTTP サービスでは、REST または REST に似た API を使用して CRUD 操作もモデル化します。
このチュートリアルでは、製品の一覧を管理するための非常に単純な Web API を構築します。 各製品には、名前、価格、カテゴリ ("toys" や "hardware" など) と製品 ID が含まれます。
products API は、次のメソッドを公開します。
アクション | HTTP メソッド | 相対 URI |
---|---|---|
すべての製品の一覧を取得する | GET | /api/products |
ID で製品を取得する | GET | /api/products/id |
カテゴリ別に製品を取得する | GET | /api/products?category=category |
新しい製品の作成 | 投稿 | /api/products |
製品を更新する | PUT | /api/products/id |
製品を削除する | DELETE | /api/products/id |
一部の URI には、パスに製品 ID が含まれることに注意してください。 たとえば、ID が 28 の製品を取得するために、クライアントは http://hostname/api/products/28
に対し GET 要求を送信します。
リソース
products API は、次の 2 種類のリソースの URI を定義します。
リソース | URI |
---|---|
すべての製品の一覧。 | /api/products |
個々の製品。 | /api/products/id |
メソッド
次のように、4 つの主要な HTTP メソッド (GET、PUT、POST、DELETE) を CRUD 操作にマップできます。
- GET は指定した URI にあるリソースの表現を取得します。 GET はサーバーに副作用を与えるべきではありません。
- PUT は、指定された URI にあるリソースを更新します。 PUT を使用して、指定した URI で新しいリソースを作成することもできます。サーバーでクライアントが新しい 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」という名前を付け、[OK] をクリックします。
[新しい ASP.NET MVC 4 プロジェクト] のダイアログで、[Web API] を選択し、[OK] をクリックします。
モデルの追加
"モデル" は、アプリケーションでデータを表すオブジェクトです。 ASP.NET Web API では、厳密に型指定された CLR オブジェクトをモデルとして使用でき、クライアントの XML または JSON に自動的にシリアル化されます。
ProductStore API の場合、データは製品で構成されるため、Product
という名前の新しいクラスを作成します。
ソリューション エクスプローラーが表示されていない場合は、[表示]メニューをクリックし、[ソリューション エクスプローラー] を選択します。 ソリューション エクスプローラーで、[モデル] フォルダーを右クリックします。 コンテキスト メニューの [追加] を選択し、[クラス] を選択します。 クラスに "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 要求を処理するクラスです。 [プロジェクトの新規作成] ウィザードでは、プロジェクトの作成時に 2 つのコントローラーが自動的に作成されました。 それらを表示するには、ソリューション エクスプローラーで Controllers フォルダーを展開します。
- HomeController は、従来の ASP.NET MVC コントローラーです。 これはサイトの HTML ページを提供する責任を負い、Web API とは直接関係ありません。
- ValuesController は、WebAPI コントローラーの例です。
ソリューション エクスプローラーでファイルを右クリックし、[削除] を選択して、ValuesController を 削除します。次の手順により新しいコントローラーを追加します。
ソリューション エクスプローラーで、[コントローラー] フォルダーを右クリックします。 [追加]、[コントローラー] の順に選択します。
[コントローラーの追加] ウィザードで、コントローラー に "ProductsController" という名前を付けます。 [テンプレート] ドロップダウン リストで、[空の API コントローラー] を選択します。 [追加] をクリックします。
Note
コントローラーを Controllers という名前のフォルダーに必ず配置する必要はありません。 フォルダー名は重要ではありません。これは単に、ソース ファイルを整理するのに便利な方法です。
[コントローラーの追加] ウィザードでは、Controllers フォルダーに ProductsController.cs という名前のファイルが作成されます。 このファイルがまだ開かれていない場合は、ファイルをダブルクリックして開きます。 次の using ステートメントを追加します。
using ProductStore.Models;
IProductRepository インスタンスを保持するフィールドを追加します。
public class ProductsController : ApiController
{
static readonly IProductRepository repository = new ProductRepository();
}
Note
コントローラーから new ProductRepository()
を呼び出すことは、コントローラーを IProductRepository
の特定の実装 に結び付けるので、最適な設計ではありません。 より良い方法については、Web API 依存関係リゾルバーの使用に関するページを参照してください。
リソースの取得
ProductStore API では、いくつかの "読み取り" アクションが HTTP GET メソッドとして公開されます。 各アクションは、ProductsController
クラス内のメソッドに対応します。
アクション | HTTP メソッド | 相対 URI |
---|---|---|
すべての製品の一覧を取得する | GET | /api/products |
ID で製品を取得する | 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 にマップされます。
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;
}
この方法については、次の 2 点に注意してください。
- メソッド名は "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 になったことに注意してください。 Product の代わりに HttpResponseMessage を返すことで、状態コードや Location ヘッダーなど、HTTP 応答メッセージの詳細を制御できます。
CreateResponse メソッドは、HttpResponseMessage を作成し、Product オブジェクトのシリアル化された表現を応答メッセージの本文に自動的に書き込 みます。
Note
この例では、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 と更新された製品の 2 つのパラメーターを受け取ります。 id パラメーターは URI パスから取得され、product パラメーターは要求本文から逆シリアル化されます。 既定では、ASP.NET Web API フレームワークは、ルートから単純なパラメーター型を受け取り、要求本文から複合型を受け取ります。
リソースの削除
リソースを削除するには、"Delete..." メソッドを定義します。
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 (コンテンツなし) に自動的に変換します。