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.
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 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<INewsService> NewsService { get; set; }
public Program()
{
NewsService = new List<INewsService>();
}
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<string>(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: