Policy Injection Application Block を使ってロギング処理を分離する

いきなりですが

簡単なサンプルを。

class Program
{
    static void Main(string[] args)
    {
        var animal = new Wankuma();

        animal.Cry();

        Console.ReadLine();
    }
}

public class Wankuma
{
    public void Cry()
    {
        Console.WriteLine("クマ〜");
    }
}

これを実行すると

クマ〜

と表示されます。

呼び出されたメソッドをログに残したい!

私が携わったプロジェクトでは、業務アプリではログを出力する場面がほとんどでした。でもログを出力するコードで汚されるのは嫌…。

そこで Policy Injection Application Block の出番

Policy Injection Application Block (以下 PIAB)はアスペクト指向によるソフトウェア開発を可能にするアプリケーションブロックです。PIAB を使う事で、ビジネスロジックなどの本来的な機能から、ロギングや例外処理などの横断的な機能を分離し、外部から注入することができるようになります。
さっそく試してみます!

参照するアセンブリを追加

次の2つを追加します。

  • Microsoft.Practices.EnterpriseLibrary.Common
  • Microsoft.Practices.EnterpriseLibrary.PolicyInjection

CustomCallHandler を作成

[ConfigurationElementType(typeof(CustomCallHandlerData))]
public class EventLogCallHandler : ICallHandler
{
    public int Order { get; set; }

    public EventLogCallHandler(NameValueCollection attributes)
    {
        // このコンストラクタは必須みたい
    }

    private const string SOURCE = "PIABSample";

    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        // メソッド呼び出し前にログを出力
        EventLog.WriteEntry(SOURCE,
            string.Format("{0} メソッドを呼び出します。", input.MethodBase.Name),
            EventLogEntryType.Information);

        // メソッド呼び出し
        IMethodReturn result = getNext()(input, getNext);

        // メソッド呼び出し後にログを出力
        if (result.Exception != null)
        {
            // 例外が発生したとき
            EventLog.WriteEntry(SOURCE,
            string.Format("{0} メソッド呼び出しで例外が発生しました。", input.MethodBase.Name),
            EventLogEntryType.Error);
        }
        else
        {
            EventLog.WriteEntry(SOURCE,
            string.Format("{0} メソッドを呼び出しました。", input.MethodBase.Name),
            EventLogEntryType.Information);
        }

        return result;
    }
}

イベントログにメソッド名を出力します。

冒頭のサンプルを書き変えます

class Program
{
    static void Main(string[] args)
    {
        // PolicyInjection クラスを使って生成
        var animal = PolicyInjection.Create<Wankuma>();

        animal.Cry();

        Console.ReadLine();
    }
}

// MarshalByRefObject から派生させる
public class Wankuma : MarshalByRefObject
{
    public void Cry()
    {
        Console.WriteLine("クマ〜");
    }
}

最後に構成ファイルを記述

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <section name="policyInjection" type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.Configuration.PolicyInjectionSettings, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </configSections>
    <policyInjection>
        <policies>
            <add name="Policy">
                <matchingRules>

                    <!--Cry メソッドをインターセプトする-->
                    <add match="Cry" ignoreCase="false" type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.MatchingRules.MethodSignatureMatchingRule, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                      name="Method Signature Matching Rule" />

                </matchingRules>
                <handlers>

                    <!--自作の Callhandler を指定-->
                    <add order="0" type="PIABSample.EventLogCallHandler, PIABSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                      name="Custom Handler" />

                </handlers>
            </add>
        </policies>
    </policyInjection>
</configuration>

Cry メソッドをインターセプトして、前後でログを出力します。

実行結果

f:id:griefworker:20081113184742p:image
メソッド呼び出し前と後の、2つのログが出力されています。

まとめ

見ての通り、横断的な機能(ここではロギング)が本来的な機能(鳴き声出力)から分離されているため、スッキリしたコードになりました。