redwarrior’s diary

C#, ASP.NET, WEB, G*などの雑多な情報

Unity で AOP

Unity で AOP

.NETのDIコンテナとしてUnityを使用していて、DIコンテナを使うならばAOPでログをはさみこめると便利だなと思って調べてみました。

Javaだとバイトコードをいじって目的を達成してしまう黒魔術なライブラリがあるけれど、C#ではCLR周りは良くわからないのでそう言った方法ではなく、インターフェースを使用する方向で調べました。

たどり着いたのが以下のサイト。Unityのバージョンが少し古いですが、設定方法は同じで行けました。

code.msdn.microsoft.com

あとは公式サイトの以下の「Dependency Injection with Unity」というドキュメントに詳しい情報が載っています。(英語ですが)

patterns & practices - Unity - Documentation

ASP.NET MVCAOP

Windows Forms等の場合は上記を応用してということになると思いますが、ASP.NET MVC はそうもいかないです。さらに言うとこっちの方が必要になると思うので引き続き調べました。

色々とググったのでサイトは覚えていませんが、最終的に以下の手順になりました。

ASP.NET MVC への導入方法

  1. NuGetから「Unity」「Unity.Mvc」「Unity.Interception」をインストール
  2. App_Startフォルダに出来たUnityMvcActivatorクラスのStartメソッドのコメントを外す
  3. UnityConfigクラスのRegisterTypesメソッドマッピングを記入する

修正したソースはこんな感じです。

2.UnityMvcActivatorクラス

public static void Start() // 修正後
{
    var container = UnityConfig.GetConfiguredContainer();

    FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
    FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));

    DependencyResolver.SetResolver(new UnityDependencyResolver(container));

    // TODO: Uncomment if you want to use PerRequestLifetimeManager
    Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
}

3.UnityConfigクラス

public static void RegisterTypes(IUnityContainer container) // 修正後
{
    // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
    // container.LoadConfiguration();

    // TODO: Register your types here
    container.AddNewExtension<Interception>();
    container.RegisterType<IProductRepository, ProductRepository>(GetInterfaceMembers());
}

private static InjectionMember[] GetInterfaceMembers() // 追加
{
    return new InjectionMember[]
               {
                   new Interceptor<InterfaceInterceptor>(),
                   new InterceptionBehavior<LoggingInterceptionBehavior>()
               };
}

LoggingInterceptionBehaviorクラスは上記サイトと大して変わらないですが、ログ出力にNLogを使用しているので自分の備忘録として残しておきます。

LoggingInterceptionBehaviorクラス

using Microsoft.Practices.Unity.InterceptionExtension;
using NLog;
using System;
using System.Collections.Generic;

namespace SampleApp.Utility
{
    public class LoggingInterceptionBehavior : IInterceptionBehavior
    {
        private static readonly Logger logger = LogManager.GetLogger("TLog");

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            var methodBase = input.MethodBase;

            logger.Trace($"{methodBase.DeclaringType.Name}.{methodBase.Name} Start");

            var result = getNext()(input, getNext);

            logger.Trace($"{methodBase.DeclaringType.Name}.{methodBase.Name} End");

            return result;
        }

        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }

        public bool WillExecute => true;
    }
}

これでDIしたオブジェクトのメソッドが実行される前後で、インターフェース名とメソッド名をログに出力することが出来ます。

インターフェース名を実装クラス名にしたい気もしますが、プロダクトではインターフェースと実装クラスは1対1になるのでまあいいかなと。

おまけ

Controllerでも同じことをしたい場合は、ActionFilterAttributeを実装したフィルターを用意することになりますね。

2016/5/11 追記

Disposeメソッドで困る(ログ出力が全てIDisposableになる)ので、ログ出力を実装クラス名にする方法を追加で調べました。以下の方法で可能でした。

LoggingInterceptionBehaviorクラスのInvokeメソッドの抜粋

logger.Trace($"{input.Target.GetType().Name}.{methodBase.Name} Start");

確認環境:

Unity 4.0.1

Unity.MVC 4.0.1

Unity.Interception 4.0.1

ASP.NET MVC 5.2.3

Visual Studio 2015

Windows 8.1 Pro