はじめに
先日、Unity で AOP を使う方法を紹介しました。
さらに、Unity を使えば、WCF でも AOP が使えます。
「サービスクラスのメソッドをインターセプトしてログを出力する」サンプルを例に、Unity を使って WCF で AOP を実現する方法を説明します。
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(); } }
この属性は、インターセプトしたいクラスまたはメソッドに付けます。
準備完了!WCF で AOP してみます
[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(); } }
実行結果がこちら。
サービスクラスのメソッド呼び出しをインターセプトできました。コンソール画面は地味ですね。成功したときはちょっと感動したんですけど。
まとめ
でも、「これが何の役に立つの?」って聞かれると、返事に困ります。ログ出力くらいしか思い付きません。WCF の場合、例外処理を集約する方法は別にあるので。
そこでアイデア募集!「こんな使い方ができるんじゃない?」って思った方はぜひコメント下さい。