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: