Sdílet prostřednictvím


Managed Extensibility Framework (MEF, Rámec pro spravovanou rozšiřitelnost)

Tento článek obsahuje přehled rozhraní Managed Extensibility Framework, které bylo zavedeno v rozhraní technologie .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í určují jejich schopnosti, 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 k dispozici v technologie .NET Framework 4 a novějších verzích a ve technologie .NET 5 a novějších verzích. MeF můžete použít v klientských aplikacích, ať už používají model Windows Forms, WPF (Windows Presentation Foundation) nebo jakoukoli jinou technologii, nebo v serverových aplikacích, které používají ASP.NET.

MEF a MAF

Předchozí verze technologie .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.

Úplný kód pro tento příklad si můžete stáhnout v ukázce 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 GitHub Spravovaná architektura rozšiřitelnosti.

  • Začněte tak, že v Visual Studio vytvoříte nový projekt konzolové aplikace a pojmenujte ho SimpleCalculator.

  • Přidejte odkaz na sestavení, kde se nachází MEF.

  • Otevřete Module1.vb nebo Program.cs a přidejte nebo direktivy pro a . 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 na řádek, který deklaruje modul Public, klíčové slovo Module1.

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 a 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 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 :

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 říká kontejneru složení, aby sestavil konkrétní sadu částí, v tomto případě současná instance . V tuto chvíli se ale nic nestane, protože nemá žádné importy k vyplnění.

Import a export s atributy

Nejprve musíte 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 , od logiky kalkulačky.

Do třídy 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 není neobvyklá, ale že je zdobena atributem . 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í . Jakýkoli export deklarovaný s odpovídající smlouvou tento import splní. Všimněte si, že zatímco typ objektu je ve skutečnosti , to není povinné. Kontrakt je nezávislý na typu importujícího objektu. (V tomto případě byste mohli vynechat . 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ů .

Public Interface ICalculator
    Function Calculate(input As String) As String
End Interface
public interface ICalculator
{
    string Calculate(string input);
}

Teď, když jste definovali , potřebujete třídu, která ji implementuje. Do modulu nebo 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 . Aby export odpovídal importu, musí mít export stejný kontrakt. Export na základě smlouvy pod 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í, bude tato část k dispozici. Pokud konstruktor pro provádí složení objektu , jeho import bude naplněn objektem , který bude vytvořen právě za tímto účelem.

Vrstva uživatelského rozhraní () nemusí znát nic jiného. Proto můžete vyplnit zbytek logiky uživatelského rozhraní v metodě.

Do metody 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á funkci výsledku, kterou zapíše zpět do konzoly. To je veškerý kód, který potřebujete v . 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ý atribut je vyplněn jedním a pouze jedním . 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 atribut.

Přidejte následující vlastnost operací do třídy .

<ImportMany()>
Public Property operations As IEnumerable(Of Lazy(Of IOperation, IOperationData))
[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;

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ý obsahuje objekt, který představuje skutečnou operaci a objekt představující jeho metadata.

Do modulu nebo 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 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 funguje stejně jako předtím. Atribut k exportu připojí metadata ve formě páru name-value. Zatímco třída implementuje , třída, která implementuje 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 objekt, který importoval , což se ukázalo být typu . importuje kolekci objektů a tento import bude uskutečněn současně při vytváření , stejně jako importy . Pokud třída 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 , který implementuje metodu :

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. Ve smyčce se zkoumá každý člen kolekce. Tyto objekty jsou typu a jejich hodnoty metadat a exportovaný objekt lze přistupovat pomocí vlastnosti a vlastnosti v uvedeném pořadí. V tomto případě, pokud je vlastnost objektu shodná, kalkulačka zavolá metodu objektu 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 :

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 Visual Basic nezapomeňte do Public přidat klíčové slovo 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 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 .

Přidejte nový adresář s názvem 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 . 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 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í přidá všechny části nalezené v libovolném sestavení v adresáři Rozšíření do kontejneru složení.

V projektu přidejte odkazy na a . V souboru třídy přidejte direktivu nebo pro . V Visual Basic přidejte také příkaz Imports pro SimpleCalculator. Pak do 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, atribut musí mít stejný typ jako .

Zkompilujte a spusťte projekt. Otestujte nový operátor Mod (%).

Conclusion

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

Úplný kód pro tento příklad si můžete stáhnout v ukázce 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ů .