Managed Extensibility Framework (MEF)

Come posso sviluppare un'applicazione che tenga conto di codice non ancora scritto?
Come posso estendere la mia applicazione senza doverla ridistribuire ogni volta?
Con l'aiuto di MEF posso fare questo e molto altro!

Managed Extensibility Framework (o MEF) è una libreria tutta nuova distribuita con il .NET Framework 4.0 sviluppata per risolvere problemi di estendibilità delle applicazioni, ovvero per poter strutturare in modo rapido e trasparente un'architettura a plug-in.

mef

Come funziona MEF

MEF riesce a comporre le parti di un sistema con architettura a plug-in grazie a due specifiche:

  • Una coppia di attributi [Import]/[Export]. [Import] viene utilizzato per decorare le parti dell'applicazione che espongono un servizio, mentre [Export] definisce le implementazioni di quel servizio (i plug-in appunto)
  • Un catalogo che definisce dove MEF deve cercare per trovare le implementazioni da associare al servizio definito nell'applicazione
MEF_Diagram

MEF in Action: un esempio

Vogliamo sviluppare un'applicazione che visualizzi all'utente una serie di notizie provenienti da fonti diverse, le fonti devo poterle caricare a runtime.

Prima di tutto devo indicare a MEF il "punto di estensione" dell'applicazione, ovvero una proprietà IEnumerable generica con tipo l'interfaccia che i vari plug-in dovranno poi implementare e lo faccio semplicemente decorando la proprietà con l'attributo [ImportMany].

namespace MEF.Console
{
    class Program
    {
        [ImportMany]
        public IList NewsService { get; set; }

        public Program()
        {
            NewsService = new List();
        }

        static void Main(string[] args)
        {
...

Ed ecco l'interfaccia definita in un assembly esterno.

namespace MEF.PluginInterface
{
  using System;

  public interface INewsService
  {
    string Source { get; }
    string[] NewsOf(DateTime day);
  }
}

Creiamo poi un plug-in che implementi questa interfaccia. Per indicare a MEF che si tratta di un plug-in dell'applicazione devo decorare la classe che implementa il servizio con l'attributo [Export]

namespace Ansa
{
    using System;
    // namespace di MEF
    using System.ComponentModel.Composition;
    using MEF.PluginInterface;

    [Export(typeof(INewsService))]
    public class AnsaNewsService : INewsService
    {
        public string Source {
            get { return "Ansa"; }
        }

        public string[] NewsOf(DateTime day)
        {
            return new string[] 
            { 
                "Internet: Amazon debutta in Italia",
                "Evento 1nn0va sugli Anti-Pattern!"
            };
        }
    }
}

Come ultimo passaggio devo indicare a MEF come trovare i plug-in. Devo definire un catalogo, che nel nostro esempio sarà di tipo Directory, devo creare un container su quel catalogo e utilizzarlo per comporre le parti del sistema (ovvero associare le implementazioni definite con [Export] al contratto definito con [ImportMany].

private void _Compose()
{
  var c = new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory);
  CompositionContainer container = new CompositionContainer(c);
  container.ComposeParts(this);
}

Nel main possiamo quindi utilizzare il servizio:

static void Main(string[] args)
{
  Program p = new Program();
  try
  {
    p._Compose();
  }
  catch (Exception ex)
  {
    System.Console.WriteLine(ex.Message);
  }

  if (p.NewsService == null)
  {
    System.Console.WriteLine("MEF non configurato correttamente.");
    System.Console.Read();
    return;
  }

  foreach (INewsService service in p.NewsService)
  {
    System.Console.WriteLine("Notizie {0}", service.Source);
    DateTime day = DateTime.Today;
    Array.ForEach(service.NewsOf(day),
            news => System.Console.WriteLine("{0} - {1}", 
                      day.ToShortDateString(), news));
  }
  System.Console.Read();
}

A questo punto possiamo definire altri plug-in e sarà sufficiente caricare gli assembly nella directory dell'applicazione per poterli eseguire.

namespace Gazzetta
{
    using System;
    // namespace di MEF
    using System.ComponentModel.Composition;
    using MEF.PluginInterface;

    [Export(typeof(INewsService))]
    public class GazzettaNewsService : INewsService
    {
        public string Source {
            get { return "Gazzetta"; }
        }

        public string[] NewsOf(DateTime day)
        {
            return new string[] 
            { 
                "Inter e Milan qualificate agli ottavi."
            };
        }
    }
}

Riferimenti: