Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek obsahuje přehled rozhraní Managed Extensibility Framework, které bylo zavedeno v rozhraní .NET Framework 4.
Co je MEF?
Rozhraní MEF (Managed Extensibility Framework) je knihovna pro vytváření lehkých a rozšiřitelných aplikací. Umožňuje vývojářům aplikací zjišťovat a používat rozšíření bez nutnosti konfigurace. Umožňuje také vývojářům rozšíření snadno zapouzdřit kód a vyhnout se křehkým pevným závislostem. MEF umožňuje nejen opakované použití rozšíření v aplikacích, ale také napříč aplikacemi.
Problém rozšiřitelnosti
Představte si, že jste architektem velké aplikace, která musí poskytovat podporu rozšiřitelnosti. Vaše aplikace musí obsahovat potenciálně velký počet menších komponent a zodpovídá za jejich vytváření a spouštění.
Nejjednodušším přístupem k problému je zahrnout komponenty jako zdrojový kód do aplikace a volat je přímo z kódu. To má řadu zjevných nevýhod. Nejdůležitější je, že nemůžete přidávat nové komponenty beze změny zdrojového kódu, omezení, které může být přijatelné například ve webové aplikaci, ale není v klientské aplikaci možné. Stejně problematické je, že nemáte přístup ke zdrojovému kódu pro komponenty, protože mohou být vyvinuty třetími stranami a ze stejného důvodu jim nemůžete povolit přístup k vašim.
Trochu složitějším přístupem by bylo poskytnout rozšiřovací bod nebo rozhraní, které umožňuje oddělení mezi aplikací a jejími komponentami. V rámci tohoto modelu můžete poskytnout rozhraní, které může komponenta implementovat, a rozhraní API, které umožňuje interakci s vaší aplikací. Tím se vyřeší problém vyžadující přístup ke zdrojovému kódu, ale stále má své vlastní potíže.
Vzhledem k tomu, že aplikace nemá žádnou kapacitu pro zjišťování komponent samostatně, musí být stále explicitně řečeno, které komponenty jsou k dispozici a měly by být načteny. To se obvykle provádí explicitně registrací dostupných komponent v konfiguračním souboru. To znamená, že zajištění správnosti komponent se stává problémem údržby, zejména pokud se jedná o koncového uživatele, a ne vývojáře, který má aktualizaci provést.
Kromě toho komponenty nejsou schopny vzájemně komunikovat, s výjimkou pevně definovaných kanálů samotné aplikace. Pokud architekt aplikace neočekává potřebu konkrétní komunikace, obvykle není možné.
Vývojáři komponent musí nakonec přijmout pevnou závislost na tom, jaké sestavení obsahuje rozhraní, které implementují. To znesnadňuje použití komponenty ve více než jedné aplikaci a může také způsobit problémy při vytváření testovací architektury pro komponenty.
Co poskytuje MEF
Místo této explicitní registrace dostupných komponent poskytuje MEF způsob, jak je implicitně zjistit prostřednictvím složení. Komponenta MEF, která se nazývá část, deklarativní určuje jak její závislosti (označované jako importy), tak i možnosti (označované jako exporty), které zpřístupňuje. Při vytváření části splňuje modul složení MEF jeho importy s tím, co je k dispozici z jiných částí.
Tento přístup řeší problémy, které jsou popsány v předchozí části. Vzhledem k tomu, že části MEF deklarativním způsobem určují jejich možnosti, jsou zjistitelné za běhu, což znamená, že aplikace může využívat části bez pevně zakódovaných odkazů nebo křehkých konfiguračních souborů. MEF umožňuje aplikacím zjišťovat a zkoumat části podle jejich metadat, aniž by je instanciovaly nebo dokonce načetly jejich assembly. V důsledku toho není nutné přesně určovat, kdy a jak načítat rozšíření.
Kromě poskytnutého exportu může část určit jeho importy, které budou vyplněny jinými částmi. Díky tomu je komunikace mezi částmi nejen možná, ale také snadná a umožňuje dobré dělení kódu. Služby společné pro mnoho komponent je například možné začlenit do samostatné části a snadno upravit nebo nahradit.
Vzhledem k tomu, že model MEF nevyžaduje žádnou pevnou závislost na konkrétním sestavení aplikace, umožňuje opětovné použití rozšíření z aplikace do aplikace. To také usnadňuje vývoj testovacího svazku nezávisle na aplikaci a testování součástí rozšíření.
Rozšiřitelná aplikace napsaná pomocí funkce MEF deklaruje import, který lze vyplnit komponentami rozšíření, a může také deklarovat exporty za účelem zveřejnění aplikačních služeb rozšířením. Každá komponenta rozšíření deklaruje export a může také deklarovat importy. Tímto způsobem jsou samotné komponenty rozšíření automaticky rozšiřitelné.
Kde je k dispozici MEF
MEF je nedílnou součástí rozhraní .NET Framework 4 a je k dispozici všude, kde se používá rozhraní .NET Framework. MeF můžete použít v klientských aplikacích, ať už používají Windows Forms, WPF nebo jakoukoli jinou technologii, nebo v serverových aplikacích, které používají ASP.NET.
MEF a MAF
Předchozí verze rozhraní .NET Framework zavedly spravovanou architekturu doplňku (MAF), která umožňuje aplikacím izolovat a spravovat rozšíření. Zaměření MAF je na o něco vyšší úrovni než MEF, soustředí se na izolaci doplňků a nahrávání a rušení sestavení, zatímco MEF se zaměřuje na zjistitelnost, rozšiřitelnost a přenositelnost. Oba rámce spolupracují hladce a jedna aplikace může využívat obojí.
SimpleCalculator: Ukázková aplikace
Nejjednodušší způsob, jak zjistit, co může MEF udělat, je vytvořit jednoduchou aplikaci MEF. V tomto příkladu vytvoříte velmi jednoduchou kalkulačku s názvem SimpleCalculator. Cílem simpleCalculatoru je vytvořit konzolovou aplikaci, která přijímá základní aritmetické příkazy ve formě "5+3" nebo "6-2" a vrátí správné odpovědi. Pomocí MEF budete moct přidávat nové operátory beze změny kódu aplikace.
Pokud chcete stáhnout úplný kód pro tento příklad, podívejte se na ukázku SimpleCalculator (Visual Basic).
Poznámka:
Účelem simpleCalculatoru je předvést koncepty a syntaxi meF, nikoli nutně poskytnout realistický scénář pro jeho použití. Mnohé z aplikací, které by mohly těžit z výkonu MEF, jsou složitější než SimpleCalculator. Podrobnější příklady najdete v rozhraní Managed Extensibility Framework na GitHubu.
Začněte tak, že v sadě Visual Studio vytvoříte nový projekt konzolové aplikace a pojmenujte ho
SimpleCalculator
.Přidejte odkaz na
System.ComponentModel.Composition
sestavení, kde se nachází MEF.Otevřete Module1.vb nebo Program.cs a přidejte
Imports
nebousing
direktivy proSystem.ComponentModel.Composition
aSystem.ComponentModel.Composition.Hosting
. Tyto dva obory názvů obsahují typy MEF, které budete potřebovat k vývoji rozšiřitelné aplikace.Pokud používáte Visual Basic, přidejte
Public
klíčové slovo na řádek, který deklarujeModule1
modul.
Kompoziční kontejner a katalogy
Jádrem modelu složení MEF je kontejner složení, který obsahuje všechny dostupné části a provádí složení. Složení je porovnávání importů s exporty. Nejběžnějším typem kontejneru složení je CompositionContainera použijete ho pro SimpleCalculator.
Pokud používáte Visual Basic, přidejte veřejnou třídu s názvem Program
v Module1.vb.
Do třídy v Program
nebo Program.cs přidejte následující řádek:
Dim _container As CompositionContainer
private CompositionContainer _container;
Aby bylo možné zjistit, které části jsou k dispozici, kontejnery složení využívají katalog. Katalog je objekt, který z nějakého zdroje zjišťuje dostupné části. MEF poskytuje katalogy ke zjišťování částí z zadaného typu, sestavení nebo adresáře. Vývojáři aplikací můžou snadno vytvářet nové katalogy pro zjišťování částí z jiných zdrojů, jako je webová služba.
Do třídy přidejte následující konstruktor Program
:
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());
}
}
Příkaz ke ComposeParts říká kontejneru složení, aby sestavil konkrétní sadu částí, v tomto případě současná instance Program
. V tuto chvíli se ale nic nestane, protože Program
nemá žádné importy k vyplnění.
Import a export s atributy
Nejprve musíte Program
importovat kalkulačku. To umožňuje oddělení záležitostí uživatelského rozhraní, jako je vstup konzoly a výstup, které budou směrovány do Program
, od logiky kalkulačky.
Do třídy Program
přidejte následující kód:
<Import(GetType(ICalculator))>
Public Property calculator As ICalculator
[Import(typeof(ICalculator))]
public ICalculator calculator;
Všimněte si, že deklarace objektu calculator
není neobvyklá, ale že je zdobena atributem ImportAttribute . Tento atribut deklaruje atribut jako import; to znamená, že bude naplněn kompozičním motorem při skládání objektu.
Každý import má kontrakt, který určuje, s jakým exportem se bude shodovat. Kontrakt může být explicitně specifikovaný jako řetězec, nebo může být automaticky generován MEF z daného typu, v tomto případě jde o rozhraní ICalculator
. Jakýkoli export deklarovaný s odpovídající smlouvou tento import splní. Všimněte si, že zatímco typ objektu calculator
je ve skutečnosti ICalculator
, to není povinné. Kontrakt je nezávislý na typu importujícího objektu. (V tomto případě byste mohli vynechat typeof(ICalculator)
. MEF automaticky předpokládá, že kontrakt bude založen na typu importu, pokud ho explicitně nezadáte.)
Vložte toto jednoduché rozhraní do modulu či oboru názvů SimpleCalculator
.
Public Interface ICalculator
Function Calculate(input As String) As String
End Interface
public interface ICalculator
{
string Calculate(string input);
}
Teď, když jste definovali ICalculator
, potřebujete třídu, která ji implementuje. Do modulu nebo SimpleCalculator
oboru názvů přidejte následující třídu:
<Export(GetType(ICalculator))>
Public Class MySimpleCalculator
Implements ICalculator
End Class
[Export(typeof(ICalculator))]
class MySimpleCalculator : ICalculator
{
}
Zde je export, který bude odpovídat importu v Program
. Aby export odpovídal importu, musí mít export stejný kontrakt. Export na základě smlouvy pod typeof(MySimpleCalculator)
by vytvořil neshodu a import by nebyl vyplněn. Smlouva se musí shodovat přesně.
Vzhledem k tomu, že kontejner složení bude naplněn všemi částmi dostupnými v rámci tohoto sestavení, MySimpleCalculator
bude tato část k dispozici. Pokud konstruktor pro Program
provádí složení objektu Program
, jeho import bude naplněn objektem MySimpleCalculator
, který bude vytvořen právě za tímto účelem.
Vrstva uživatelského rozhraní (Program
) nemusí znát nic jiného. Proto můžete vyplnit zbytek logiky uživatelského rozhraní v Main
metodě.
Do metody Main
přidejte následující kód:
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));
}
}
Tento kód jednoduše přečte řádek vstupu a zavolá Calculate
funkci ICalculator
výsledku, kterou zapíše zpět do konzoly. To je veškerý kód, který potřebujete v Program
. Všechny ostatní práce budou probíhat v částech.
Import a ImportMany – atributy
Aby byl SimpleCalculator rozšiřitelný, musí importovat seznam operací. Běžný ImportAttribute atribut je vyplněn jedním a pouze jedním ExportAttribute. Pokud je k dispozici více než jeden, modul složení způsobí chybu. K vytvoření importu, který lze vyplnit libovolným počtem exportů, můžete použít ImportManyAttribute atribut.
Přidejte následující vlastnost operací do třídy MySimpleCalculator
.
<ImportMany()>
Public Property operations As IEnumerable(Of Lazy(Of IOperation, IOperationData))
[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;
Lazy<T,TMetadata> je typ, který poskytuje MEF pro uchovávání nepřímých odkazů na exporty. Kromě exportovaného objektu získáte také metadata exportu nebo informace popisované exportovaným objektem. Každý Lazy<T,TMetadata> obsahuje IOperation
objekt, který představuje skutečnou operaci a IOperationData
objekt představující jeho metadata.
Do modulu nebo SimpleCalculator
oboru názvů přidejte následující jednoduchá rozhraní:
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; }
}
V tomto případě jsou metadata pro každou operaci symbolem, který představuje tuto operaci, například +, -, *atd. Pokud chcete operaci sčítání zpřístupnit, přidejte do modulu nebo jmenného prostoru SimpleCalculator
následující třídu:
<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;
}
}
Atribut ExportAttribute funguje stejně jako předtím. Atribut ExportMetadataAttribute k exportu připojí metadata ve formě páru name-value.
Add
Zatímco třída implementuje IOperation
, třída, která implementuje IOperationData
není explicitně definována. Místo toho je třída implicitně vytvořená meF s vlastnostmi na základě názvů zadaných metadat. (Toto je jeden z několika způsobů přístupu k metadatům v MEF.)
Složení v MEF je rekurzivní. Explicitně jste složili Program
objekt, který importoval ICalculator
, což se ukázalo být typu MySimpleCalculator
.
MySimpleCalculator
importuje kolekci IOperation
objektů a tento import bude uskutečněn současně při vytváření MySimpleCalculator
, stejně jako importy Program
. Pokud třída Add
deklarovala další import, musela by být také vyplněna, a tak dále. Všechny nenaplněné importy způsobí chybu složení. (Je však možné deklarovat importy jako volitelné nebo jim přiřadit výchozí hodnoty.)
Logika kalkulačky
Když jsou tyto části na místě, vše, co zůstává, je logika kalkulačky samotná. Do třídy přidejte následující kód MySimpleCalculator
, který implementuje metodu Calculate
:
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!";
}
Počáteční kroky rozkládají vstupní řetězec na levý a pravý operand a znak operátoru.
foreach
Ve smyčce se zkoumá každý člen operations
kolekce. Tyto objekty jsou typu Lazy<T,TMetadata> a jejich hodnoty metadat a exportovaný objekt lze přistupovat pomocí vlastnosti Metadata a vlastnosti Value v uvedeném pořadí. V tomto případě, pokud je vlastnost objektu Symbol
shodná, kalkulačka zavolá metodu IOperationData
objektu Operate
a vrátí výsledek.
K dokončení kalkulačky potřebujete také pomocnou metodu, která vrátí pozici prvního neciferného znaku v řetězci. Do třídy přidejte následující pomocnou metodu MySimpleCalculator
:
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;
}
Teď byste měli být schopni projekt zkompilovat a spustit. V jazyce Visual Basic se ujistěte, že jste přidali klíčové slovo Public
do Module1
. V okně konzoly zadejte operaci sčítání, například 5+3, a kalkulačka vrátí výsledky. Výsledkem jakéhokoli jiného operátoru je zpráva „Operace nenalezena!“
Rozšíření SimpleCalculatoru pomocí nové třídy
Teď, když kalkulačka funguje, je přidání nové operace snadné. Do modulu nebo SimpleCalculator
oboru názvů přidejte následující třídu:
<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;
}
}
Zkompilujte a spusťte projekt. Zadejte operaci odčítání, například 5-3. Kalkulačka teď podporuje odčítání i sčítání.
Rozšíření SimpleCalculatoru pomocí nového sestavení
Přidání tříd do zdrojového kódu je dost jednoduché, ale MEF poskytuje možnost hledat mimo vlastní zdroj aplikace pro části. Chcete-li to ukázat, budete muset upravit SimpleCalculator tak, aby prohledával adresář i své vlastní sestavení, a hledal části přidáním prvku DirectoryCatalog.
Přidejte nový adresář s názvem Extensions
do projektu SimpleCalculator. Nezapomeňte ho přidat na úrovni projektu, a ne na úrovni řešení. Pak do řešení přidejte nový projekt knihovny tříd s názvem ExtendedOperations
. Nový projekt se zkompiluje do samostatného sestavení.
Otevřete Návrhář vlastností projektu pro projekt ExtendedOperations a klikněte na kartu Kompilace nebo Sestavení. Změňte výstupní cestu sestavení nebo výstupní cestu tak, aby směřovala do adresáře Extensions v adresáři projektu SimpleCalculator (..\SimpleCalculator\Extensions\).
Do Module1.vb nebo Program.cs přidejte do konstruktoru Program
následující řádek:
catalog.Catalogs.Add(
New DirectoryCatalog(
"C:\SimpleCalculator\SimpleCalculator\Extensions"))
catalog.Catalogs.Add(
new DirectoryCatalog(
"C:\\SimpleCalculator\\SimpleCalculator\\Extensions"));
Nahraďte ukázkovou cestu cestou k adresáři Extensions. (Tato absolutní cesta slouží pouze pro účely ladění. V produkční aplikaci byste použili relativní cestu.) Nyní DirectoryCatalog přidá všechny části nalezené v libovolném sestavení v adresáři Rozšíření do kontejneru složení.
ExtendedOperations
V projektu přidejte odkazy na SimpleCalculator
a System.ComponentModel.Composition
. V souboru třídy ExtendedOperations
přidejte direktivu Imports
nebo using
pro System.ComponentModel.Composition
. V jazyce Visual Basic přidejte také příkaz Imports
pro SimpleCalculator
. Pak do ExtendedOperations
souboru třídy přidejte následující třídu:
<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;
}
}
Mějte na paměti, že aby se smlouva shodovala, ExportAttribute atribut musí mít stejný typ jako ImportAttribute.
Zkompilujte a spusťte projekt. Otestujte nový operátor Mod (%).
Závěr
Toto téma se zabývalo základními koncepty MEF.
Díly, katalogy a kontejner složení
Části a kontejner složení jsou základními stavebními bloky aplikace MEF. Součástí se rozumí jakýkoli objekt, který importuje nebo exportuje hodnotu, včetně sebe sama. Katalog poskytuje kolekci částí z určitého zdroje. Kontejner kompozice používá části, které poskytuje katalog, k provádění kompozice, tedy k vazbě importů na exporty.
Dovoz a vývoz
Importy a exporty představují způsob, jakým komponenty komunikují. Při importu komponenta určuje potřebu konkrétní hodnoty nebo objektu a při exportu určuje dostupnost hodnoty. Každý import se shoduje se seznamem exportů na základě příslušné smlouvy.
Další kroky
Pokud chcete stáhnout úplný kód pro tento příklad, podívejte se na ukázku SimpleCalculator (Visual Basic).
Další informace a příklady kódu naleznete v tématu Managed Extensibility Framework. Seznam typů MEF najdete v oboru názvů System.ComponentModel.Composition.