LoginSignup
0
3

More than 1 year has passed since last update.

DryIoc にInterceptor 機能を追加する

Last updated at Posted at 2021-06-04

Interceptor(Aspect)

DI によって注入されるオブジェクトの動作に「横断的関心」を組み込む。
主要なものにログ取得、トランザクション処理がある。

Aspect使わない場合

public int Foo(){
    logger.LogTrace($"{nameof(Foo)} start");
    DoSomething();
    using(var transaction = context.Database.BeginTransaction()){    
        var data = context.Foos.Find(1);
        logger.LogDebug($"data id = {data.Id} name = {data.Name}")
        data.Name ="updated";
        context.SaveChanges();
        transaction.Commit();
        logger.LogDebug($"{nameof(Foo)} commit")

        return 1;
    }
}

となって、特にLog出力は処理の本質には関係ない(=Log出力を削除しても処理結果は同じ)のに処理の可読性を下げてしまう。

Aspect使うと

[TransactionMethod]
public int Foo(){
    DoSomething();

        var data = context.Foos.Find(1);
        logger.LogDebug($"data id = {data.Id} name = {data.Name}")

        data.Name ="updated";
        context.SaveChanges();
        transaction.Commit();

        return 1;
    }
}

で済み、デバッグ用出力以外の「本質に関係ない」コードを除去できる。

他にも、メソッドの前後に必ず実行する処理を挟み込んだり継承によらず処理を書き換えたりできるようになったりする。

DryIoc によるInterceptor

Castle.DynamicProxy.IInterceptor の実装を作る。

// Interceptor 自体のコード
    public class TraceInterceptor : IInterceptor
    {
        ILogger logger = LogManager.GetCurrentClassLogger(); // 例えばNLogに出力 
        public void Intercept(IInvocation invocation)
        {
             logger.Trace($"{invocation.Method.Name} start");
            invocation.Proceed();
             logger.Trace($"{invocation.Method.Name} end");
        }
    }

Interceptor を適用できるようにする

    public static class DryIocInterception
    {
        public static void Intercept<TService, TInterceptor>(this IRegistrator registrator, object serviceKey = null)
          where TInterceptor : class, IInterceptor =>
          registrator.Intercept<TInterceptor>(typeof(TService), serviceKey);

        public static void Intercept<TInterceptor>(this IRegistrator registrator, Type serviceType, object serviceKey = null)
            where TInterceptor : class, IInterceptor =>
            registrator.Intercept(serviceType, Parameters.Of.Type(typeof(IInterceptor[]), typeof(TInterceptor[])), serviceKey);

        public static void Intercept(this IRegistrator registrator, Type serviceType, Type[] interceptors, object serviceKey = null)
        {
            registrator.Intercept(
               serviceType,
               Parameters.Of.Type(
                   typeof(IInterceptor[]),
                       request =>
                       {
                           var list = new List<IInterceptor>();
                           foreach(var t in interceptors)
                           {
                               list.Add((IInterceptor)request.Container.Resolve(t));
                           }
                           return list.ToArray();
                       }),
               serviceKey);
        }

        public static void Intercept(this IRegistrator registrator,
            Type serviceType, ParameterSelector interceptorsParameterSelector, object serviceKey = null)
        {
            Type proxyType;
            if (serviceType.IsInterface())
                proxyType = ProxyBuilder.CreateInterfaceProxyTypeWithTargetInterface(
                    serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
            else if (serviceType.IsClass())
                proxyType = ProxyBuilder.CreateClassProxyTypeWithTarget(
                    serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
            else
                throw new ArgumentException(
                    $"Intercepted service type {serviceType} is not a supported, cause it is nor a class nor an interface");

            registrator.Register(serviceType, proxyType,
                made: Made.Of(
                    pt => pt.PublicConstructors().FindFirst(ctor => ctor.GetParameters().Length != 0),
                    interceptorsParameterSelector),
                setup: Setup.DecoratorOf(useDecorateeReuse: true, decorateeServiceKey: serviceKey));
        }

        private static DefaultProxyBuilder ProxyBuilder => _proxyBuilder ?? (_proxyBuilder = new DefaultProxyBuilder());
        private static DefaultProxyBuilder _proxyBuilder;


        public static void Intercept<TInterceptor>(this IRegistrator registrator, Func<Type, bool> predicate)
           where TInterceptor : class, IInterceptor
        {
            foreach (var registration in registrator.GetServiceRegistrations().Where(r => predicate(r.ServiceType)))
            {
                registrator.Intercept<TInterceptor>(registration.ServiceType);
            }
        }
    }

DIコンテナを構成する

        public static IServiceProvider CreateServiceProvider()
        {
            var services = new ServiceCollection();
            services.AddLogging(loggingBuilder => {
                loggingBuilder.ClearProviders();
                loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
                loggingBuilder.AddNLog();
            });

            var container = new DryIoc.Container(rules => rules.With()).WithDependencyInjectionAdapter(services);

            container.Register<TraceInterceptor>(reuse: Reuse.Transient);
            container.Register<TraceInterceptorAsync>(reuse: Reuse.Transient);

            container.Register<IService, Service>(reuse: Reuse.Scoped);
            container.Intercept(typeof(IService), new Type[] { typeof(TraceInterceptor), typeof(TransactionInterceptor) });

            return container.BuildServiceProvider();
        }

使う

            var provider = CreateServiceProvider();
            using(var scope = provider.CreateScope())
            {
                var service = scope.ServiceProvider.GetRequiredService<Services.IService>();
                Console.WriteLine(service.DoSomething());

            }

その他

AutofacにもUnity Container にもInterceptor注入の仕組みがあるのだけど DryIoc はいろいろと自前でやる必要があったりする。

ここのサンプルでは1サービスに複数のInterceptor を登録できるようにしている。
オリジナルは https://github.com/dadhi/DryIoc/blob/master/test/DryIoc.IssuesTests/Interception/DryIocInterception.cs

追加したのは

        public static void Intercept(this IRegistrator registrator, Type serviceType, Type[] interceptors, object serviceKey = null)
        {
            registrator.Intercept(
               serviceType,
               Parameters.Of.Type(
                   typeof(IInterceptor[]),
                       request =>
                       {
                           var list = new List<IInterceptor>();
                           foreach(var t in interceptors)
                           {
                               list.Add((IInterceptor)request.Container.Resolve(t));
                           }
                           return list.ToArray();
                       }),
               serviceKey);
        }

Parameters.Of.Type が注入する Interceptor を取り出せればよい。

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3