Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo articolo offre una panoramica di Managed Extensibility Framework introdotto in .NET Framework 4.
Che cos'è MEF?
Managed Extensibility Framework (MEF) è una libreria per la creazione di applicazioni leggere ed estendibili. Consente agli sviluppatori di applicazioni di individuare e usare le estensioni senza alcuna configurazione necessaria. Consente inoltre agli sviluppatori di estensioni di incapsulare facilmente il codice ed evitare dipendenze difficili fragili. MEF non solo consente di riutilizzare le estensioni all'interno delle applicazioni, ma anche tra le applicazioni.
Il problema dell'estendibilità
Si supponga di essere l'architetto di un'applicazione di grandi dimensioni che deve fornire supporto per l'estendibilità. L'applicazione deve includere un numero potenzialmente elevato di componenti più piccoli ed è responsabile della creazione e dell'esecuzione.
L'approccio più semplice al problema consiste nell'includere i componenti come codice sorgente nell'applicazione e chiamarli direttamente dal codice. Questo presenta numerosi svantaggi evidenti. Soprattutto, non è possibile aggiungere nuovi componenti senza modificare il codice sorgente, una restrizione che potrebbe essere accettabile in, ad esempio, un'applicazione Web, ma non è utilizzabile in un'applicazione client. Altrettanto problematico, è possibile che non si abbia accesso al codice sorgente per i componenti, perché potrebbero essere sviluppati da terze parti e per lo stesso motivo non è possibile consentire loro di accedere al proprio.
Un approccio leggermente più sofisticato consiste nel fornire un punto di estensione o un'interfaccia, per consentire la separazione tra l'applicazione e i relativi componenti. In questo modello è possibile fornire un'interfaccia che un componente può implementare e un'API per consentire l'interazione con l'applicazione. Questo risolve il problema di richiedere l'accesso al codice sorgente, ma ha ancora le proprie difficoltà.
Poiché l'applicazione non dispone di alcuna capacità per l'individuazione dei componenti autonomamente, deve comunque essere indicato in modo esplicito quali componenti sono disponibili e devono essere caricati. Questa operazione viene in genere eseguita registrando in modo esplicito i componenti disponibili in un file di configurazione. Ciò significa che assicurarsi che i componenti siano corretti diventi un problema di manutenzione, in particolare se si tratta dell'utente finale e non dello sviluppatore che si prevede di eseguire l'aggiornamento.
Inoltre, i componenti non sono in grado di comunicare tra loro, tranne attraverso i canali definiti rigidamente dell'applicazione stessa. Se l'architetto dell'applicazione non ha previsto la necessità di una particolare comunicazione, è in genere impossibile.
Infine, gli sviluppatori di componenti devono accettare una dipendenza rigida dall'assembly che contiene l'interfaccia implementata. Ciò rende difficile l'uso di un componente in più di un'applicazione e può anche creare problemi quando si crea un framework di test per i componenti.
Cosa fornisce MEF
Invece di questa registrazione esplicita dei componenti disponibili, MEF offre un modo per individuarli in modo implicito, tramite la composizione. Un componente MEF, denominato parte, specifica in modo dichiarativo sia le relative dipendenze (note come importazioni) che le funzionalità (note come esportazioni) che rende disponibili. Quando viene creata una parte, il motore di composizione MEF soddisfa le sue importazioni con ciò che è disponibile da altre parti.
Questo approccio risolve i problemi descritti nella sezione precedente. Poiché le parti MEF specificano in modo dichiarativo le relative funzionalità, sono individuabili in fase di esecuzione, il che significa che un'applicazione può usare parti senza riferimenti hardcoded o file di configurazione fragili. MEF consente alle applicazioni di individuare ed esaminare parti in base ai metadati, senza crearne un'istanza o persino caricando gli assembly. Di conseguenza, non è necessario specificare attentamente quando e come caricare le estensioni.
Oltre alle esportazioni fornite, una parte può definire le importazioni, che verranno soddisfatte da altre parti. Ciò rende la comunicazione tra le parti non solo possibile, ma semplice e consente un buon factoring del codice. Ad esempio, i servizi comuni a molti componenti possono essere inseriti in una parte separata e facilmente modificati o sostituiti.
Poiché il modello MEF non richiede alcuna dipendenza rigida da un particolare assembly dell'applicazione, consente il riutilizzo delle estensioni dall'applicazione all'applicazione. In questo modo è anche facile sviluppare un test harness, indipendentemente dall'applicazione, per testare i componenti dell'estensione.
Un'applicazione estendibile scritta tramite MEF dichiara un'importazione che può essere compilata dai componenti di estensione e può anche dichiarare esportazioni per esporre i servizi dell'applicazione alle estensioni. Ogni componente di estensione dichiara un'esportazione e può anche dichiarare importazioni. In questo modo, i componenti di estensione stessi sono estendibili automaticamente.
Dove MEF è disponibile
MEF è parte integrante di .NET Framework 4 ed è disponibile ovunque venga usato .NET Framework. È possibile usare MEF nelle applicazioni client, indipendentemente dal fatto che usino Windows Form, WPF o qualsiasi altra tecnologia o nelle applicazioni server che usano ASP.NET.
MEF e MAF
Le versioni precedenti di .NET Framework hanno introdotto il framework del componente aggiuntivo gestito (MAF), progettato per consentire alle applicazioni di isolare e gestire le estensioni. L'attenzione di MAF è leggermente più orientata rispetto a quella di MEF, concentrandosi sull'isolamento delle estensioni e sul caricamento e scaricamento degli assembly, mentre il focus di MEF è sull'individuabilità, estendibilità e portabilità. I due framework interagiscono senza problemi e una singola applicazione può sfruttare entrambi i vantaggi.
SimpleCalculator: un'applicazione di esempio
Il modo più semplice per vedere cosa può fare MEF consiste nel creare una semplice applicazione MEF. In questo esempio si crea una calcolatrice molto semplice denominata SimpleCalculator. L'obiettivo di SimpleCalculator è creare un'applicazione console che accetta comandi aritmetici di base, nel formato "5+3" o "6-2" e restituisce le risposte corrette. Con MEF sarà possibile aggiungere nuovi operatori senza modificare il codice dell'applicazione.
Per scaricare il codice completo per questo esempio, vedere l'esempio SimpleCalculator (Visual Basic).To download the complete code for this example, see the SimpleCalculator sample (Visual Basic).
Annotazioni
Lo scopo di SimpleCalculator è illustrare i concetti e la sintassi di MEF, invece di fornire necessariamente uno scenario realistico per il suo uso. Molte delle applicazioni che trarrebbero maggior vantaggio dalla potenza di MEF sono più complesse di SimpleCalculator. Per esempi più completi, vedere Managed Extensibility Framework in GitHub.
Per iniziare, in Visual Studio creare un nuovo progetto applicazione console e denominarlo
SimpleCalculator
.Aggiungere un riferimento all'assembly
System.ComponentModel.Composition
, in cui si trova MEF.Apri Module1.vb o Program.cs e aggiungi le direttive
Imports
ousing
perSystem.ComponentModel.Composition
eSystem.ComponentModel.Composition.Hosting
. Questi due namespace contengono tipi MEF necessari per sviluppare un'applicazione estendibile.Se si usa Visual Basic, aggiungere la
Public
parola chiave alla riga che dichiara ilModule1
modulo.
Contenitori di composizione e cataloghi
Il nucleo del modello di composizione MEF è il contenitore di composizione, che contiene tutte le parti disponibili ed esegue la composizione. La composizione è la corrispondenza tra le importazioni e le esportazioni. Il tipo più comune di contenitore di composizione è CompositionContainere verrà usato per SimpleCalculator.
Se si usa Visual Basic, aggiungere una classe pubblica denominata Program
in Module1.vb.
Aggiungere la riga seguente alla Program
classe in Module1.vb o Program.cs:
Dim _container As CompositionContainer
private CompositionContainer _container;
Per individuare le parti disponibili, i contenitori di composizione usano un catalogo. Un catalogo è un oggetto che rende disponibili le parti individuate da un'origine. MEF fornisce cataloghi per individuare parti da un tipo specificato, un assembly o una directory. Gli sviluppatori di applicazioni possono creare facilmente nuovi cataloghi per individuare parti da altre origini, ad esempio un servizio Web.
Aggiungere il costruttore seguente alla Program
classe :
Public Sub New()
' An aggregate catalog that combines multiple catalogs.
Dim catalog = New AggregateCatalog()
' Adds all the parts found in the same assembly as the Program class.
catalog.Catalogs.Add(New AssemblyCatalog(GetType(Program).Assembly))
' Create the CompositionContainer with the parts in the catalog.
_container = New CompositionContainer(catalog)
' Fill the imports of this object.
Try
_container.ComposeParts(Me)
Catch ex As CompositionException
Console.WriteLine(ex.ToString)
End Try
End Sub
private Program()
{
try
{
// An aggregate catalog that combines multiple catalogs.
var catalog = new AggregateCatalog();
// Adds all the parts found in the same assembly as the Program class.
catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
// Create the CompositionContainer with the parts in the catalog.
_container = new CompositionContainer(catalog);
_container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
Console.WriteLine(compositionException.ToString());
}
}
La chiamata a ComposeParts indica al contenitore di composizione di comporre un set specifico di parti, in questo caso l'istanza corrente di Program
. A questo punto, tuttavia, non accadrà nulla, poiché Program
non ha importazioni da riempire.
Importazioni ed esportazioni con attributi
Prima di tutto, è necessario Program
importare una calcolatrice. Ciò consente la separazione delle problematiche dell'interfaccia utente, ad esempio l'input e l'output della console che verranno inseriti in Program
, dalla logica del calcolatore.
Aggiungere il codice seguente alla classe Program
:
<Import(GetType(ICalculator))>
Public Property calculator As ICalculator
[Import(typeof(ICalculator))]
public ICalculator calculator;
Si noti che la dichiarazione dell'oggetto calculator
non è insolita, ma è decorata con l'attributo ImportAttribute . Questo attributo dichiara qualcosa da importare; ovvero, verrà riempito dal motore di composizione quando l'oggetto è composto.
Ogni importazione ha un contratto, che determina le esportazioni con cui verrà confrontata. Il contratto può essere una stringa specificata in modo esplicito oppure può essere generato automaticamente da MEF da un determinato tipo, in questo caso l'interfaccia ICalculator
. Tutte le esportazioni dichiarate con un contratto corrispondente soddisfano questa importazione. Si noti che, mentre il tipo dell'oggetto calculator
è in realtà ICalculator
, questo non è obbligatorio. Il contratto è indipendente dal tipo dell'oggetto di importazione. In questo caso, è possibile escludere l'oggetto typeof(ICalculator)
. MEF presupporrà automaticamente che il contratto sia basato sul tipo di importazione, a meno che non venga specificato in modo esplicito.
Aggiungere l'interfaccia molto semplice al modulo o allo spazio dei nomi SimpleCalculator
.
Public Interface ICalculator
Function Calculate(input As String) As String
End Interface
public interface ICalculator
{
string Calculate(string input);
}
Dopo aver definito ICalculator
, è necessaria una classe che la implementa. Aggiungere la classe seguente al modulo o allo spazio dei nomi SimpleCalculator
:
<Export(GetType(ICalculator))>
Public Class MySimpleCalculator
Implements ICalculator
End Class
[Export(typeof(ICalculator))]
class MySimpleCalculator : ICalculator
{
}
Ecco l'esportazione che corrisponderà all'importazione in Program
. Affinché l'esportazione corrisponda all'importazione, l'esportazione deve avere lo stesso contratto. L'esportazione in base a un contratto basato su typeof(MySimpleCalculator)
provocherebbe una mancata corrispondenza, e l'importazione non verrebbe completata; il contratto deve corrispondere esattamente.
Poiché il contenitore di composizione verrà popolato con tutte le parti disponibili in questo assieme, il componente MySimpleCalculator
sarà disponibile. Quando il costruttore per Program
esegue la composizione sull'oggetto Program
, l'importazione dell'oggetto verrà popolata con un oggetto MySimpleCalculator
, che verrà creato a tale scopo.
Il livello dell'interfaccia utente (Program
) non deve conoscere altro. È quindi possibile compilare il resto della logica dell'interfaccia utente nel Main
metodo .
Aggiungere al metodo Main
il codice seguente:
Sub Main()
' Composition is performed in the constructor.
Dim p As New Program()
Dim s As String
Console.WriteLine("Enter Command:")
While (True)
s = Console.ReadLine()
Console.WriteLine(p.calculator.Calculate(s))
End While
End Sub
static void Main(string[] args)
{
// Composition is performed in the constructor.
var p = new Program();
Console.WriteLine("Enter Command:");
while (true)
{
string s = Console.ReadLine();
Console.WriteLine(p.calculator.Calculate(s));
}
}
Questo codice legge semplicemente una riga di input e chiama la Calculate
funzione di ICalculator
sul risultato, che visualizza nella console. Questo è tutto il codice necessario in Program
. Tutto il resto del lavoro avverrà nelle sezioni.
Attributi Imports e ImportMany
Affinché SimpleCalculator sia estendibile, è necessario importare un elenco di operazioni. Un attributo ordinario ImportAttribute viene riempito da uno e un solo ExportAttribute. Se più di uno è disponibile, il motore di composizione genera un errore. Per creare un'importazione che può essere compilata da un numero qualsiasi di esportazioni, è possibile usare l'attributo ImportManyAttribute .
Aggiungere la proprietà operations seguente alla MySimpleCalculator
classe :
<ImportMany()>
Public Property operations As IEnumerable(Of Lazy(Of IOperation, IOperationData))
[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;
Lazy<T,TMetadata> è un tipo fornito da MEF per contenere riferimenti indiretti alle esportazioni. In questo caso, oltre all'oggetto esportato stesso, si ottengono anche metadati di esportazione o informazioni che descrivono l'oggetto esportato. Ogni Lazy<T,TMetadata> oggetto contiene un IOperation
oggetto che rappresenta un'operazione effettiva e un IOperationData
oggetto che ne rappresenta i metadati.
Aggiungi le seguenti interfacce semplici al modulo o allo spazio dei nomi SimpleCalculator
.
Public Interface IOperation
Function Operate(left As Integer, right As Integer) As Integer
End Interface
Public Interface IOperationData
ReadOnly Property Symbol As Char
End Interface
public interface IOperation
{
int Operate(int left, int right);
}
public interface IOperationData
{
char Symbol { get; }
}
In questo caso, i metadati per ogni operazione sono il simbolo che rappresenta tale operazione, ad esempio +, -, *e così via. Per rendere disponibile l'operazione di addizione, aggiungere la classe seguente al modulo o allo spazio dei nomi SimpleCalculator
.
<Export(GetType(IOperation))>
<ExportMetadata("Symbol", "+"c)>
Public Class Add
Implements IOperation
Public Function Operate(left As Integer, right As Integer) As Integer Implements IOperation.Operate
Return left + right
End Function
End Class
[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
class Add: IOperation
{
public int Operate(int left, int right)
{
return left + right;
}
}
L'attributo ExportAttribute funziona come in precedenza. L'attributo ExportMetadataAttribute associa i metadati, sotto forma di coppia nome-valore, a tale esportazione. Mentre la Add
classe implementa IOperation
, una classe che implementa IOperationData
non è definita in modo esplicito. Una classe viene invece creata in modo implicito da MEF con proprietà basate sui nomi dei metadati forniti. Si tratta di uno dei diversi modi per accedere ai metadati in MEF.
La composizione in MEF è ricorsiva. L'oggetto Program
è stato composto in modo esplicito, che ha importato un oggetto ICalculator
che risulta essere di tipo MySimpleCalculator
.
MySimpleCalculator
, a sua volta, importa una raccolta di oggetti IOperation
; tale importazione sarà completata quando MySimpleCalculator
verrà creato, contemporaneamente alle importazioni di Program
. Se la classe Add
dichiarasse un ulteriore import, anche quello dovrebbe essere gestito, e così via. Qualsiasi importazione lasciata non compilata genera un errore di composizione. È tuttavia possibile dichiarare le importazioni come facoltative o assegnarle valori predefiniti.
Logica calcolatrice
Con queste parti sul posto, tutto ciò che rimane è la logica della calcolatrice stessa. Aggiungere il codice seguente nella MySimpleCalculator
classe per implementare il Calculate
metodo :
Public Function Calculate(input As String) As String Implements ICalculator.Calculate
Dim left, right As Integer
Dim operation As Char
' Finds the operator.
Dim fn = FindFirstNonDigit(input)
If fn < 0 Then
Return "Could not parse command."
End If
operation = input(fn)
Try
' Separate out the operands.
left = Integer.Parse(input.Substring(0, fn))
right = Integer.Parse(input.Substring(fn + 1))
Catch ex As Exception
Return "Could not parse command."
End Try
For Each i As Lazy(Of IOperation, IOperationData) In operations
If i.Metadata.symbol = operation Then
Return i.Value.Operate(left, right).ToString()
End If
Next
Return "Operation not found!"
End Function
public String Calculate(string input)
{
int left;
int right;
char operation;
// Finds the operator.
int fn = FindFirstNonDigit(input);
if (fn < 0) return "Could not parse command.";
try
{
// Separate out the operands.
left = int.Parse(input.Substring(0, fn));
right = int.Parse(input.Substring(fn + 1));
}
catch
{
return "Could not parse command.";
}
operation = input[fn];
foreach (Lazy<IOperation, IOperationData> i in operations)
{
if (i.Metadata.Symbol.Equals(operation))
{
return i.Value.Operate(left, right).ToString();
}
}
return "Operation Not Found!";
}
I passaggi iniziali analizzano la stringa di input in operandi sinistro e destro e un carattere di operatore. Nel ciclo foreach
, viene esaminato ogni membro della raccolta operations
. Questi oggetti sono di tipo Lazy<T,TMetadata>e i relativi valori di metadati e gli oggetti esportati possono essere accessibili rispettivamente con la Metadata proprietà e la Value proprietà . In questo caso, se la Symbol
proprietà dell'oggetto IOperationData
viene individuata come corrispondenza, il calcolatore chiama il Operate
metodo dell'oggetto IOperation
e restituisce il risultato.
Per completare la calcolatrice, è necessario anche un metodo helper che restituisce la posizione del primo carattere non cifra in una stringa. Aggiungere il metodo helper seguente alla MySimpleCalculator
classe :
Private Function FindFirstNonDigit(s As String) As Integer
For i = 0 To s.Length - 1
If Not Char.IsDigit(s(i)) Then Return i
Next
Return -1
End Function
private int FindFirstNonDigit(string s)
{
for (int i = 0; i < s.Length; i++)
{
if (!char.IsDigit(s[i])) return i;
}
return -1;
}
A questo momento dovrebbe essere possibile compilare ed eseguire il progetto. In Visual Basic assicurati di aggiungere la Public
parola chiave a Module1
. Nella finestra della console digitare un'operazione di addizione, ad esempio "5+3" e il calcolatore restituisce i risultati. Qualsiasi altro operatore restituisce il messaggio "Operation Not Found!".
Estendere SimpleCalculator usando una nuova classe
Ora che il calcolatore funziona, l'aggiunta di una nuova operazione è semplice. Aggiungere la classe seguente al modulo o allo spazio dei nomi SimpleCalculator
:
<Export(GetType(IOperation))>
<ExportMetadata("Symbol", "-"c)>
Public Class Subtract
Implements IOperation
Public Function Operate(left As Integer, right As Integer) As Integer Implements IOperation.Operate
Return left - right
End Function
End Class
[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '-')]
class Subtract : IOperation
{
public int Operate(int left, int right)
{
return left - right;
}
}
Compilare ed eseguire il progetto. Inserire un'operazione di sottrazione, come "5-3". Il calcolatore supporta ora la sottrazione e l'aggiunta.
Estendere SimpleCalculator usando un nuovo assembly
L'aggiunta di classi al codice sorgente è abbastanza semplice, ma MEF offre la possibilità di cercare componenti al di fuori del codice sorgente di un'applicazione. Per dimostrare questo problema, è necessario modificare SimpleCalculator per cercare una directory, nonché il relativo assembly, per le parti, aggiungendo un oggetto DirectoryCatalog.
Aggiungere una nuova directory denominata Extensions
al progetto SimpleCalculator. Assicurarsi di aggiungerlo a livello di progetto e non a livello di soluzione. Aggiungere quindi un nuovo progetto libreria di classi alla soluzione, denominato ExtendedOperations
. Il nuovo progetto verrà compilato in un assembly separato.
Aprire il Progettazione delle Proprietà del Progetto per il progetto ExtendedOperations e fare clic sulla scheda Compila o Build. Modificare il percorso di output di compilazione o il percorso di uscita output in modo che punti alla directory Extensions nella directory del progetto SimpleCalculator (..\SimpleCalculator\Extensions\).
In Module1.vb o Program.cs aggiungere la riga seguente al Program
costruttore:
catalog.Catalogs.Add(
New DirectoryCatalog(
"C:\SimpleCalculator\SimpleCalculator\Extensions"))
catalog.Catalogs.Add(
new DirectoryCatalog(
"C:\\SimpleCalculator\\SimpleCalculator\\Extensions"));
Sostituire il percorso di esempio con il percorso della directory delle estensioni. Questo percorso assoluto è solo a scopo di debug. In un'applicazione di produzione si userà un percorso relativo. Ora DirectoryCatalog aggiungerà tutte le parti presenti in tutti gli assembly nella directory Extensions al contenitore di composizione.
ExtendedOperations
Nel progetto aggiungere riferimenti a SimpleCalculator
e System.ComponentModel.Composition
. Nel file classe ExtendedOperations
, aggiungere una direttiva Imports
o using
per System.ComponentModel.Composition
. In Visual Basic aggiungere anche un'istruzione Imports
per SimpleCalculator
. Aggiungere quindi la classe seguente al file di ExtendedOperations
classe:
<Export(GetType(SimpleCalculator.IOperation))>
<ExportMetadata("Symbol", "%"c)>
Public Class Modulo
Implements IOperation
Public Function Operate(left As Integer, right As Integer) As Integer Implements IOperation.Operate
Return left Mod right
End Function
End Class
[Export(typeof(SimpleCalculator.IOperation))]
[ExportMetadata("Symbol", '%')]
public class Mod : SimpleCalculator.IOperation
{
public int Operate(int left, int right)
{
return left % right;
}
}
Si noti che affinché il contratto corrisponda, l'attributo ExportAttribute deve avere lo stesso tipo di ImportAttribute.
Compilare ed eseguire il progetto. Prova il nuovo operatore Mod (%).
Conclusione
In questo argomento sono stati illustrati i concetti di base di MEF.
Parti, cataloghi e contenitore di composizione
Le parti e il contenitore di composizione sono i blocchi predefiniti di base di un'applicazione MEF. Una parte è qualsiasi oggetto che importa o esporta un valore, fino a includere se stesso. Un catalogo fornisce una raccolta di parti di una determinata origine. Il contenitore di composizione utilizza le parti fornite da un catalogo per effettuare la composizione, collegando le importazioni alle esportazioni.
Importazioni ed esportazioni
Le importazioni e le esportazioni sono il modo in cui i componenti comunicano. Con un'importazione, il componente specifica la necessità di un determinato valore o oggetto e con un'esportazione specifica la disponibilità di un valore. Ogni importazione viene confrontata con un elenco di esportazioni tramite il relativo contratto.
Passaggi successivi
Per scaricare il codice completo per questo esempio, vedere l'esempio SimpleCalculator (Visual Basic).To download the complete code for this example, see the SimpleCalculator sample (Visual Basic).
Per altre informazioni ed esempi di codice, vedere Managed Extensibility Framework. Per un elenco dei tipi MEF, vedere il namespace System.ComponentModel.Composition.