かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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の連携が出来ることが確認できました。めでたしめでたし。