RESTful HTTPサービス開発を実践!ASP.NET Web API徹底解剖

ASP.NET 4.5で実装されたフレームワークには、RESTfulなHTTPサービスを構築できるASP.NET Web APIが含まれています。ここでは、このフレームワークの特徴を、実際のコードとともにご紹介します。

HTTPの仕様に準じたフレームワーク

ASP.NET Web APIは、Web標準――URI、HTTPに準じたHTTPサービスを構築するためのフレームワークです。公開されたAPIはHTTP通信で利用できるため、PCブラウザ(JavaScript⁠⁠・iOS・Android・タブレット等、多種多様なクライアントとともにアプリケーションを提供することができます。

実装の中心:エンドポイントの定義

まずは、APIの実装の中心となる場所、エンドポイントをご覧ください。

ASP.NET Web APIでは、リスト1のように定義します。

リスト1 顧客APIのエンドポイント
public class CustomerController : ApiController
{
    // GET api/customer
    public IEnumerable GetAll()
    {
        // すべてのCustomerを返す処理
        throw new NotImplementedException();
    }

    // GET api/customer/5
    public Customer GetCustomerById(int id)
    {
        // Idと一致するCustomerを返す処理
        throw new NotImplementedException();
    }
        
    // POST api/customer
    public void PostCustomer(Customer newCustomer)
    {
        // 新しくCustomerを作成する処理
        throw new NotImplementedException();
    }
}

このコードは、顧客APIを提供する例になります。

「HTTPメソッド:GET、URL:~/api/customer」でアクセスされたときは、GetAllメソッドが、⁠HTTPメソッド:POST、URL:~/api/customer」でアクセスされたときは、PostCustomerメソッドが呼び出されます。

このように、ApiControllerクラスを継承したクラスに、publicメソッドを定義することでAPIのエンドポイントを定義することができます。

ここでは、このエンドポイントについて2つの特徴をご紹介します。

メソッド名

1つは、呼び出されるメソッドは、メソッド名がリクエストされたHTTPメソッドから始まる(GetやPostから始まる)ものだということです。これは、⁠規約は設定に勝る」のコンセプトによるデフォルトの動作です。

もちろん、メソッド名を任意に変更することもできます。たとえば、メソッド名をPostCustomerではなくCreateにしたい場合は、以下の様に属性を付与することで呼び出しが可能になります。

リスト2 Httpメソッド属性の付与
// POST api/customer
[HttpPost]
public void Create(Customer newCustomer)

リクエストとレスポンス

2つ目の特徴は、リクエストの値はメソッドの引数で、レスポンスの値はメソッドの戻り値として、実際のHTTP通信に依存しないオブジェクトを定義できる点です。

レスポンスから見ていきましょう。

GetAllメソッドの戻り値はIEnumerable<Customer>、PostCustomerメソッドの戻り値はvoidを定義しています。このとき、実際のHTTPレスポンスは、表1のように構築されクライアントへ返されます。

表1 実際のHTTPレスポンス
メソッドの戻り値の例HTTPレスポンス(ステータスコード Body) 
IEnumerable<Customer>, Customer200 OK戻り値の値をJsonやXml等にシリアライズされたもの
void204 No Contentなし

ですが、状況によっては、エラーを表すステータスコードや、リソースが見つからないことを表すステータスコードを返したい場合もあります。

その場合は、リスト3のように例外を吐き出すことで可能になります。

リスト3 404 NotFoundを返すコードの例
// GET api/customer/5
public Customer GetCustomerById(int id)
{
    // ...データが見つからなかった場合
    throw new HttpResponseException(HttpStatusCode.NotFound);
}

次はリクエストを見てみましょう。

GetCustomerByIdメソッドの引数はint型のid、PostCustomerメソッドの引数はCustomerオブジェクトを定義しています。

デフォルトでは、表2のようにHTTPリクエストの値が代入された状態でメソッドが呼び出されます。

表2
メソッドの引数の例代入されるHTTPリクエストの値
int、string、DateTime等のプリミティブ型QueryStringの値、URLの一部の値
例のCustomerクラスの様な複合オブジェクトBodyの値(Json、XML、URLエンコードデータ等)

もちろん、この他にも画像データやCSV、HTTPヘッダの値等、さまざまなHTTPリクエストの値を、任意のオブジェクトに代入するよう定義することができます。⁠詳細は後述の「様々なメディアタイプへの対応」をご参照ください⁠⁠。

以上、実装の中心になるエンドポイントについてご紹介しました。

この後は、その他の特徴についていくつかご紹介します。

検証機能

リクエストの値を検証しなければならないことは少なくありません。

ASP.NET MVCから登場したモデルバインダによる検証の機能は、ASP.NET Web APIでも有効です。検証を有効にするには、DataAnnotationsの検証属性を付与することで、ModelStateオブジェクトから検証結果を参照することができます。

リスト4 検証属性を付与したCustomerクラス
public class Customer
{
    public int Id { get; set; }
        
    [Required(ErrorMessage = "{0} は必須項目です")]
    [StringLength(50, MinimumLength = 6)]
    public string Name { get; set; }

    public string Address { get; set; }

    [Range(1, 10)]
    public int Point { get; set; }
}
図1 検証結果がModelStateに格納されていることを確認
図1 検証結果がModelStateに格納されていることを確認

自由自在にURLを定義する

URLルーティング機能より、URLとエンドポイントのマッピングを設定することができるため、URLを自由自在に定義することもできます。

リスト5のコードは、URL「api/customer/{groupName}/{id}」を、CustomerクラスのGetByGroupNameメソッドにマッピングを行う例です。

リスト5 URLルーティングの例
config.Routes.MapHttpRoute(
    name: "GetCustomerApi",
    routeTemplate: "api/customer/{groupName}/{id}",
    defaults: new { 
        controller = "Customer", 
        action = "GetByGroupName" },
    constraints: new { 
        httpMethod = new HttpMethodConstraint(HttpMethod.Get) }
);

URLだけなく、HTTPメソッドの条件も指定することができます。

またこのURLルーティングでは、マッピングを設定するだけではなく、{groupName}や{id}の様に、URLの一部をパラメータとして定義し、エンドポイントにてその値を受け取ることもできます。

ODataのURL規約によるクエリオプションの利用

ASP.NET Web APIはODataにも対応しました。それに伴い、ODataのURL規約によるクエリオプションを利用できます。

URLに「$top=2」「$orderby=name」と付けることで、コレクションの値をどのように取得するかクライアント側で指定することができます。

実装は、リスト6のようにエンドポイントのメソッドにQueryable属性をつけるだけです。

リスト6 Queryable属性の付与
// GET api/customer
[Queryable]
public IEnumerable<Customer> GetAll()

この例では、⁠api/customer?$top=2&$skip=1」というURLでクエリオプションを指定した場合、コレクションを1つスキップし、上位2つのCustomerの値が返されます。

また、このクエリオプションの利用に制限を加えることもできます。

$skipの利用は制限したい、といった場合は、以下の様に属性のプロパティを設定します。クライアントが$skipをURLに追加して送信すると、エラーメッセージを含んだレスポンスが返されます。

リスト7 クエリオプションを制限する
// GET api/customer
[Queryable(AllowedQueryOptions = AllowedQueryOptions.Skip)]
public IEnumerable<Customer> GetAll()

このようにODataクエリオプションに対応したことにより、データベースから値を必要な範囲だけ取得するといったことや、ページングの処理等を、容易に行えるようになりました。

さまざまなメディアタイプへの対応

冒頭のエンドポイントのコードでは、リクエストとレスポンスを共にCustomerオブジェクトで定義しましたが、実際のHTTP通信では、さまざまなメディアタイプ(JsonやXML等)が利用されます。

ASP.NET Web APIは、クライアントから要求されるメディアタイプに対応できるよう、拡張ポイントが用意されています。デフォルトでは、application/json、application/xml、application/x-www-form-urlencodedの3つに対応しているので、それら以外に対応する必要がない場合は特に手を加える必要はありません。

もしimage/jpgやmultipart/form-data等他のメディアタイプに対応する場合は、⁠MediaTypeFormatter」を実装することになります。

ここでは実装方法を割愛しますが、冒頭のエンドポイントのコードを例に、概念図を図2に掲載します。

図2 MediaTypeFormatterの概念図
図2 MediaTypeFormatterの概念図

たとえば、メディアタイプtext/plainに対応する場合は、Customerオブジェクトをtext/plainにシリアライズ・シリアル化解除する処理を、MediaTypeFormatterに記述することになります。

このとき、エンドポイントのコードを変更する必要はありません。対応するメディアタイプが増えた場合でも、エンドポイントとは疎結合に拡張することができます。

強力な開発者へのサポート機能

最後にコードから少し離れて、開発を支援する強力な機能をご紹介します。

ヘルプページ自動生成機能と、トレース機能です。

前者の機能は、コードから自動的にAPIのヘルプページを生成する機能です図3⁠。

図3 自動生成されたヘルプページの例
図3 自動生成されたヘルプページの例

このヘルプページはASP.NET MVCで作成され、そのままWebサイトとして発行することができます。また、CSSとHtmlはプロジェクトに配置されているため、構成やデザインは自由に変更することができます。

後者のトレース機能は、フレームワーク内部の働きをログとして出力してくれる機能です図4⁠。実装の問題発見に役に立つでしょう。

図4 トレースの出力結果
図4 トレースの出力結果

以上、ASP.NET Web APIの特徴をご紹介させて頂きました。

ASP.NET Web APIのライブラリはNuGetで配布されており、Visual Studioにはテンプレートが用意されています。ご興味をお持ちになった方はぜひお試しになってはいかがでしょうか。

おすすめ記事

記事・ニュース一覧