Возникли проблемы с разрешением импорта MEF

Это своего рода продолжение одной из моих предыдущих статей, посвященной разрешению модулей в моем приложении WPF. Этот вопрос конкретно связан с влиянием взаимозависимостей модулей и метода построения этих модулей (т. е. через MEF или через new) на способность MEF разрешать отношения.

Я пробовал два подхода:

  • левый подход: приложение реализует IError
  • правильный подход: в приложении есть член, который реализует IError

Левый подход

Мой код выглядел так (только материал, связанный с MEF):

// app.cs
[Export(typeof(IError))]
public partial class Window1 : Window, IError
{
    [Import]
    public CandyCo.Shared.LibraryInterfaces.IPlugin Plugin { get; set; }
    [Import]
    public CandyCo.Shared.LibraryInterfaces.ICandySettings Settings { get; set; }

    private ICandySettings Settings;

    public Window1()
    {
        // I create the preferences here with new, instead of using MEF.  I wonder
        // if that's my whole problem?  If I use MEF, and want to have parameters
        // going to the constructor, then do I have to [Export] a POCO (i.e. string)?
        Settings = new CandySettings( "Settings", @"c:\settings.xml");

        var catalog = new DirectoryCatalog( ".");
        var container = new CompositionContainer( catalog);
        try {
            container.ComposeParts( this);
        } catch( CompositionException ex) {
            foreach( CompositionError e in ex.Errors) {
                string description = e.Description;
                string details = e.Exception.Message;
            }
            throw;
        }
    }
}

// plugin.cs
[Export(typeof(IPlugin))]
public class Plugin : IPlugin
{
    [Import]
    public CandyCo.Shared.LibraryInterfaces.ICandySettings CandySettings { get; set; }
    [Import]
    public CandyCo.Shared.LibraryInterfaces.IError ErrorInterface { get; set; }

    [ImportingConstructor]
    public Plugin( ICandySettings candy_settings, IError error_interface)
    {
        CandySettings = candy_settings;
        ErrorInterface = error_interface;
    }
}

// candysettings.cs
[Export(typeof(ICandySettings))]
public class CandySettings : ICandySettings
{
    ...
}

Правый подход

В основном то же, что и левосторонний подход, за исключением того, что я создал класс, наследуемый от IError, в той же сборке, что и Window1. Затем я использовал [Import], чтобы попытаться заставить MEF разрешить это для меня.

Может ли кто-нибудь объяснить, почему два способа, которыми я здесь подошел к MEF, ошибочны? Я так долго был в неведении, что вместо того, чтобы читать о MEF и пробовать разные предложения, я добавил MEF в свое решение и приступаю к коду. Часть, где это выглядит как сбой, это когда он вызывает partManager.GetSavedImport(). По какой-то причине importCache имеет значение null, чего я не понимаю. Вплоть до этого момента он просматривал часть (Window1) и пытался разрешить два импортированных интерфейса — IError и IPlugin. Я бы ожидал, что он введет код, который просматривает другие сборки в той же исполняемой папке, а затем проверит его на предмет экспорта, чтобы он знал, как разрешить импорт...

Я нашел ошибку в своем коде, и когда я ее исправил, исключение MEF изменилось и стало более полезным. Он ясно указал, что не может найти конструктор CandySettings по умолчанию! И копая дальше, я нашел хороший пост от Гленна Блока, в котором это обсуждается. Поэтому мне нужно закончить чтение и посмотреть, поможет ли его обходной путь или нет. Я все равно был бы признателен за дополнительные ответы, поскольку невозможно сказать, является ли обходной путь правильным или нет.


person Dave    schedule 19.04.2010    source источник


Ответы (2)


Этот пост действительно помог. Я не видел эту информацию раньше, но она полностью помогла мне.

http://mindinthewater.blogspot.com/2010/01/using-mef-with-classes-what-take.html

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

Моя первая попытка обойти это неудобство заключалась в том, чтобы сделать все возможное с помощью конструктора по умолчанию. Затем я предоставил судьбе, что разработчик не забудет вызвать метод Init(). Это было плохо по понятным причинам, но я все равно хотел попробовать. В конце концов, это просто не сработало - проблема здесь в том, что MEF хочет разрешить импорт и экспорт, но мой метод Init() не будет вызываться до тех пор, пока после составления частей. ., так что любые другие зависимые элементы этой конкретной библиотеки в конечном итоге получат не-настоящий экземпляр библиотеки, поскольку Init() не будет вызываться до тех пор, пока не будет вызван.

В любом случае, этот трюк с импортом строк для параметров конструктора сработал как шарм.

person Dave    schedule 20.04.2010
comment
а, так вы анонимно комментируете мою запись в блоге :-) - person Wim Coenen; 21.04.2010

Было бы полезно, если бы вы включили сообщение об ошибке, которое вы получаете.

Однако, если вы выберете левый подход, я думаю, что добавление атрибута PartNotDiscoverableAttribute в ваш класс Window1 может решить проблему.

Проблема в том, что DirectoryCatalog будет включать сборку, включающую Window1, поэтому из каталога будет доступен экспорт IError (и MEF создаст экземпляр Window1, если вы запросите значение этого экспорта). Когда вы добавляете Window1, созданный с помощью ComposeParts, вы пытаетесь добавить в контейнер еще один экспорт IError. Поскольку ваш плагин запрашивает только один экспорт IError, он не будет работать, если доступно более одного. Добавление атрибута PartNotDiscoverableAttribute к классу Window1 предотвратит его включение в каталог.

person Daniel Plaisted    schedule 19.04.2010
comment
Извините, я забыл опубликовать ошибку, но ошибка, которую я бы опубликовал, была бы неправильной. :) Оказывается, ошибка MEF говорит мне, что я должен использовать конструктор без параметров. Однако в обоих случаях у меня была только одна реализация IError. - person Dave; 20.04.2010
comment
@Dave Вы можете использовать конструкторы с параметрами, если вы поместите в них параметр ImportingConstructorAttribute, что вы сделали для плагина в опубликованном вами коде. - person Daniel Plaisted; 20.04.2010
comment
да, я сделал это в одном случае. Я плохо объяснил, что имел в виду. Я получил эту ошибку, потому что у меня не было конструктора по умолчанию, а другие конструкторы не были помечены [ImportingConstructor]. Однако все, что я видел, это примеры, в которых параметры конструктора являются другими интерфейсами, которые будут разрешены MEF, поэтому я запутался, потому что мне просто нужно передать пару строк. Я не хотел делать какую-то странную обертку указанных строк только для поддержки MEF. - person Dave; 20.04.2010