Получение только необходимых плагинов с MEF в .NET - PullRequest
8 голосов
/ 15 июня 2011

У меня есть интерфейс IMessageSender.

using System.ComponentModel.Composition;

public interface IMessageSender
{
    void Send(string message);
}

И у меня есть два плагина, которые реализуют этот интерфейс.Это plugin.cs.

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;

[Export(typeof(IMessageSender))]
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}

, а это plugin2.cs

[Export(typeof(IMessageSender))]
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message + "!!!!");
    }
}

И у меня есть этот код для запуска этих плагинов с MEF.

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.Collections.Generic;
using System;

public class Program
{
    [ImportMany]
    public IEnumerable<IMessageSender> MessageSender { get; set; }

    public static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();

        foreach (var message in p.MessageSender) {
            message.Send("hello, world");
        }
    }

    public void Run()
    {
      Compose();
    }

    private void Compose()
    {
        var catalog = new AggregateCatalog(); 
        catalog.Catalogs.Add(new DirectoryCatalog(@"./"));

        var container = new CompositionContainer(catalog);
        container.ComposeParts(this);
    }
}

После компиляции я получаю то, что хочу.

> mono program.exe 
hello, world
hello, world!!!!

Мой вопрос заключается в том, как можно выборочно исчерпать многие плагины.Этот пример просто получает все доступные плагины для их запуска, но что мне делать, если я просто хочу запустить первый плагин или второй плагин?

Например, могу ли я запустить только plugin2.dll следующим образом?

public static void Main(string[] args)
{
    Program p = new Program();
    p.Run();

    var message = messageSender.GetPlugin("plugin"); // ???
    message.Send("hello, world");
}

решено

Основано на этом сайте и ответе Мэтью Эбботта,Я мог бы придумать этот рабочий код.

код интерфейса (interface.cs)

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;

public interface IMessageSender
{
    void Send(string message);
}

public interface IMessageSenderMetadata
{
    string Name {get; }
    string Version {get; }
}

[MetadataAttribute]  
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MessageMetadataAttribute : ExportAttribute, IMessageSenderMetadata
{
    public MessageMetadataAttribute( string name, string version)  
            : base(typeof(IMessageSender))  
        {  
            Name = name;  
            Version = version;  
        }  

    public string Name { get; set; }  
    public string Version { get; set; }  
}

Код плагина (Plugin.cs ...)

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;

[MessageMetadataAttribute("EmailSender1", "1.0.0.0")]
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message + "????");
    }
}

Program.cs

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.Collections.Generic;
using System;
using System.Linq;

public class Program
{
    [ImportMany(typeof(IMessageSender), AllowRecomposition = true)]  
    public IEnumerable<Lazy<IMessageSender, IMessageSenderMetadata>> Senders { get; set; }

    public static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();

        var sender1 = p.GetMessageSender("EmailSender1","1.0.0.0");
        sender1.Send("hello, world");
        sender1 = p.GetMessageSender("EmailSender2","1.0.0.0");
        sender1.Send("hello, world");
    }

    public void Run()
    {
      Compose();
    }

    public IMessageSender GetMessageSender(string name, string version)
    {
      return Senders
        .Where(l => l.Metadata.Name.Equals(name) && l.Metadata.Version.Equals(version))
        .Select(l => l.Value)
        .FirstOrDefault();
    }

    private void Compose()
    {
        var catalog = new AggregateCatalog(); 
        catalog.Catalogs.Add(new DirectoryCatalog(@"./"));

        var container = new CompositionContainer(catalog);
        container.ComposeParts(this);
    }
}

1 Ответ

19 голосов
/ 15 июня 2011

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

public interface INameMetadata
{
  string Name { get; }
}

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

[Export(typeof(IMessageSender)), ExportMetadata("Name", "EmailSender1")]
public class EmailSender : IMessageSender
{
  public void Send(string message)
  {
    Console.WriteLine(message);
  }
}

Что MEF сделает, так это сгенерирует проект с реализацией вашего интерфейса, INameMetadata с использованием значения, хранящегося в атрибуте ExportMetadata("Name", "EmailSender1").

После того, как вы это сделаете, вы можете сделать небольшую фильтрацию, поэтому переопределите ваш [Import] примерно так:

[ImportMany]
public IEnumerable<Lazy<IMessageSender, INameMetadata>> Senders { get; set; }

MEF создаст перечислимые Lazy<T, TMetadata> экземпляры, которые поддерживают отложенное создание экземпляра вашего типа экземпляра. Мы можем запросить как:

public IMessageSender GetMessageSender(string name)
{
  return Senders
    .Where(l => l.Metadata.Name.Equals(name))
    .Select(l => l.Value)
    .FirstOrDefault();
}

Выполнение этого с аргументом "EmailSender1" для параметра name приведет к возвращению нашего экземпляра EmailSender. Важно отметить, как мы выбрали конкретный экземпляр для использования на основе запроса метаданных, связанных с типом.

Вы можете пойти еще дальше и объединить атрибуты Export и ExportMetadata в один атрибут, такой как:

[AttributeUsage(AttributeTargets.Class, AllowMultiple=false), MetadataAttribute]
public class ExportMessageSenderAttribute : ExportAttribute, INameMetadata
{
  public ExportMessageSenderAttribute(string name)
    : base(typeof(IMessageSender))
  {
    Name = name;
  }

  public string Name { get; private set; }
}

Это позволяет нам использовать один атрибут для экспорта типа, в то же время предоставляя дополнительные метаданные:

[ExportMessageSender("EmailSender2")]
public class EmailSender : IMessageSender
{
  public void Send(string message)
  {
    Console.WriteLine(message);
  }
}

Очевидно, что запрос этого способа представляет вам проектное решение. Использование Lazy<T, TMetadata> экземпляров означает, что вы сможете отложить создание экземпляра экземпляра, но это означает, что для каждого ленивого может быть создан только один экземпляр. Вариант Silverlight среды MEF также поддерживает тип ExportFactory<T, TMetadata>, который позволяет вам каждый раз показывать новые экземпляры T, в то же время предоставляя вам механизм с богатыми метаданными.

...