奇想曲 in C#

2010-07-31

Unity あれこれ(9): UnityとWCF(その1)

WCF(Windows Communication Foundation)とDIコンテナを組み合わせて使いたいというケースは結構多いらしく、CodePlexのDiscussionにおいても定期的に話題に上っている。

一口にWCFとUnityといっても両者の接点となりうる場所はさまざまである。大別すると、


a. WCF Serverにおいて、サービスコントラクトの実装をコンテナから取得する。

b. WCF Clientにおいて、Proxyをコンテナから取得する。


の2種に分けて考えることができる。今回は、まず a.のケースについて取り上げたいと思う。(なお、本記事ではSelf hostingサーバーについてのみ考慮する。)


WCF ServerとUnity

app.configファイルでアドレスとバインディングの設定を行っている場合、WCFサービスを開設するには最小限次のように書けばよい。

var host = new ServiceHost(typeof(Service1));
host.Open();


Service1は、サービスコントラクトIService1の実装であって、例えば、次のような定義を持つとする。

[ServiceContract]
public interface IService1
{
    [OperationContract]
    string GetData(int value);
}
public class Service1 : IService1
{
    private string _msg = "You entered:";

    public Service1() { }

    public Service1(string msg) { _msg = msg; }

    public string GetData(int value)
    {
        return string.Format("{0}: {1}", _msg, value);
    }


ここで、Service1の実装を切り替えたりService1にDependency Injectionを行いたいと思っても、ServiceHostには実装クラスの型のみを知らせていてWCF内部でそのインスタンス生成が行われるため、Unityを介在させることができない。ServiceHostのコンストラクタにUnityで構築したインスタンスを渡すことはできるが、その後のインスタンス制御はWCFに移ってしまうので、例えば独自のオブジェクトプールでサービスインスタンスを管理しようと思っても対応しきれない。

WCFのサービスインスタンス生成をプラグインのような形でカスタマイズできないのであろうか?

実はWCFには、そのための拡張ポイントが存在しており、下記の記事などにおいてその方法が紹介されている。

前者はSpring.NET, 後者はUnityを用いているが、要点は全く同じである。

すなわち、簡単に要約すると、

  1. System.ServiceModel.Dispatcher.IInstancePrividerインターフェイスを実装するカスタムのインスタンス生成・管理クラスを作成する。
  2. そのInstanceProviderを使用するカスタムのServiceBehaviorクラス(System.ServiceModel.Description.IServiceBehaviorインターフェイスの実装)を作成する。
  3. そのServiceBehaviorを元にしてServiceHostを作成する。


では、具体的にコードを用いて説明する。


まず 1. であるが、IInstancePrivoderは、GetInstance()とReleaseInstance()の2種のインターフェイスを持っており、これらをUnityを利用して実装すればよい。UnityContainerはコンストラクタで与えられるものとしており、これを用いて、自らの提供すべき型のインスタンスをResolve()して返すことがこのクラスの役割となる。

public class MyInstanceProvider : IInstanceProvider
{
    private IUnityContainer _container;
    private Type _serviceType;

    public MyInstanceProvider(Type serviceType, IUnityContainer container)
    {
        _serviceType = serviceType;
        _container = container;
    }

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

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return _container.Resolve(_serviceType);
    }

    public void ReleaseInstance(System.ServiceModel.InstanceContext instanceContext, object instance) { }
}


GetInstance()のパラメータとして、WCFの管理するサービスインスタンスの通信状態を示すInstanceContextと、このサービスインスタンスの生成を要求したWCF Messageそのものとが渡されるが、ここでは使用していない。

また、ReleaseInstance()は通信が閉じられた時など、サービスインスタンスをDisposeさせるために呼び出されるものである。ここでは何も処理を行っていない。


次に 2. の実装を示す。ここでは、ApplyDispatchBehavior()インターフェイスの実装が重要で、他の2つは何も処理を行わない。

class MyServiceBehavior : IServiceBehavior
{
    private IUnityContainer _container;

    public MyServiceBehavior(IUnityContainer container) { _container = container; }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        IInstanceProvider iprov = new MyInstanceProvider(serviceDescription.ServiceType, this._container);
        foreach (var cdb in serviceHostBase.ChannelDispatchers)
        {
            ChannelDispatcher cd = cdb as ChannelDispatcher;
            if (cd == null) continue;
            foreach (EndpointDispatcher ed in cd.Endpoints)
            {
                ed.DispatchRuntime.InstanceProvider = iprov;
            }
        }
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
}

ここでもUnityContainerはコンストラクタにより上位から与えられるものとしている。ApplyDispatchBehavior()では、ServiceDescription --> ChannelDispatcher --> EndpointDispatcher --> DispatchRuntime --> InstanceProviderとたどって、1.で作成したカスタムInstancePrivoderをアサインしている。


最後に3.であるが、ここでは、ServiceHostのOpen()が呼ばれたときのハンドラであるOnOpening()をオーバーライドし、その中で、自身のプロパティからたどれるServiceBehaviorに新たに作成したMyServiceBehaviorのインスタンスをアサインする。

また、ここでもUnityContainerは上位から与えるものとしている。

class MyServiceHost : ServiceHost
{
    private IUnityContainer _container;
    public MyServiceHost(IUnityContainer c) : base() { _container = c;  }
    public MyServiceHost(IUnityContainer c, Type serviceType, params Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    { _container = c; }

    protected override void OnOpening()
    {
        this.Description.Behaviors.Add(new MyServiceBehavior(this._container));
        base.OnOpening();
    }
}


さて、これで準備が整ったので、MyServiceHostを使うように冒頭のサーバーのコードを書き直すと次のようになる。

var container = new UnityContainer()
    .RegisterType<Service1>(new InjectionConstructor("Hello, I'm Unity-made. BTW, you entered:"));
var host = new MyServiceHost(container, typeof(Service1));
host.Open();


クライアントからサービスを呼び出すと、サービスインスタンスの構築が要求され、MyInstanceProvider.GetInstance()が呼び出される。ここで、ServiceHostの生成時に渡してあったService1型によりResolveされたインスタンスを返すコード(上の1.)が作動するわけである。


なお、ここでは具象クラスであるService1型をResolveに使用しているため、マッピングをRegisterして実装切替を行いたい場合にはあまり適していない。その場合にはMyServiceHostにService1だけでなくIService1も与えておいて(コンストラクタパラメータ経由でMyServiceBehaviorまで渡す)、MyServiceBehavior.ApplyDispatchBehavior()の中でserviceDescription.ServiceTypeの代わりに渡されたIService1を使ってMyInstanceProviderを初期化すればよい。ここで、ServiceHostの初期化に必要とされる具象クラスService1は形式的に与えるだけであり、InstanceProviderが決める型(UnityがResolveする型)が最終的なサービスインスタンスの型となる。


Unityを用いることで、サービスのインスタンスのLifecycle管理をUnityで完全に掌握できるし、ここでは示さなかったが、サービスがさらに内部構造として他のオブジェクト(例えば他のビジネスロジックやデータアクセスレイヤ)への依存関係を持つ場合も、通常のUnityの機能により自動的に関係を注入してサービス全体をconfigurationの指示のままに構築できる。

要するにサービスの構築がUnityにより非常に柔軟に行えるようになることが理解できると思う。

付記

本記事で紹介したのと同一のWCF拡張ポイントを用いて、Unityを使わないオブジェクトプールを作る方法が以下に記述されている:

MSDN Library - .NET Development/.NET Framework 4/WCF/WCF Samples/Extensibility/Pooling

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証