読者です 読者をやめる 読者になる 読者になる

Unity を使って WCF で AOP する

はじめに

先日、Unity で AOP を使う方法を紹介しました。

さらに、Unity を使えば、WCF でも AOP が使えます。

「サービスクラスのメソッドをインターセプトしてログを出力する」サンプルを例に、Unity を使って WCFAOP を実現する方法を説明します。

Unity を使ってサービスクラスのインスタンスを生成するクラスを作成します

WCF では、System.ServiceModel.Dispatcher.IInstanceProvider インタフェースを実装するクラスが、サービスクラスのインスタンス生成や解放を行います。カスタム InstanceProvider の作成が可能なので、Unity を使うカスタム InstanceProvider を使えば、サービスクラスのインスタンス生成に Unity を使えます。

public class UnityInstanceProvider : IInstanceProvider
{
    private static UnityContainer _container;
    private Type _serviceContractType;
    private Type _serviceType;

    static UnityInstanceProvider()
    {
        _container = new UnityContainer();

        // UnityContainer を拡張して、メソッドのインターセプトを可能にする
        _container.AddNewExtension<Interception>();
    }

    public UnityInstanceProvider(Type serviceContractType)
    {
        _serviceContractType = serviceContractType;

        // インターセプトには透過プロキシを使う
        _container.Configure<Interception>()
            .SetInterceptorFor(_serviceContractType, new TransparentProxyInterceptor());
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        return GetInstance(instanceContext, null);
    }

    // サービスクラスのインスタンスを取得する
    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        // 1回目だけ、UnityContainer に型を登録する
        if (_serviceType == null)
        {
            _serviceType = instanceContext.Host.Description.ServiceType;
            _container.RegisterType(_serviceContractType, _serviceType);
        }
        return _container.Resolve(_serviceContractType);
    }

    // インスタンスを解放
    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        IDisposable disposable = instance as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }
}

さらに、今回は AOP がやりたいので、UnityContainer を拡張し、メソッドのインターセプトを可能にしています。

インスタンス生成で Unity を使うように指定するための属性を作成します

System.ServiceModel.Dispatcher.DispatchRuntime クラスの InstanceProvider プロパティに、カスタム InstanceProvider のインスタンスをセットすることで、インスタンス管理機能を差し替えることができます。セットする方法は構成ファイルと属性の2つ。今回は属性を使います。

public class UnityBehaviorAttribute : Attribute, IContractBehavior, IContractBehaviorAttribute
{
    public UnityBehaviorAttribute() { }
    public Type TargetContract
    {
        get { return null; }
    }
    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        Type contractType = contractDescription.ContractType;

        // InstanceProvider を差し替える
        dispatchRuntime.InstanceProvider = new UnityInstanceProvider(contractType);
    }
    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) { }
}

インスタンス生成に Unity を使いたいサービスクラスに、この属性を付けます。

ログを出力するクラスを作成します

これは先日の記事をそのまま流用。

public class LogCallHandler : ICallHandler
{
    public int Order { get; set; }
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        Console.WriteLine("Before {0}", input.MethodBase.Name);
        IMethodReturn result = getNext()(input, getNext);
        Console.WriteLine("After {0}", input.MethodBase.Name);
        return result;
    }
}

サービスクラスのメソッドがインターセプトされ、このクラスの Invoke メソッドが呼ばれます。今回は Console にログを出力。実際の開発では、イベントログやデータベースに出力するでしょうね。

インターセプトするクラスを指定するための属性を作成します

これも先日の記事から流用。

public class LogCallHandlerAttribute : HandlerAttribute
{
    public override ICallHandler CreateHandler(IUnityContainer container)
    {
        return new LogCallHandler();
    }
}

この属性は、インターセプトしたいクラスまたはメソッドに付けます。

準備完了!WCFAOP してみます

[ServiceContract]
public interface IGreetingService
{
    [OperationContract]
    string Greet(string name);
}

// GreetingService のインスタンス生成に Unity を使うように指定する。
// また、GreetingService のメソッド呼び出しをインターセプトして、
// ログを出力する。
[UnityBehavior]
[LogCallHandler]
public class GreetingService : IGreetingService
{
    public string Greet(string name)
    {
        return string.Format("Hello, {0}!", name);
    }
}

class Program
{
    static void Main(string[] args)
    {
        // サービス起動
        string address = "net.pipe://localhost/Greeting";
        ServiceHost host = new ServiceHost(typeof(GreetingService));
        host.AddServiceEndpoint(typeof(IGreetingService),
            new NetNamedPipeBinding(),
            address);
        host.Open();

        // サービス呼び出し
        IGreetingService proxy = ChannelFactory<IGreetingService>.CreateChannel(
            new NetNamedPipeBinding(),
            new EndpointAddress(address));
        string result = proxy.Greet("Ichiro");
        Console.WriteLine(result);

        // 終了
        ((IClientChannel)proxy).Close();
        host.Close();
        Console.ReadLine();
    }
}

実行結果がこちら。

f:id:griefworker:20091111220119p:image

サービスクラスのメソッド呼び出しをインターセプトできました。コンソール画面は地味ですね。成功したときはちょっと感動したんですけど。

まとめ

Unity を使えば WCFAOP が出来ます。

でも、「これが何の役に立つの?」って聞かれると、返事に困ります。ログ出力くらいしか思い付きません。WCF の場合、例外処理を集約する方法は別にあるので。

そこでアイデア募集!「こんな使い方ができるんじゃない?」って思った方はぜひコメント下さい。