(2012/03 : サンプル コードを、WCF Web Api から ASP.NET Web Api に変更)
(2012/06 : サンプル コードを、ASP.NET Web API RC 版にあわせて変更)
(2013/05 : OData 対応のサンプルを、古いものから最新の ASP.NET Web API にあわせて変更)
環境 : Visual Studio 2010, ASP.NET Web API RC (ASP.NET MVC 4 RC)
REST サービス / Web Api の実践
- ASP.NET Web Api (REST サービス) におけるクロス ドメイン接続 (JSONP, CORS 等) と諸注意
- ASP.NET Web Api (REST サービス) で Custom Basic 認証を使用してクラウド (Azure) に配置する
- ASP.NET Web Api (REST サービス) の Custom HTTP Header (HTTP ヘッダー)
- ASP.NET Web Api (REST サービス) における同時実行制御 (Concurrency Management)
- ASP.NET Web Api (REST サービス) で Custom MIME タイプを処理する
- ASP.NET Web Api (REST サービス) を検索 (Query) 可能にする (および、OData への対応)
- ASP.NET Web Api (REST サービス) における IoC (関心事の分割)
- ASP.NET Web Api (REST サービス) における操作ごとの制御 (Validation, 認証/権限, Exception 処理 など)
ここでは、応用的なテーマをとりあげます。基本的な構築手順については、「Getting Started with ASP.NET Web Api」 (ASP.NET の場合)、または「REST サービスの作成」 (WCF の場合) を参照してください。
こんにちは。
今回は、REST で返すデータを、クライアント側から検索 (Query) 可能にする方法について、具体的なサンプル コードを使って説明します。なお、今回も、前回同様、ASP.NET Web API を使います。
プロジェクトの準備
前回同様、準備として、まず、ASP.NET Web API を使用した簡単な REST サービスを構築しておきましょう。(この投稿では、詳細の手順は省略します。)
今回のサービスでは、コレクションを扱うようにしたいので、下記の通り、Contact クラスの List を返すようにします。
. . .public class ContactController : ApiController{ private static List<Contact> contacts = new List<Contact>() { new Contact { Id = 1, Name = "Ichiro Matsuzaki", Rating = 3}, new Contact { Id = 2, Name = "Jiro Matsuzaki", Rating = 1 }, new Contact { Id = 3, Name = "Saburo Matsuzaki", Rating = 3 }, new Contact { Id = 4, Name = "Ichiro Suzuki", Rating = 2 }, new Contact { Id = 5, Name = "Jiro Suzuki", Rating = 1 } }; // GET /api/contact public List<Contact> Get() { return contacts; }}public class Contact{ public int Id { get; set; } public string Name { get; set; } public int Rating { get; set; }}. . .
Query 可能な REST Services
上記のサービス (ContactController) を、クライアント側からクエリー可能となるように変更してみましょう。
ASP.NET Web API で、クエリーをサポートする方法は、驚くほど簡単です!
NuGet から Microsoft ASP.NET Web API for OData (microsoft.aspnet.webapi.odata) パッケージを取得して、上記のコードを下記太字の通り変更すれば完了です。
. . .public class ContactController : ApiController{ . . . // GET /api/contact [Queryable][System.Web.Http.OData.EnableQuery] public IQueryable<Contact> Get() { return contacts.AsQueryable(); }}. . .
上記のコードを見ると、いったんデータを全件取得して、そのあとで絞り込むように動作するように思われますが、そうではありません。
Linq を使ったことがある方はご存じの通り、IQuerable を使うと、yield return により検索条件が遅延評価され、その結果が一覧として返されます。(上記の場合、List の Generic クラスが、内部で、そうした処理を実行しています。) クライアントにすべてのデータを転送してから検索をおこなうのとは異なり、無駄のない、最適なスループットを実現できます。
では、実際に動作を確認してみましょう。
まず、Id が 4 の Contact を取得してみましょう。下記で、eq は、Equal を意味しています。(下記で、%20 は、空白文字を URL エンコードした文字列です。以降も同様です。)
GET http://localhost:16826/api/contact?$filter=Id%20eq%204 HTTP/1.1User-Agent: FiddlerHost: localhost:16826
HTTP/1.1 200 OKServer: ASP.NET Development Server/10.0.0.0Date: Mon, 05 Mar 2012 06:59:01 GMTX-AspNet-Version: 4.0.30319Cache-Control: no-cachePragma: no-cacheExpires: -1Content-Type: application/json; charset=utf-8Connection: CloseContent-Length: 44[ {"Id":4,"Name":"Ichiro Suzuki","Rating":2}]
つぎに、Rating が 3 未満 (2 以下) の Contact の一覧を取得してみます。下記で、lt は、”Less Than” を意味しています。
GET http://localhost:16826/api/contact?$filter=Rating%20lt%203 HTTP/1.1User-Agent: FiddlerHost: localhost:16826
HTTP/1.1 200 OKServer: ASP.NET Development Server/10.0.0.0Date: Mon, 05 Mar 2012 07:04:12 GMTX-AspNet-Version: 4.0.30319Cache-Control: no-cachePragma: no-cacheExpires: -1Content-Type: application/json; charset=utf-8Connection: CloseContent-Length: 129[ {"Id":2,"Name":"Jiro Matsuzaki","Rating":1}, {"Id":4,"Name":"Ichiro Suzuki","Rating":2}, {"Id":5,"Name":"Jiro Suzuki","Rating":1}]
つぎに、Rating が 3 未満で、かつ、Name に “Matsu” という文字列が含まれているデータに絞り込んでみましょう。下記の通り、and や or を組み合わせることができます。
GET http://localhost:16826/api/contact? $filter=Rating%20lt%203%20and%20substringof('Matsu',Name) HTTP/1.1User-Agent: FiddlerHost: localhost:16826
HTTP/1.1 200 OKServer: ASP.NET Development Server/10.0.0.0Date: Mon, 05 Mar 2012 07:11:37 GMTX-AspNet-Version: 4.0.30319Cache-Control: no-cachePragma: no-cacheExpires: -1Content-Type: application/json; charset=utf-8Connection: CloseContent-Length: 45[ {"Id":2,"Name":"Jiro Matsuzaki","Rating":1}]
また、検索 (クエリー) だけでなく、ソートもおこなうことができます。
以下は、Rating でソートしています。
GET http://localhost:16826/api/contact?$orderby=Rating HTTP/1.1User-Agent: FiddlerHost: localhost:16826
HTTP/1.1 200 OKServer: ASP.NET Development Server/10.0.0.0Date: Mon, 05 Mar 2012 07:13:18 GMTX-AspNet-Version: 4.0.30319Cache-Control: no-cachePragma: no-cacheExpires: -1Content-Type: application/json; charset=utf-8Connection: CloseContent-Length: 221[ {"Id":2,"Name":"Jiro Matsuzaki","Rating":1}, {"Id":5,"Name":"Jiro Suzuki","Rating":1}, {"Id":4,"Name":"Ichiro Suzuki","Rating":2}, {"Id":1,"Name":"Ichiro Matsuzaki","Rating":3}, {"Id":3,"Name":"Saburo Matsuzaki","Rating":3}]
また、以下は、Rating でソートしたうちの、上位 3 件を取得しています。
GET http://localhost:16826/api/contact?$orderby=Rating&$top=3 HTTP/1.1User-Agent: FiddlerHost: localhost:16826
HTTP/1.1 200 OKServer: ASP.NET Development Server/10.0.0.0Date: Mon, 05 Mar 2012 07:14:38 GMTX-AspNet-Version: 4.0.30319Cache-Control: no-cachePragma: no-cacheExpires: -1Content-Type: application/json; charset=utf-8Connection: CloseContent-Length: 129[ {"Id":2,"Name":"Jiro Matsuzaki","Rating":1}, {"Id":5,"Name":"Jiro Suzuki","Rating":1}, {"Id":4,"Name":"Ichiro Suzuki","Rating":2}]
Microsoft のテクノロジーに詳しい方は、もうお分かりと思いますが、これらのクエリー オプション (上記の $filter、$orderby など) は、すべて、OData の仕様で定義されるクエリー オプションそのものです。(下記に記載されています。)
[MSDN] REST エンドポイントを使用する OData システム クエリ オプション
http://msdn.microsoft.com/ja-jp/library/gg309461.aspx
OData フォーマットへの対応 (2013/05 変更)
OData に準拠した、Full Customize の (つまり、WCF Data Services を使用しない) REST サービスも簡単に構築できます。
まず、準備として、ASP.NET and Web Tools 2012.2 Update 以降がインストールされていない場合には、NuGet で Microsoft.AspNet.WebApi.OData を取得します。
つぎに、上記のコードの ApiController (継承元) を、下記コードの通り EntitySetController に変更し、このクラスの override メソッドとして各メソッドを実装します。
なお、「ASP.NET : Create a Read-Only OData Endpoint with ASP.NET Web API」で紹介されているように、この EntitySetController<TEntity, TKey> クラスは、ApiController クラスから派生した ODataController の派生クラス (つまり、孫) です。
. . .using System.Web.Http.OData;public class ContactController : EntitySetController<Contact, int>{ . . . // GET /api/contact [System.Web.Http.OData.EnableQuery] public override IQueryable<Contact> Get() { return contacts.AsQueryable(); } protected override Contact GetEntityByKey(int key) { return contacts.FirstOrDefault(p => p.Id == key); } . . .
さいごに、OData の応答を可能にするため、App_Statrt/WebApiConfig.cs に下記の通り追記します。
この設定により、この OData のサービスに、/odata/Contact のルート Uri でアクセスできるようになります。(なお、下記の「Contact」は、Controller の名前にあわせておいてください。)
. . .public static void Register(HttpConfiguration config){ . . . ODataModelBuilder modelBuilder = new ODataConventionModelBuilder(); modelBuilder.EntitySet<WebApiSample.Controllers.Contact>("Contact"); Microsoft.Data.Edm.IEdmModel model = modelBuilder.GetEdmModel(); config.Routes.MapODataRoute("ODataRoute", "odata", model); . . .
このサービスにアクセスすると、下記の通り、メタデータを含んだ OData の Json フォーマットが返ってきます。(もちろん、上記のクエリーもすべて可能です。)
GET http://localhost:16826/odata/Contact HTTP/1.1User-Agent: FiddlerHost: localhost:16826
HTTP/1.1 200 OKCache-Control: no-cachePragma: no-cacheContent-Type: application/json; odata=minimalmetadata; streaming=true; charset=utf-8Expires: -1Server: Microsoft-IIS/8.0DataServiceVersion: 3.0X-AspNet-Version: 4.0.30319X-Powered-By: ASP.NETDate: Wed, 08 May 2013 02:53:33 GMTContent-Length: 383{ "odata.metadata":"http://localhost:16826/odata/$metadata#Contact","value":[ { "Id":1,"Name":"Ichiro Matsuzaki","Rating":3 },{ "Id":2,"Name":"Jiro Matsuzaki","Rating":1 },{ "Id":3,"Name":"Saburo Matsuzaki","Rating":3 },{ "Id":4,"Name":"Ichiro Suzuki","Rating":2 },{ "Id":5,"Name":"Jiro Suzuki","Rating":1 } ]}
エンベロープが長くなってしまいますが、もちろん、atom の OData フォーマットで取得することも可能です。
GET http://localhost:16826/odata/Contact HTTP/1.1User-Agent: FiddlerHost: localhost:16826Accept: application/atom+xml
HTTP/1.1 200 OKCache-Control: no-cachePragma: no-cacheContent-Type: application/atom+xml; charset=utf-8Expires: -1Server: Microsoft-IIS/8.0DataServiceVersion: 3.0X-AspNet-Version: 4.0.30319X-Powered-By: ASP.NETDate: Wed, 08 May 2013 02:59:25 GMTContent-Length: 3968<?xml version="1.0" encoding="utf-8"?><feed xml:base="http://localhost:16826/odata/" xmlns=. . .> <id>http://schemas.datacontract.org/2004/07/</id> <title /> <updated>2013-05-08T02:59:25Z</updated> <link rel="self" name="localhost:16826/odata/Contact" /> <entry> <id>http://localhost:16826/odata/Contact(1)</id> <category term="WebApiSample.Controllers.Contact" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <link rel="edit" name="localhost:16826/odata/Contact(1)" /> <link rel="self" name="localhost:16826/odata/Contact(1)" /> <title /> <updated>2013-05-08T02:59:25Z</updated> <author> <name /> </author> <content type="application/xml"> <m:properties> <d:Id m:type="Edm.Int32">1</d:Id> <d:Name>Ichiro Matsuzaki</d:Name> <d:Rating m:type="Edm.Int32">3</d:Rating> </m:properties> </content> </entry> <entry> <id>http://localhost:16826/odata/Contact(2)</id> <category term="WebApiSample.Controllers.Contact" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <link rel="edit" name="localhost:16826/odata/Contact(2)" /> <link rel="self" name="localhost:16826/odata/Contact(2)" /> <title /> <updated>2013-05-08T02:59:25Z</updated> <author> <name /> </author> <content type="application/xml"> <m:properties> <d:Id m:type="Edm.Int32">2</d:Id> <d:Name>Jiro Matsuzaki</d:Name> <d:Rating m:type="Edm.Int32">1</d:Rating> </m:properties> </content> </entry> <entry> <id>http://localhost:16826/odata/Contact(3)</id> <category term="WebApiSample.Controllers.Contact" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <link rel="edit" name="localhost:16826/odata/Contact(3)" /> <link rel="self" name="localhost:16826/odata/Contact(3)" /> <title /> <updated>2013-05-08T02:59:25Z</updated> <author> <name /> </author> <content type="application/xml"> <m:properties> <d:Id m:type="Edm.Int32">3</d:Id> <d:Name>Saburo Matsuzaki</d:Name> <d:Rating m:type="Edm.Int32">3</d:Rating> </m:properties> </content> </entry> <entry> <id>http://localhost:16826/odata/Contact(4)</id> <category term="WebApiSample.Controllers.Contact" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <link rel="edit" name="localhost:16826/odata/Contact(4)" /> <link rel="self" name="localhost:16826/odata/Contact(4)" /> <title /> <updated>2013-05-08T02:59:25Z</updated> <author> <name /> </author> <content type="application/xml"> <m:properties> <d:Id m:type="Edm.Int32">4</d:Id> <d:Name>Ichiro Suzuki</d:Name> <d:Rating m:type="Edm.Int32">2</d:Rating> </m:properties> </content> </entry> <entry> <id>http://localhost:16826/odata/Contact(5)</id> <category term="WebApiSample.Controllers.Contact" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <link rel="edit" name="localhost:16826/odata/Contact(5)" /> <link rel="self" name="localhost:16826/odata/Contact(5)" /> <title /> <updated>2013-05-08T02:59:25Z</updated> <author> <name /> </author> <content type="application/xml"> <m:properties> <d:Id m:type="Edm.Int32">5</d:Id> <d:Name>Jiro Suzuki</d:Name> <d:Rating m:type="Edm.Int32">1</d:Rating> </m:properties> </content> </entry></feed>
データ フォーマットだけでなく、OData 仕様の、括弧付きの問い合わせなどにも対応しています。
GET http://localhost:16826/odata/Contact(3) HTTP/1.1User-Agent: FiddlerHost: localhost:16826
HTTP/1.1 200 OKCache-Control: no-cachePragma: no-cacheContent-Type: application/json; odata=minimalmetadata; streaming=true; charset=utf-8Expires: -1Server: Microsoft-IIS/8.0DataServiceVersion: 3.0X-AspNet-Version: 4.0.30319X-Powered-By: ASP.NETDate: Wed, 08 May 2013 03:03:01 GMTContent-Length: 126{ "odata.metadata":"http://localhost:16826/odata/$metadata#Contact/@Element", "Id":3, "Name":"Saburo Matsuzaki", "Rating":3}
追記 :
WCF Data Services のような Model First (Data Driven) の ASP.NET Web API サービスを構築する際は、 Visual Studio 2013 から使用可能なスキャフォールディング (Scaffold) を使用してください。
スキャフォールディング (Scaffold) の使用方法については、「Visual Studio 2013 の Single Page Application (SPA) テンプレートを使った開発 (Knockout.js)」を参照してください。
Categories: Uncategorized
11 replies»