nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

SimpleInjectorでCastle.Core DynamicProxyを適用する拡張ライブラリを公開しました

表題の通りですが、SimpleInjectorでCastle.Core DynamicProxyを適用する拡張ライブラリをNuGetに公開しました。

www.nuget.org

こんな感じでつかいます。

var container = new Container();

container.InterceptWith<IncrementInterceptor>(x => x == typeof(Target));

container.Register<Target>();
container.Register<IncrementInterceptor>();
container.Verify();

var target = container.GetInstance<Target>();
Console.WriteLine(target .Increment(1));

InterceptWithのところで、IInterceptorを適用する条件と、適用するIInterceptorの実装を指定しています。上の例ではTargetクラスのみにIncrementInterceptorを適用しています。

実際にはトランザクション管理や認証などの処理を、ASP.NET Web APIのControllerなどに一括して適用などの利用方法が便利です。

つづきにもう少し詳しい情報を記載しておきます。

はじめに

以前DIコンテナの比較記事を紹介しましたが、SimpleInjectorが良さそうなので、しばらく使ってみることにしました。それにあたりSimpleInjectorでCastle.CoreのDynamicProxyを使ってAOPをしたいため表題のライブラリを作成し公開しました。公式では参考に公開されているスニペットを参考にしています。スニペットはDynamicProxyではなくRealProxyを利用したものですが。

事前準備

まず、アスペクトを織り込む対象のクラスを作ります。

public interface ITarget
{
    int Increment(int value);
}

public class Target
{
    public virtual int Increment(int value)
    {
        return ++value;
    }
}

渡された数値をインクリメントして返却するだけの簡単なクラスです。

つづいて、メソッドの呼び出し時に、呼び出しをインターセプトするアスペクトにあたるIInterceptorの実装クラスを作成します。

public class IncrementInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();
        invocation.ReturnValue = ((int)invocation.ReturnValue) + 1;
    }
}

メソッドの呼び出しをインターセプトし、対象のメソッドを呼び出したあと(invocation.Proceed()のところです)、戻り値にさらに1を加算するインターセプターです。

インターセプターの適用方法

インターセプターの適用方法はつぎのようなバリエーションがあります。

  1. 型パラメーターで適用先を指定する
  2. 型パラメーターをインターセプターを指定して適用する
  3. インスタンスを直接指定して適用する
  4. インスタンスを生成するFuncを指定して適用する
  5. インスタンスを生成する際にExpressionBuiltEventArgsを引数に取るFuncを指定して適用する

型パラメーターをインターセプターを指定して適用する

インターセプターの適用対象に、個別にインターセプターを指定して適用する方法が次の通りになります。

var container = new Container();
container.Intercept<ITarget>(typeof(IncrementInterceptor));
container.Register<ITarget, Target>();
container.Register<IncrementInterceptor>();
container.Verify();

var target = container.GetInstance<ITarget>();
Console.WriteLine(target .Increment(1));

実行結果はtargetのIncrementメソッドの引数である「1」にたいして、TargetクラスとIncrementInterceptorでそれぞれ1づつ加算され、3が出力されます。実際には例えばロギングや認証、トランザクション処理に使うと便利です。

織り込みの指定はInterceptWith拡張メソッドで指定しており、引数で指定された条件に合致するTypeに対してインターセプターを適用します。インターセプター自体もコンテナから取得されるので、インターセプターへのDIも可能です。

インターセプターはチェーン オブ レスポンシビリティのように(というか、そのままですが)複数連鎖するように指定することもできます。

container.Intercept<ITarget>(typeof(FirstInterceptor), typeof(SecondInterceptor));

型パラメーターをインターセプターを指定して適用する

先ほどはインターセプターの適用対象にインターセプターを指定しましたが、こちらは逆に特定のインターセプターを、条件に該当するオブジェクトに適用するケースはこちらのように利用します。

var container = new Container();
container.InterceptWith<IncrementInterceptor>(x => x == typeof(Target));
container.Register<Target>();
container.Register<IncrementInterceptor>();
container.Verify();

var target = container.GetInstance<Target>();
...

インターセプターを複数連鎖するように指定する場合は、型パラメーターでは指定しにくいので、Typeを引数で渡します。

container.InterceptWith(x => x == typeof(Target), typeof(FirstInterceptor), typeof(SecondInterceptor));

インスタンスを直接指定して適用する

container.InterceptWith(x => true, new FirstInterceptor());
container.InterceptWith(x => true, new FirstInterceptor(), new SecondInterceptor());

インスタンスを生成するFuncを指定して適用する

container.InterceptWith(x => true, () => new FirstInterceptor());
container.InterceptWith(x => true, () => new IInterceptor[] {new FirstInterceptor(), new SecondInterceptor()});

インスタンスを生成する際にExpressionBuiltEventArgsを引数に取るFuncを指定して適用する

container.InterceptWith(x => true, e => new FirstInterceptor());
container.InterceptWith(x => true, e => new IInterceptor[] {new FirstInterceptor(), new SecondInterceptor()});

良かったらご利用ください。