Graeme_Grant
Как рекомендовал Йоханнес Нестлер, мне нравится использовать Меф[^] для таких приложений, как эти.
Это будет длинный ответ, но держитесь, это того стоит...
Я сделал для вас пару примеров, чтобы у вас было представление о том, как вы могли бы сделать это с помощью MEF:
1. консольное решение - это демонстрирует, как может работать MEF;
2. В WPF приложение с модулями - это расширяет на примере консоли с автозагрузкой модулей
Во-первых, вот простой пример консольного приложения, который показывает, как работает MEF:
internal static class Program
{
private static void Main()
{
Mef.Initialize();
var process = new Process();
process.Service?.Print();
Console.ReadKey();
}
}
class Process
{
public Process()
{
Mef.ComposeParts(this);
}
[Import]
public DummyService Service { get; set; }
}
[Export]
class DummyService
{
public void Print()
{
Console.WriteLine("Running Service...");
}
}
Меф делает всю тяжелую работу за вас.
[Export]
имеет соответствующий
Import
и разве это волшебство, когда ты просишь его об этом:
Mef.ComposeParts(this);
. Вот (сокращенный) класс, который я использую для обертывания MEF:
public static class Mef
{
private static object lockObj = new object();
private static CompositionContainer container;
private static AggregateCatalog catalog;
public static void Initialize()
{
catalog = new AggregateCatalog();
container = new CompositionContainer(catalog);
}
public static void ComposeParts(params object[] attributedParts)
{
lock (lockObj)
container.ComposeParts(attributedParts);
}
}
То
Initialize()
метод сшивает все импортные и экспортные операции вместе.
Теперь, для плагинов WPF и погрузки/показывать элементы управления UserControl, немного больше работы требуется. Раствор состоит из 3 частей:
1. Общий плагин contact Dll
2. основное приложение WPF
3. модули, содержащие элементы управления пользователя
Общий плагин состоит из:
1. The
MEF
вспомогательный класс
2. А
UIProvider
- метаданные, описывающие плагин, используемый основным приложением
3. В
IView
интерфейс - используется для привязки UserControl к UIProvider
4. В
ExportView
атрибут - для именования уникальных экспортируемых IViews
Пересмотренная версия
MEF
класс, который находит модули плагинов и загружает их в реальном времени:
public static class Mef
{
private static object lockObj = new object();
private static CompositionContainer container;
private static AggregateCatalog catalog;
private static void Initialize()
{
catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.exe"));
catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.dll"));
container = new CompositionContainer(catalog);
}
public static void Initialize(bool? isRecomposable = true)
{
Initialize();
if (isRecomposable == true)
StartWatch();
}
public static void Initialize<T>(T attributedPart, bool isRecomposable)
{
Initialize(isRecomposable);
if (isRecomposable)
ComposeParts(attributedPart);
else
lock (lockObj)
container.SatisfyImportsOnce(attributedPart);
}
public static void ComposeParts(params object[] attributedParts)
{
lock (lockObj)
container.ComposeParts(attributedParts);
}
private static void StartWatch()
{
var watcher = new FileSystemWatcher() { Path = ".", NotifyFilter = NotifyFilters.LastWrite };
watcher.Changed += (s, e) =>
{
string lName = e.Name.ToLower();
if (lName.EndsWith(".dll") || lName.EndsWith(".exe"))
Refresh();
};
watcher.EnableRaisingEvents = true;
}
public static void Refresh()
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
foreach (DirectoryCatalog dCatalog in catalog.Catalogs)
dCatalog.Refresh();
});
}
}
// Helper class for dispatcher operations on the UI thread...
// (Abridged version)
// full version: https://github.com/lbugnion/mvvmlight/blob/master/GalaSoft.MvvmLight/GalaSoft.MvvmLight.Platform%20(NET45)/Threading/DispatcherHelper.cs
public static class DispatcherHelper
{
public static Dispatcher UIDispatcher
{
get;
private set;
}
public static void CheckBeginInvokeOnUI(Action action)
{
if (action == null)
return;
CheckDispatcher();
if (UIDispatcher.CheckAccess())
action();
else
UIDispatcher.BeginInvoke(action);
}
private static void CheckDispatcher()
{
if (UIDispatcher == null)
{
var error = new StringBuilder("The DispatcherHelper is not initialized.");
error.AppendLine();
error.Append("Call DispatcherHelper.Initialize() in the static App constructor.");
throw new InvalidOperationException(error.ToString());
}
}
public static void Initialize()
{
if (UIDispatcher != null
&& UIDispatcher.Thread.IsAlive)
return;
UIDispatcher = Dispatcher.CurrentDispatcher;
}
}
То
DispatcherHelper
используется для того, чтобы избежать каких-либо проблем с перекрестным тредингом.
Теперь базовый UIProvider. Это позволяет использовать несколько типов, таких как
UserControl
и
Page
типы и описание каждого плагина для хостинга приложения:
public interface IUIProviderBase
{
string Key { get; }
string Title { get; }
}
public abstract class UIProviderBase : IUIProviderBase
{
public string Key { get; set; }
public string Title { get; set; }
}
И основание
UIViewProviderBase
реализация метода представления пользовательского элемента управления, интерфейс, маркер
IView
, и обычай
Export
атрибут для
UserControl
:
public interface IUIViewProviderBase : IUIProviderBase
{
ExportFactory<IView> Entry { get; set; }
}
public abstract class UIViewProviderBase : UIProviderBase, IUIViewProviderBase
{
public abstract ExportFactory<IView> Entry { get; set; }
}
public interface IView
{
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ExportViewAttribute : ExportAttribute
{
public ExportViewAttribute(string contractName)
: base(contractName, typeof(IView))
{
}
}
Теперь мы можем создавать автономные модули плагинов DLL/EXE. Модуль плагина должен ссылаться на
Общий плагин contact Dll выше. Библиотека DLL должна быть либо типом проекта WPF UserControl, либо типом проекта Wpf Custom Control.
Вот один пример плагина. Сделайте столько, сколько вы хотите проверить...
Первый плагин UIProvider, чтобы описать плагин с фабричный метод для представления (пользовательского элемента управления):
[Export(typeof(IUIViewProviderBase))]
public class UIProvider : UIViewProviderBase
{
public UIProvider()
{
Key = "PluginA";
Title = "My Plugin A";
}
[Import("MyViewA")]
public override ExportFactory<IView> Entry { get; set; }
}
Теперь вид плагина (пользовательский элемент управления) сама:
[ExportView("MyViewA")]
public partial class View : UserControl, IView
{
public View()
{
InitializeComponent();
}
}
А вот макет XAML для целей тестирования:
<UserControl x:Class="PluginA.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PluginA"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<TextBlock Text="ViewA"/>
</Viewbox>
</UserControl>
Наконец, хост-приложение WPF. Мне нравится использовать шаблон проектирования MVVM, но код позади него может работать так же хорошо. Убедитесь, что вы также добавили ссылку на
Общий плагин contact Dll выше.
Сначала ViewModel для размещения плагинов для основного вида/окна:
public class MainViewModel : IPartImportsSatisfiedNotification
{
public MainViewModel()
{
Mef.Initialize(this, isRecomposable: true);
}
[ImportMany(typeof(IUIViewProviderBase), AllowRecomposition = true)]
public ObservableCollection<IUIViewProviderBase> Plugins { get; set; }
public void OnImportsSatisfied()
{
// uncomment if you need to do something when plugin(s) is/are loaded
//Debugger.Break();
}
}
Нам нужен ...
UIExportViewConverter
класс для загрузки представления плагина (UserControl)
class UIExportViewConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((ExportFactory<IView>) value).CreateExport().Value;
}
public object ConvertBack(object value, Type targetType, obj