Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье представлен обзор управляемой платформы расширяемости, которая появилась в .NET Framework 4.
Что такое MEF?
Платформа управляемого расширения (MEF) — это библиотека для создания упрощенных и расширяемых приложений. Это позволяет разработчикам приложений обнаруживать и использовать расширения без необходимости настройки. Он также позволяет разработчикам расширений легко инкапсулировать код и избегать хрупких жестких зависимостей. MEF не только позволяет повторно использовать расширения в приложениях, но и между приложениями.
Проблема расширяемости
Представьте, что вы являетесь архитектором большого приложения, которое должно обеспечить поддержку расширяемости. Приложение может включать потенциально большое количество небольших компонентов и отвечает за их создание и запуск.
Самый простой подход к проблеме заключается в том, чтобы включить компоненты в качестве исходного кода в приложение и вызвать их непосредственно из кода. Это имеет ряд очевидных недостатков. Самое главное, нельзя добавлять новые компоненты, не изменяя исходный код, ограничение, которое может быть приемлемым, например, веб-приложением, но не работает в клиентском приложении. В равной степени проблематично, у вас может не быть доступа к исходному коду для компонентов, так как они могут быть разработаны сторонними лицами, и по той же причине вы не можете разрешить им доступ к вашим компонентам.
Немного более сложный подход заключается в предоставлении точки расширения или интерфейса, чтобы разрешить разделение между приложением и его компонентами. В этой модели можно предоставить интерфейс, который может реализовать компонент, и API для его взаимодействия с приложением. Это решает проблему, требующую доступа к исходному коду, но она по-прежнему имеет свои собственные трудности.
Так как приложение не имеет возможности обнаруживать компоненты самостоятельно, должно однозначно указываться, какие компоненты доступны и должны быть загружены. Обычно это достигается путем явной регистрации доступных компонентов в файле конфигурации. Это означает, что обеспечение правильности компонентов становится проблемой обслуживания, особенно если это конечный пользователь, а не разработчик, который, как ожидается, будет выполнять обновление.
Кроме того, компоненты не могут взаимодействовать друг с другом, за исключением жестко определенных каналов самого приложения. Если архитектор приложения не ожидал необходимости определенного взаимодействия, обычно это невозможно.
Наконец, разработчики компонентов должны принимать жесткую зависимость от того, какая сборка содержит интерфейс, который они реализуют. Это затрудняет использование компонента в нескольких приложениях, а также может создавать проблемы при создании тестовой платформы для компонентов.
Что предоставляет MEF
Вместо явной регистрации доступных компонентов MEF предоставляет способ их обнаружения неявно с помощью композиции. Компонент MEF, называемый частью, декларативно указывает как его зависимости (известные как импорт), так и какие возможности (известные как экспорты) он предоставляет. При создании части подсистема композиции MEF удовлетворяет его импорту тем, что доступно из других частей.
Этот подход решает проблемы, описанные в предыдущем разделе. Так как компоненты MEF декларативно указывают их возможности, они обнаруживаются во время выполнения, что означает, что приложение может использовать части без жестко закодированных ссылок или хрупких файлов конфигурации. MEF позволяет приложениям обнаруживать и проверять части по их метаданным, не создавая их экземпляры или даже загружая их сборки. В результате нет необходимости тщательно указывать, когда и как следует загружать расширения.
Помимо предоставленных экспортов, часть может указать свои импорты, которые будут заполнены другими частями. Это делает обмен данными между частями не только возможными, но и простыми и позволяет хорошо учитывать факторы кода. Например, службы, общие для многих компонентов, могут быть вынесены в отдельную часть и легко изменены или заменены.
Так как модель MEF не требует жесткой зависимости от конкретной сборки приложения, она позволяет повторно использовать расширения из приложения в приложение. Это также упрощает разработку тестовой инфраструктуры, независимой от приложения, для тестирования расширяемых компонентов.
Расширяемое приложение, написанное с помощью MEF, объявляет импорт, который может быть заполнен компонентами расширения, а также может объявлять экспорт для предоставления служб приложений расширениям. Каждый компонент расширения объявляет экспорт, а также может объявлять импорт. Таким образом, сами компоненты расширения автоматически расширяемы.
Где доступен MEF
MEF доступна в .NET Framework 4 и более поздних версиях, а также в .NET 5 и более поздних версиях. MeF можно использовать в клиентских приложениях, независимо от того, используются ли они Windows Forms, WPF или любой другой технологии или в серверных приложениях, использующих #REF!.
MEF и MAF
В предыдущих версиях платформы .NET Framework появилась управляемая надстройка (MAF), предназначенная для изоляции расширений и управления ими. Фокус MAF немного выше, чем MEF, концентрируясь на изоляции расширений и загрузке сборок и выгрузке, в то время как основное внимание MEF уделяется обнаружению, расширяемости и переносимости. Две платформы взаимодействуют плавно, и одно приложение может воспользоваться преимуществами обоих.
SimpleCalculator: пример приложения
Самый простой способ увидеть, что может сделать MEF, заключается в создании простого приложения MEF. В этом примере вы создадите очень простой калькулятор с именем SimpleCalculator. Цель SimpleCalculator — создать консольное приложение, которое принимает основные арифметические команды в форме "5+3" или "6-2" и возвращает правильные ответы. С помощью MEF вы сможете добавлять новые операторы, не изменяя код приложения.
Чтобы скачать полный код для этого примера, см. пример SimpleCalculator (Visual Basic).
Замечание
Цель SimpleCalculator заключается в демонстрации концепций и синтаксиса MEF, а не для обязательного предоставления реалистичного сценария его использования. Многие из приложений, которые выиграют больше всего от возможностей MEF, являются более сложными, чем SimpleCalculator. Более подробные примеры см. в Managed Extensibility Framework на #REF!.
Чтобы начать, в Visual Studio создайте проект консольного приложения и назовите его
SimpleCalculator.Добавьте ссылку на сборку , в которой находится MEF.
Откройте Module1.vb или Program.cs и добавьте или директивы для и . Эти два пространства имен содержат типы MEF, необходимые для разработки расширяемого приложения.
Если вы используете Visual Basic, добавьте ключевое слово
Publicв строку, которая объявляет модульModule1.
Контейнер композиции и каталоги
Основой модели композиции MEF является контейнер композиции, содержащий все доступные части и выполняющий композицию. Композиция — это соотнесение импорта с экспортом. Наиболее распространенный тип контейнера композиции — это , и его вы будете использовать для SimpleCalculator.
Если вы используете Visual Basic, добавьте открытый класс с именем Program в Module1.vb.
Добавьте следующую строку в класс в Module1.vb или Program.cs:
Dim _container As CompositionContainer
private CompositionContainer _container;
Чтобы обнаружить доступные ему части, контейнеры композиции используют каталог компонентов. Каталог — это объект, который делает доступные части, обнаруженные из какого-то источника. MEF предоставляет каталоги для обнаружения частей из предоставленного типа, сборки или каталога. Разработчики приложений могут легко создавать новые каталоги для обнаружения частей из других источников, таких как веб-служба.
Добавьте следующий конструктор в класс:
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());
}
}
Вызов инструктирует контейнер композиции составить определенный набор частей, а именно текущий экземпляр . Однако на этом этапе ничего не произойдет, так как не имеет импорта для заполнения.
Импорт и экспорт с атрибутами
Во-первых, импортируйте калькулятор. Это позволяет отделить аспекты пользовательского интерфейса, такие как ввод и вывод данных консоли, которые будут направлены в , от логики калькулятора.
Добавьте в класс следующий код.
<Import(GetType(ICalculator))>
Public Property calculator As ICalculator
[Import(typeof(ICalculator))]
public ICalculator calculator;
Обратите внимание, что объявление объекта не является необычным, но оно украшено атрибутом . Этот атрибут объявляет элемент как импортируемый; т. е. он будет заполняться компонентом компоновки при создании объекта.
У каждого импорта есть контракт, который определяет, с каким экспортом он будет соответствовать. Контракт может быть явно указанной строкой или автоматически генерироваться MEF из заданного типа, в данном случае из интерфейса . Любой экспорт, объявленный с соответствующим контрактом, будет удовлетворять этот импорт. Обратите внимание, что, хотя тип объекта действительно , это не требуется. Контракт не зависит от типа импорта объекта. (В этом случае можно исключить . MEF автоматически предполагает, что контракт будет основан на типе импорта, если только вы не указали его явным образом.)
Добавьте этот очень простой интерфейс в модуль или пространство имен:
Public Interface ICalculator
Function Calculate(input As String) As String
End Interface
public interface ICalculator
{
string Calculate(string input);
}
Теперь, когда вы определили , вам нужен класс, реализующий его. Добавьте следующий класс в модуль или пространство имен:
<Export(GetType(ICalculator))>
Public Class MySimpleCalculator
Implements ICalculator
End Class
[Export(typeof(ICalculator))]
class MySimpleCalculator : ICalculator
{
}
Ниже приведен экспорт, соответствующий импорту . Чтобы экспорт соответствовал импорту, экспорт должен иметь тот же контракт. Экспорт по контракту на основе , приведет к несоответствию, и импорт не будет выполнен; контракт должен полностью соответствовать.
Так как контейнер для композиции будет заполнен всеми частями, которые доступны в этой сборке, часть также будет доступна. Когда конструктор выполняет композицию над объектом , его импорт будет заполнен объектом , который будет создан специально для этой цели.
Уровень пользовательского интерфейса () не должен знать ничего другого. Поэтому можно заполнить остальную часть логики пользовательского интерфейса в методе .
Добавьте следующий код в метод :
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));
}
}
Этот код просто считывает строку входных данных и вызывает функцию на результате , после чего выводит его обратно в консоль. Вот весь код, который вам нужен в . Все остальные действия будут выполняться в частях.
Атрибуты Imports и ImportMany
Чтобы simpleCalculator был расширяемым, необходимо импортировать список операций. Обычный атрибут заполняется одним и только одним . Если доступно более одного, подсистема композиции выдает ошибку. Чтобы создать импорт, который можно заполнить любым количеством экспортов, можно использовать атрибут.
Добавьте следующее свойство operations в класс .
<ImportMany()>
Public Property operations As IEnumerable(Of Lazy(Of IOperation, IOperationData))
[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;
— это тип, предоставляемый MEF для хранения косвенных ссылок на экспорт. Здесь, помимо экспортированного объекта, вы также получаете метаданные экспорта или сведения, описывающие экспортируемый объект. Каждый содержит объект, представляющий фактическую операцию и объект, представляющий его метаданные.
Добавьте в модуль или пространство имен следующие простые интерфейсы:
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; }
}
В этом случае метаданные для каждой операции — это символ, представляющий эту операцию, например +, -, *и т. д. Чтобы сделать операцию добавления доступной, добавьте следующий класс в модуль или пространство имен:
<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;
}
}
Атрибут функционирует так же, как и раньше. Атрибут присоединяет метаданные в виде пары "имя-значение" к экспорту. Класс реализует, но класс, который реализует, не определён явно. Класс создается MEF неявно с использованием свойств, основанных на именах предоставленных метаданных. (Это один из нескольких способов доступа к метаданным в MEF.)
Композиция в MEF рекурсивна. Вы явно создали объект , который импортировал , оказавшийся объектом типа . , в свою очередь, импортирует коллекцию объектов, и этот импорт будет заполнен при создании , одновременно с импортом . Если класс объявил дальнейший импорт, это тоже должно быть заполнено и т. д. Любой импорт, оставшийся без заполнения, приводит к ошибке композиции. (Однако можно объявить импорт необязательным или назначить значения по умолчанию.)
Логика калькулятора
Остается только логика калькулятора. Добавьте следующий код в класс для реализации метода:
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!";
}
Начальные шаги анализируют входную строку в левой и правой операндах и символ оператора. В цикле проверяется каждый элемент коллекции. Эти объекты имеют тип , и их значения метаданных, а также экспортируемый объект можно получить с помощью свойства и свойства соответственно. В этом случае, если свойство объекта совпадает, калькулятор вызывает метод объекта и возвращает результат.
Для завершения калькулятора также требуется вспомогательный метод, который возвращает позицию первого незначного символа в строке. Добавьте в класс следующий вспомогательный метод:
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;
}
Теперь вы сможете скомпилировать и запустить проект. В Visual Basic убедитесь, что вы добавили ключевое слово Public для Module1. В окне консоли введите операцию сложения, например "5+3", и калькулятор возвращает результаты. Любой другой оператор вызывает сообщение "Операция не найдена!".
Расширение 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;
}
}
Скомпилируйте и запустите проект. Введите операцию вычитания, например "5-3". Теперь калькулятор поддерживает вычитание, а также добавление.
Расширьте SimpleCalculator с использованием новой сборки
Добавление классов в исходный код достаточно просто, но MEF предоставляет возможность искать компоненты вне собственного исходного кода приложения. Чтобы продемонстрировать это, необходимо изменить SimpleCalculator таким образом, чтобы он искал части как в каталоге, так и в собственной сборке, добавив элемент .
Добавьте новый каталог с именем в проект SimpleCalculator. Не забудьте добавить его на уровне проекта, а не на уровне решения. Затем добавьте новый проект библиотеки классов в решение с именем . Новый проект будет компилироваться в отдельную сборку.
Откройте конструктор свойств проекта для проекта ExtendedOperations и перейдите на вкладку "Компиляция или сборка". Измените выходной путь сборки или путь вывода, чтобы указать каталог extensions в каталоге проекта SimpleCalculator (.). \SimpleCalculator\Extensions\).
В Module1.vb или Program.cs добавьте следующую строку в конструктор:
catalog.Catalogs.Add(
New DirectoryCatalog(
"C:\SimpleCalculator\SimpleCalculator\Extensions"))
catalog.Catalogs.Add(
new DirectoryCatalog(
"C:\\SimpleCalculator\\SimpleCalculator\\Extensions"));
Замените пример пути на путь к каталогу Extensions. (Этот абсолютный путь предназначен только для отладки. В рабочем приложении используется относительный путь.) Теперь все части, найденные в любом каталоге расширений, будут добавлены в контейнер композиции.
В проекте добавьте ссылки на и . В файле класса добавьте директиву или для . В Visual Basic также добавьте оператор Imports для SimpleCalculator. Затем добавьте следующий класс в файл класса:
<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;
}
}
Обратите внимание, что для сопоставления контракта атрибут должен иметь тот же тип, что и .
Скомпилируйте и запустите проект. Проверьте новый оператор Mod (%) .
Conclusion
В этом разделе рассматриваются основные понятия MEF.
Детали, каталоги и композиционный контейнер
Части и контейнер композиции являются основными стандартными блоками приложения MEF. Часть — это любой объект, который импортирует или экспортирует значение, вплоть до самого себя. Каталог предоставляет коллекцию частей из определенного источника. Контейнер композиции использует части, предоставляемые каталогом, для осуществления композиции, то есть привязки импорта к экспорту.
Импорт и экспорт
Импорт и экспорт — это способ взаимодействия компонентов. При импорте компонент указывает потребность в определенном значении или объекте, а при экспорте он указывает доступность значения. Каждый импорт сопоставляется со списком экспортов в соответствии с его контрактом.
Дальнейшие шаги
Чтобы скачать полный код для этого примера, см. пример SimpleCalculator (Visual Basic).
Дополнительные сведения и примеры кода см. в разделе Managed Extensibility Framework. Список типов MEF см. в пространстве имен.