MEF и Silverlight или как сортировать импортируемые объекты

ru-RU | создано: 05.12.2010 | опубликовано: 05.12.2010 | обновлено: 31.12.2017 | просмотров за всё время: 3828

Предположу, что читатель этот статьи уже знаком с MEF и уж точно знаком с Silverlight. А теперь давайте предположим, что при помощи MEF вы получаете какие-нибудь данные, например набор информационных панелей. В силу того, что просто невозможно предугадать в какой последовательности они буду добавлены в MEF-каталог, возникает вопрос: как сортировать импортированные данные? Как это сделать я и постараюсь рассказать в этой статье.

Вступление

Когда я писал очередную версию игры по мотивам "Кто хочет стать миллионером?", то постарался применить побольше новых (в целях самообучения) интересных технологий. Одна из таких технологий, которая вошла в NET 4.0 является MEF. Теперь к делу... 

Есть такой атрибут в MEF

Речь пойдет об атрибуте ImportMany. Этот атрибут позволяет получить коллекцию экспортов (причем можно даже перекомпозицией MEF-каталога, но не об этом сейчас). Есть у меня такое свойство, которое получает набор информационных панелей. Например, панель с правилами игры, панель с таблицей рекордов "на время" или панель с таблицей рекордов "на количество", или другие какие-нибудь. Вот как это свойство выглядит:

[ImportMany]
public ObservableCollection<IInfoPanel> Panels { get; set; }

Попробывал каким-либо образом отсортировать полученные панели, но не тут-то было! На самом деле, если предоложить, что панели эти могут получаться не только из самого приложения, но и из других xap-файлов (которые могут быть даже на других сайтах и загружаться по требованию OnDemand), то сортировка встает непростым вопросом.

Всё решают метаданные

Пришлось переделать свойство, сделать свой атрибут экспорта таких панелей, в котором и указывать индекс сортировки. Ах, да, чуть не забыл, так же пришлось сделать интерфейс. Вот с него и начнем, хотя нет... сначало атрибут:

/// <summary>
/// Атрибут для экспорта экранов
/// </summary>
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class ExportInfoAttribute : ExportAttribute
{
    /// <summary>
    /// наименование ключ а в коллекции
    /// </summary>
    public int Order { get; private set; }

    /// <summary>
    /// конструктор по умолчанию
    /// </summary>
    /// <param name="orderId">индекс сортировки</param>
    public ExportInfoAttribute(int orderId)
        : base(typeof(IInfoPanel))
    {
        this.Order = orderId;
    }
}

Не трудно заметить, что авто-свойство в атрибуте называется Order и имеет тип int. Мой атрибут унаследован от базового класса. В базовый конструктор передается тип интерфейса IInfoPanel. Всё просто, правда?

Теперь займемся интерфейсом IInfoPanelMetadata. Зачем он нужен вы поймете позже когда увидите переделанное свойство Panels, которое получает информационные поля. Вот как выглядит интерфейс:

public interface IInfoPanelMetadata
{
    [DefaultValue(Int32.MaxValue)] 
    int Order { get;}
}

Сначала я попробывал сделать атрибут без установки значения по умолчанию (DefaultValue), но при старте программы вываливалось странное предупреждения, точнее ошибка получения импортов, а при компилляции проекта никаких ошибок не было.

Обновленное свойство с Lazy

Сразу приведу код:

[ImportMany]
public ObservableCollection<Lazy<IInfoPanel, IInfoPanelMetadata>> Panels { get; set; }

Заметили разницу? Правильно, свойство теперь стало "обернутым" в класс Lazy, который благодаря метаданным поможет сделать сортировку!

Решение проблемы

Для того чтобы мои панели можно было сортировать после получения, пришлось завести еще одно свойство (к которому и обращается из представления (View):

private ObservableCollection<IInfoPanel> infoPanels;
public ObservableCollection<IInfoPanel> InfoPanels
{
    get
    {
        if (infoPanels == null)
        {
            var panes = from infopans in Panels
                        orderby infopans.Metadata.Order
                        select infopans.Value;
            infoPanels = new ObservableCollection<IInfoPanel>(panes);
        }
        return infoPanels;
    }
}

Не трудно заметить, что при получении панелей в геттере используется сортировка по атрибуту Order в метаданных. Можно еще добавить, что если ImportMany используется с параметром AllowRecomposition=true, то при рекомпозиции MEF-каталога можно обнулять переменную infoPanels в null. Тогда вновь прибывшие информационные панели снова выстроятся всоответствии с указанным порядком. Это всё.