最初に補足
完全版のやりかたをするのであれば、この内容は不要です。
やること
前の記事の続きとして、ついでにRequest Scopeでのオブジェクト管理機能を追加してみます。
通常、各種DIコンテナのスコープ管理については、その実装固有の部分があるため、詳細な対応方法は異なります。
ここでは、ASP.NET Coreとの統合をするにあたり、どのような初期化処理や終了処理が必要かについてサンプルでその概要を示し、他のDIコンテナでも応用できるようにします。
環境
- Visual Strudio 2015
- .NET Core Tooling Preview 2 for Visual Studio 2015
- Smart.Resolver 1.0.4 (自作のGuice型Dependency Resolver)
前提
前回の内容は完了している状態とします。
サンプル
前回のサンプルにRequest Scopeの対応を追加してあります。
今回は上記からソースを抜粋して概要を記述することで対応方法について示します。
概要
Smart.Resolverについて
Request Scopeの実装ではコンテナ固有の知識が必要になるため、まずSmart.Resolverのスコープ管理の概要について記述しておきます。
Smart.Resolverは、標準ではPrototypeとSingleton Scopeにのみ対応したDependency Resolverです。
ただし、機能の拡張が可能となっており、以下の実装を用意することで独自のスコープ管理機能を追加することが可能になっています。
インターフェース | 概要 |
---|---|
IScope | IScopeStorageのファクトリー、Bind()時に指定する |
IScopeStorage | Remember()/TryGet()/Clear()を実装する、スコープ内のオブジェクトプール機能 |
独自のスコープ管理を行いたい場合には、IScopeStorageの実装とそのファクトリーであるIScopeの実装を用意します。
Resolverに対してインスタンスの要求が行われた場合、スコープ管理されているバインディングについては、まずIScopeStorage.TryGet()でスコープのオブジェクトプールからインスタンスの検索を行います。
また、オブジェクトプールにインスタンスがない場合はインスタンスを生成し、IScopeStorage.Remember()によりオブジェクトプールにインスタンスを追加するというのがSmart.Resolverのスコープ管理の概要です。
なお、Singleton Scopeでのスコープ管理も、Dictionaryを使用したIScopeStorageにより実装されています。
今回は、HttpContext.Itemsでオブジェクトを管理するRequestScopeStorageを用意することで、Request Scopeでのオブジェクト管理機能を追加しています。
ライフサイクル管理
Request Scopeでのライフサイクル管理の要件を要約すると、HTTPリクエストが終わったタイミングでオブジェクトプール内のインスタンス破棄を行う、ということになります。
これをASP.NET Coreのコードイメージで言えば、以下のような処理を書けば良いと言うことになります。
app.Use(async (context, next) =>
{
using (new RequestScopeObjectPool())
{
await next();
}
});
サンプルでは以下のような拡張メソッドを用意して、next()の後でRequest Scopeで管理されるオブジェクトの破棄を行うようにしています。
public static void UseSmartResolverRequestScope(this IApplicationBuilder app, StandardResolver resolver)
{
var storage = new RequestScopeStorage(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
// RequestScopeStorageでのオブジェクト管理機能をStandardResolverに追加
resolver.Configure(container => container.Register(storage));
app.Use(async (context, next) =>
{
try
{
await next();
}
finally
{
// Request Scopeで管理されるオブジェクトの破棄
storage.Clear();
}
});
}
利用方法としては、以下のようにStartupにおいてこの拡張メソッドを以下のように使用することで、MVCの処理が終わった後にオブジェクトの破棄処理ができるようになります。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
// UseMvcの前に記述
app.UseSmartResolverRequestScope(resolver);
// Enable request scope
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
なお、注意事項として、UseSmartResolverRequestScope()はUseMvc()の前に記述する必要があります。
HttpContextでのオブジェクト管理
Request Scopeのオブジェクト自体はHttpContext.Itemsに入れてライフサイクル管理を行いますが、ASP.NET CoreではHttpContextへのアクセスにIHttpContextAccessorを使用します。
IHttpContextAccessorは標準ではサービスに追加されていないので、Startupで以下のようにIHttpContextAccessorの追加をしておく必要があります。
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
...
}
これにより、拡張メソッドUseSmartResolverRequestScope()内において、IApplicationBuilder.ApplicationServices.GetRequiredService()でIHttpContextAccessorの取得が可能となります。
RequestScopeStorage内ではIHttpContextAccessor.HttpContextにより現在のHttpContextを取得し、そこでオブジェクトプールを管理しています。
この詳細についてはRequestScopeStorageのソースを参照してください。
バインディング
Request Scopeで管理されるオブジェクトのバインディングは以下のように記述します。
resolver
.Bind<RequestScopedObject>()
.ToSelf()
.InRequestScope();
上記のバインディングを記述することにより、RequestScopedObjectのインスタンスはHttpContext内で管理されるようになり、HTTPの処理毎にインスタンスの生成と破棄が行われるようになります。
なお、InRequestScope()は統合用に用意した拡張メソッドで、その実態は以下のようになっています。
public static IBindingNamedWithSyntax InRequestScope(this IBindingInSyntax syntax)
{
return syntax.InScope(new RequestScope());
}
うさコメ
まあ、自分はRequest Scopeが必要になるような設計をすることは無いと思うんですけどね(´д`;)