WCFのサービスにMEFを使って依存性注入

以下の記事を参考にしました。

この記事はMEFではなくPIABを使っていますが、好きなDIコンテナに置き換えて読めると思います。

簡単なサンプル

簡単なサンプルを作って実際に動かしてみました。

サービスコントラク


[ServiceContract]
public interface IService1
{
[OperationContract]
string Hello();
}

サービスコントラクトの実装

MEFがフックできるようにMefServiceBehavior属性がついているのがポイント。Import属性がついたプロパティはMEFによって設定されます。


[MefServiceBehavior]
public class Service1 : IService1
{
[Import]
public Greeting Greeting { get; set; }

public string Hello()
{
return Greeting.Hello();
}
}

MEFによってエキスポートされるクラス

エキスポートされてサービスコントラクトの実装にインポートされます。


[Export(typeof(Greeting))]
public class Greeting
{
public string Hello()
{
return "Hello!!!";
}
}

Service1でGreetingインスタンスを呼び出したいわけですが、デフォルトのままではGreetingはServic1にImporされないので、そのために必要なクラスを以下に作ります。

サービスのインスタンスを提供するクラス。

WCFの拡張ポイント。


public class MefInstanceProvider : IInstanceProvider
{
public CompositionContainer Container { get; private set; }

public MefInstanceProvider()
{
Container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
}

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

public object GetInstance(InstanceContext instanceContext, Message message)
{
var type = instanceContext.Host.Description.ServiceType;
if (type == null)
{
throw new InvalidOperationException();
}
var instance = Activator.CreateInstance(type);
Container.SatisfyImportsOnce(instance);
return instance;
}

public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
var disposable = instance as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}

サービスコントラクトを制御する属性。

WCFの拡張ポイント。サービスコントラクトもしくはサービスコントラクトの実装クラスに指定できます。


[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public class MefServiceBehavior : Attribute, IContractBehavior, IContractBehaviorAttribute
{
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}

public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.InstanceProvider = new MefInstanceProvider();
}

public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}

public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}

public Type TargetContract
{
get
{
return null;
}
}
}

クライアント

たとえばコンソールアプリからIISにホストされたWCFを呼び出せます。


class Program
{
static void Main(string[] args)
{
var client = new ServiceReference1.Service1Client();
var result = client.Hello(); // Hello!!!
Console.WriteLine(result);
Console.ReadKey();
}
}

コンソールには Hello!!! と出力されます。

まとめ

意外と簡単に実現できることがわかりました。
MefServiceBehaviorみたいな属性をわざわざすべてのサービスコントラクトの実装に記述したくないという場合はServiceHostFactoryを使えるかも。ServiceHostFactoryを使ってUnityとWCFを組み合わせているわかりやすい例がありました。

でも、これ全部のsvcファイルに記述しないといけないんでしょうか?どこか一箇所だけ修正すればいいなら使えますが、全部書くなら属性で指定するのとそんなに変わんないですね。