SharePoint アドイン製品一覧
SharePoint 2010 開発のステップ・バイ・ステップ
Windows Azure 入門
Windows Azure How-To 集
WCF / WF 入門
(2012/03 : サンプル コードを、WCF Web Api から ASP.NET Web Api に変更)
環境 :Visual Studio 2010ASP.NET Web API Beta (ASP.NET MVC 4 Beta)WCF Web API (Preview 5)Autofac for .NET 4 (Release 2.6.1)
REST サービス / Web Api の実践
ここでは、応用的なテーマをとりあげます。基本的な構築手順については、「REST サービスの作成」 (WCF の場合)、または 「Getting Started with ASP.NET Web Api」 (ASP.NET の場合) を参照してください。(2012/02 変更 : 「WCF Web Api」は、今後、「ASP.NET Web API」としてリリース予定です。)
こんにちは。
ちょっとこむずかしいタイトルですみませんが、今回は、ASP.NET Web API WCF Web API のメリットの 1 つである「処理の分離」について記載します。
メッセージ ハンドラー (HttpMessageHandler)
これまで、ヘッダーのカスタマイズ、ETag を使った排他処理 など、さまざまな処理を Web Api のロジックに混ぜて記載してきました。しかし、こうした汎用性の高い処理は、Web Api のロジックと分離した形で構築 (もしくは、ライブラリー化) をおこない、組み合わせ (Assemble) したいですね。ASP.NET Web API WCF Web API を使用すると、こうした Middler tire の処理をカスタマイズするための HttpMessageHandler (DelegatingHandler) を構築して、プラグインできます。
では、こちら で作成した OrderController のサンプル コード (Expires ヘッダーを追加するサンプル コード) を、今回は、カスタム ハンドラーを使って処理してみましょう。
まず、ハンドラー クラスを作成します。プロジェクトに、クラスを新規追加します。(今回、このファイル名を CustomHeaderMessageHandler.cs とします。) 作成されたクラス ファイルに、下記の通り、DelegatingHandler クラスを継承したコードを記述します。
. . .using System.Net.Http;using System.Threading;using System.Threading.Tasks;. . .public class CustomHeaderMessageHandler : DelegatingHandler{ public CustomHeaderMessageHandler() { } protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { Task<HttpResponseMessage> t = base.SendAsync(request, cancellationToken); return t.ContinueWith( task => { task.Result.Headers.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { Private = true }; task.Result.Content.Headers.Expires = DateTimeOffset.UtcNow.AddMinutes(1); return task.Result; }, TaskContinuationOptions.ExecuteSynchronously); }}. . .
DelegatingHandler クラスの override メソッドで重要なメソッドは、上記の SendAsync メソッドです。登録されたハンドラーが処理される際に、この SendAsync が呼び出されます。
なお、上記の通り、base.SendAsync は必ず呼び出すようにしてください。(この呼び出しで、他のメッセージ ハンドラーも引き続き呼び出されるようになります。) 上記では、base.SendAsync を呼び出して、その結果の HttpResponseMessage オブジェクトに、同期的に (順番に)、Expires ヘッダーを追記しています。(もちろん、他の処理と競合しないなら、ContinueWith を使わず、非同期で処理しても構いません。)今回は HttpResponseMessage オブジェクトを変更していますが、引数で渡されている HttpRequestMessage を変更し、Web Api に渡すリクエストの内容をカスタマイズすることもできます。
では、上記のメッセージ ハンドラー (CustomHeaderMessageHandler) を Web Api に組み込んでみましょう。プロジェクトの Global.asax.cs を開き、下記 (太字) の通り、追記します。ここでも、「Web Api (REST サービス) で Custom MIME タイプを処理する」で使用した HttpConfiguration を使用して、Web Api の構成情報をカスタマイズします。今回は、HttpConfiguration の MessageHandlers プロパティ (コレクション) を使用して、上記のメッセージ ハンドラーを追加しています。
. . .public static void RegisterRoutes(RouteCollection routes){ routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); HttpConfiguration conf = GlobalConfiguration.Configuration; conf.MessageHandlers.Add(new CustomHeaderMessageHandler()); . . .
今回は単にヘッダーを追加しましたが、この仕組みを使って、例えば、ETag の処理をおこなったり、Path (URI の Segment) に応じた独自の処理をおこなったり (例えば、http://.../xml、http://.../json、http://.../atom など、URI Segment に応じて Accept フォーマットをカスタマイズしたり)、バージョンに応じて URI のマッピングをおこなう など、さまざまなカスタマイズを、Web Api のロジックと分離した形で構築して、上記のようにプラグインできます。
IoC コンテナと ServiceResolver
上記のメッセージ ハンドラーだけでは、分離がむずかしい場合もあります。典型的なパターンとして、Web Api の操作と、データベースなどへの永続化の処理を関連付けるような場合です。こうした永続化 (Persistence) の処理は、GET / POST / PUT / MERGE / DELETE などの各操作のロジックと密接に関連しているため、上述のようにメッセージ ハンドラーを使ってきれいに分離することは困難です。
こうした場合に使用できるのが、HttpConfiguration オブジェクトの ServiceResolver です。ServiceResolver を使うと、Web Api (REST サービス) が呼び出されてインスタンス化する際の処理など、ASP.NET Web Api のフレームワークに組み込まれたさまざまな処理をカスタマイズできます。Autofac、Ninject、.NET 標準の MEF などの IoC コンテナ (Inversion of Control) と組み合わせることで、Web Api で使用される各オブジェクトを関連付けることが可能です。
では、実際に、上記のコードを変更して、永続化リポジトリーの分離の例を示しましょう。今回は、IoC Container として、Open Project の Autofac を使用します。
補足 : なお、Autofac 自体も、ASP.NET MVC、WCF、MEF など、さまざまな .NET の要素技術と連携する仕組みを持っています。今回は、Autofac の基本的な仕組みだけを使用します。
まず、クラスを追加して、下記の通り、ISampleRepository インタフェースを作成します。データベースの種類に応じた永続化処理などは、この ISampleRepository インタフェースを実装 (implement) したクラスを提供して、Web Api にプラグインします。下記では、その実体 (implementation) の 1 つである SampleRepository1 も同時に作成しておきましょう。
補足 : 実際の開発では、下記の OrderItem というカスタム クラス自体も、Web Api に依存しないような方法で設計しておきましょう。今回は、サンプルとして、そのまま OrderItme を使用しています。。。
. . .public interface ISampleRepository{ OrderItem Get(int id); void Insert(OrderItem item); void Update(OrderItem item); void Delete(int id);}public class SampleRepository1 : ISampleRepository{ public OrderItem Get(int id) { OrderItem resObj = null; // Search from database (this time, we skip code . . .) . . . return resObj; } public void Insert(OrderItem item) { throw new NotImplementedException(); } public void Update(OrderItem item) { throw new NotImplementedException(); } public void Delete(int id) { throw new NotImplementedException(); }}. . .
OrderController のコンストラクタを変更し、下記 (太字) の通り、ISampleRepository インタフェースと関連付けができるようにしておきます。
. . .public class OrderController : ApiController{ private ISampleRepository rep; public OrderController(ISampleRepository arg) { this.rep = arg; } // GET /api/order/5 public HttpResponseMessage Get(int id) { // get data using ISampleRepository obejct OrderItem resObj = rep.Get(id); HttpResponseMessage<OrderItem> resMsg = new HttpResponseMessage<OrderItem>(resObj, HttpStatusCode.OK); return resMsg; } . . .}. . .
さいごに、Autofac を使って、上記のリポジトリーの実装 (SampleRepository1) と Web Api (OrderController) の関連付けをおこないます。Autofac のアセンブリ (Autofac.dll) の参照追加をおこない、Global.asax.cs を開いて、下記 (太字) の通りコードを追記します。
. . .using System.Collections;using Autofac;. . .public static void RegisterRoutes(RouteCollection routes){ routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); HttpConfiguration conf = GlobalConfiguration.Configuration; ContainerBuilder afBuilder = new ContainerBuilder(); afBuilder.RegisterType<MvcApplication1.Controllers.OrderController>(); afBuilder.RegisterType<SampleRepository1>().As<ISampleRepository>(); IContainer afContainer = afBuilder.Build(); conf.ServiceResolver.SetResolver(t => { return afContainer.IsRegistered(t) ? afContainer.Resolve(t) : null; }, t => { return new object[] { }; // do nothing }); . . .}
Web Api のフレームワークの中で呼び出される各種オブジェクトは、「サービス」 (Service) という概念で呼び出されます。今回の場合、OrderController 自体もサービスの 1 つです。そして、上記の ServiceResolver を使うと、この各サービスのインスタンス化の処理をラムダ式によってカスタマイズできます。(各サービスは、クライアントからの Web Api の呼び出しの際に、都度、呼び出されます。)上記では、OrderController の Api が呼び出されると、Autofac の Resolve メソッドにより、ビルドされた OrderController がインスタンス化されますが、この際、ISampleRepository (SampleRepository1 オブジェクト) がコンテナにビルドされているため、Autofac により、前述のコンストラクタが呼び出され、コンストラクタの引数に SampleRepository1 が渡されます。
今回 (OrderController) のように、単一のオブジェクトを返すようなサービスでは、SetResolver の最初のラムダ式 (Func<type, obejct>) が呼ばれます。また、複数のオブジェクトを返すようなサービスでは、SetResolver の 2 番目のラムダ式 (Func<type, IEnumerable<object>>) が呼ばれます。ASP.NET Web API Beta では、下記の各サービスが呼び出されます。
単一のサービス (GetService)
複数のサービス (GetServices)
また、上記のすべてのサービスを実装する必要はなく、Dependency が解決できない場合は、 1 番目のラムダ式 (GetService) では null を返し、2 番目のラムダ式 (GetServices) では IEnumerable<object>(0) を返すようにします。
補足 : Resolver として何も指定しなくても、内部で、既定の IDependencyResolver インスタンス (internal の DefaultServiceResolver) が設定され、既定で、いくつかのサービスが設定されます。
今回は、上記の通り、OrderController の Injection のみ処理していますが、例えば、下記の通り記述すると、IFilterProvider の取得の際に、FilterProvider1、FilterProvider2 の 2 つの要素の IEnumerable (複数のフィルター要素) が解決されて返されます。(既定では、属性 (Attribute) として設定されたフィルターを取得する ActionDescriptorFilterProvider や、HttpConfiguration に設定されたフィルターを取得する ConfigurationFilterProvider などが使用されています。)
. . .public static void RegisterRoutes(RouteCollection routes){ routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); HttpConfiguration conf = GlobalConfiguration.Configuration; ContainerBuilder afBuilder = new ContainerBuilder(); afBuilder.RegisterType<FilterProvider1>().As<IFilterProvider>(); afBuilder.RegisterType<FilterProvider2>().As<IFilterProvider>(); . . . IContainer afContainer = afBuilder.Build(); conf.ServiceResolver.SetResolver(t => { return null; }, t => { Type e = typeof(IEnumerable<>).MakeGenericType(t); return ((IEnumerable) afContainer.Resolve(e)).Cast<object>(); // This returns 2 instances. });. . .
ここでは、Autofac を例に使用しましたが、もちろん、.NET 標準の MEF、Ninject などの IoC を使うこともできます。また、上記は簡単なサンプルで例示しましたが、Autofac などが持つさらに高度な仕組みと組み合わせ、より高度な処理の分割が可能です。
お知らせ
今年も、恒例のマッシュアップ アワード (Mashup Awards) が開催されます。API リストにある Web Api の活用はもちろん、自分でAPI 化して登録するのもアリですので、是非、皆さんの叡智をご披露ください。(コツは、砂金のブログ に書かれています。。。)
Mashup Awards 7 (MA7)http://ma7.mashupaward.jp/?locale=ja
作品募集期間 : 2011/09/07 - 2011/11/07授賞式 : 2011/12/11 (日)