かずきのBlog@hatena

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

XAMLからMEFのコンテナにアクセスするマークアップ拡張

何となく作ってみた。
まず、コンテナを保持しておくためのstaticなクラスを用意する。

using System.ComponentModel.Composition.Hosting;

namespace WpfMVVMBase.Markup
{
    public static class ContainerProvider
    {
        public static CompositionContainer Container { get; set; }
    }
}

そして、上のコンテナからインスタンスを返すマークアップ拡張

using System;
using System.Linq;
using System.Windows;
using System.Windows.Markup;

namespace WpfMVVMBase.Markup
{
    public class MefExtension : MarkupExtension
    {
        public string Contract { get; set; }
        public Type ContractType { get; set; }

        public MefExtension()
        {
            ContractType = typeof(FrameworkElement);
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            // コンテナが無い場合は何もできないよね
            if (ContainerProvider.Container == null) return null;
            var exports = ContainerProvider.Container.GetExports(
                ContractType, null, Contract);
            // みつからない場合は何も返せない
            if (exports.Count() == 0) return null;
            // 単一項目の場合は、それを返す
            if (exports.Count() == 1) return exports.ElementAt(0).Value;
            // 複数ある場合はIEnumerableで返す
            return exports.Select(lazy => lazy.Value);
        }
    }
}

使うときのイメージはこんな感じ。

using System.ComponentModel.Composition;

namespace WpfMVVMBase
{
    // とりあえず何も無いけど登録しておく
    [InheritedExport]
    public interface IShellModel
    {
    }

    public class ShellModel : IShellModel
    {
    }
}

そして、何処かで1回コンテナを作成して、ContainerProviderのContainerに入れておきます。

var catalog = new AggregateCatalog(
    new AssemblyCatalog(Assembly.GetExecutingAssembly()));
var container = new CompositionContainer(catalog);
ContainerProvider.Container = container;

あとは、XAMLからさくっと使えます。

<Window x:Class="WpfMVVMBase.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Shell" Height="300" Width="300"
    xmlns:mef="clr-namespace:WpfMVVMBase.Markup"
    xmlns:l="clr-namespace:WpfMVVMBase"
    Content="{mef:Mef ContractType=l:IShellModel}">
</Window>