かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WCF RIA ServicesのDomainServiceにMEFを使ってDIする方法

先日、DomainServiceの生成処理をのっとる方法を紹介しました。

これを応用してDomainServiceにMEFを使ってDIしてしまおうと思います。ということで早速MEFRIAServicesというSilverlightアプリケーションを作ります。そして、System.ComponentModel.Compositionを参照に追加します。

参照を追加したら、DomainServiceにDIするための部品を先に作ります。とりあえずEmployeeクラスのデータをとってくるだけのシンプルなEmployeeRepositoryを作成しました。

/// <summary>
/// DomainServiceから返すデータの入れ物
/// </summary>
public class Employee
{
    [Key]
    public int ID { get; set; }

    public string Name { get; set; }
}
    
/// <summary>
/// リポジトリのインターフェース
/// </summary>
/// <typeparam name="T"></typeparam>
[InheritedExport]
public interface IRepository<T>
{
    IQueryable<Employee> GetEmployees();
}

/// <summary>
/// Employee用リポジトリ
/// </summary>
public class EmployeeRepository : IRepository<Employee>
{
    /// <summary>
    /// とりあえずダミーデータを返す
    /// </summary>
    /// <returns></returns>
    public IQueryable<Employee> GetEmployees()
    {
        return new[]
        {
            new Employee { ID = 1, Name = "田中 太郎" },
            new Employee { ID = 2, Name = "田中 二郎" },
            new Employee { ID = 3, Name = "田中 三郎" },
        }
        .AsQueryable();
    }
}

別段難しいことはしていません。IRepositoryインターフェースにInheritedExport属性をつけて、このインターフェースを実装したクラスは自動的にMEFに登録されるようにしています。

次に、DomainServiceを新規作成します。名前はEmployeeDomainServiceにしました。EmployeeDomainServiceクラスはコンストラクタでIRepositoryを受け取るようにします。

[EnableClientAccess]
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class EmployeeDomainService : DomainService
{
    private IRepository<Employee> emps;

    /// <summary>
    /// MEFからDIしてもらう
    /// </summary>
    /// <param name="emps"></param>
    [ImportingConstructor]
    public EmployeeDomainService(IRepository<Employee> emps)
    {
        this.emps = emps;
    }

    public IQueryable<Employee> GetEmployees()
    {
        return this.emps.GetEmployees();
    }
}

DomainServiceは、毎回インスタンスが作られるようにしたいのでPartCreationPolicyでNonSharedを指定しました。


そして、ファクトリクラスを用意します。こいつはコンストラクタで受け取ったコンテナからDomainServiceを取得して初期化して返すという動きをします。

namespace MEFRIAServices.Web
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;
    using System.ServiceModel.DomainServices.Server;

    public class MEFDomainServiceFactory : IDomainServiceFactory
    {
        // ジェネリックじゃないGetExportedValueメソッド
        private static MethodInfo nonGenericMethod = typeof(CompositionContainer)
                .GetMethod("GetExportedValue", Type.EmptyTypes);

        // GetExportedValue<T>のMethodInfoのキャッシュ
        private static Dictionary<Type, MethodInfo> methodCache = new Dictionary<Type,MethodInfo>();

        // MEFのコンテナ
        private CompositionContainer container;

        // コンテナをコンストラクタで渡してもらう
        public MEFDomainServiceFactory(CompositionContainer container)
        {
            this.container = container;
        }

        public DomainService CreateDomainService(Type domainServiceType, DomainServiceContext context)
        {
            // GetExporetedValue<T>メソッドを呼んでDomainServiceを生成する
            var service = MakeGenericMethod(domainServiceType)
                .Invoke(container, null) as DomainService;
            // 初期化
            service.Initialize(context);
            return service;
        }

        public void ReleaseDomainService(DomainService domainService)
        {
            domainService.Dispose();
        }

        private MethodInfo MakeGenericMethod(Type domainServiceType)
        {
            lock (methodCache)
            {
                // キャッシュ済みならキャッシュしている値を返す
                if (methodCache.ContainsKey(domainServiceType))
                {
                    return methodCache[domainServiceType];
                }

                // GetExporetedValue<T>のキャッシュをする
                var method = nonGenericMethod.MakeGenericMethod(domainServiceType);
                methodCache[domainServiceType] = method;
                return method;
            }
        }
    }
}

そして、Global.asaxで、上で作成したファクトリを使うように設定します。

namespace MEFRIAServices.Web
{
    using System;
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;
    using System.ServiceModel.DomainServices.Server;

    public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            // コンテナを作成
            var container = new CompositionContainer(
                new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            // ファクトリの設定
            DomainService.Factory = new MEFDomainServiceFactory(container);
        }

        // 間のメソッドは省略

        protected void Application_End(object sender, EventArgs e)
        {
            DomainService.Factory = null;
        }
    }
}

早速使ってみます。DomainServiceの動きが確認できればいいだけなので、データソースからMainPageにEmployeeをドラッグアンドドロップしてDataGridを作成します。

そして、おもむろに実行します!実行すると、EmployeeRepositoryクラスが返しているダミーデータが表示されます。ちゃんとDomainServiceにDIされてるみたいですね。

ということで、以上でWCF RIA Services + MEFの連携が出来ることが確認できました。めでたしめでたし。