在当今的软件开发中,灵活性和动态性是应用开发的重要需求。然而,许多应用程序在更改后需要重启,这无疑是对时间的极大浪费。为了解决这一问题,进行了一项简短的市场调查,以了解在.Net环境下实现插件架构的三种方式:MEF、MAF和反射。但是,这些方法都没有完全满足需求。
MEF(Managed Extensibility Framework)虽然提供了插件加载的功能,但它阻止了在运行时更改DLL文件,并且所有DLL都在同一个AppDomain中加载,如果其中一个插件抛出未处理的异常,整个应用程序可能会崩溃。MAF(Managed Addin Framework)要求插件文件夹具有预定义的结构,并且在插件实现方面实现复杂。反射虽然是一个低级别的实现方式,但不想重新发明轮子。
该框架基于FileSystemWatcher、AppDomain和MEF技术,主要特点包括:
框架基本上监控指定的文件夹及其所有子文件夹上的创建/更改/重命名/删除事件。当发生“创建”事件时,框架会创建一个单独的AppDomain,并将插件加载到该域中。为了避免阻塞文件,AppDomain会创建一个创建文件夹的阴影副本。当文件夹或其根文件发生变化/重命名/删除时,框架会删除旧的AppDomain并创建一个新的,其中包含插件的新实例。因此,每个文件夹将有一个AppDomain。唯一的限制是,从插件传递到宿主应用程序的对象和反之亦然的对象应该是MarshalByRefObject。这是因为对象需要通过AppDomain边界传递。
首先需要定义一个合约,这是宿主应用程序和插件之间共享的接口。它应该在共享程序集中定义:
public interface IPlugin : IDisposable
{
string Name { get; }
string SayHelloTo(string personName);
}
倾向于每个项目实现一个插件,但并没有限制实现多个插件。
using Contracts;
using System;
using System.ComponentModel.Composition;
namespace Plugin1
{
[Export(typeof(IPlugin))]
public class Plugin : BasePlugin
{
public Plugin() : base("Plugin1")
{
Console.WriteLine("ctor_{0}", Name);
}
public override string SayHelloTo(string personName)
{
string hello = string.Format("Hello {0} from {1}.", personName, Name);
return hello;
}
public override void Dispose()
{
Console.WriteLine("dispose_{0}", Name);
}
}
}
以控制台应用程序为例,需要添加以下引用:
using Contracts;
using Mark42;
using System;
using System.Collections.Generic;
using System.IO;
namespace TestConsole
{
class Program
{
static void Main(string[] args)
{
var pluginsFolderPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins");
PluginService<IPlugin> pluginService = new PluginService<IPlugin>(pluginsFolderPath, "*.dll", true);
pluginService.PluginsAdded += pluginService_PluginAdded;
pluginService.PluginsChanged += pluginService_PluginChanged;
pluginService.PluginsRemoved += pluginService_PluginRemoved;
pluginService.Start();
Console.ReadKey();
pluginService.Stop();
}
#region Event handlers
private static void pluginService_PluginRemoved(PluginService<IPlugin> sender, List<IPlugin> plugins)
{
foreach (var plugin in plugins)
{
Console.WriteLine("PluginRemoved: {0}.", plugin.Name);
plugin.Dispose();
}
}
private static void pluginService_PluginChanged(PluginService<IPlugin> sender, List<IPlugin> oldPlugins, List<IPlugin> newPlugins)
{
Console.WriteLine("PluginChanged: {0} plugins -> {1} plugins.", oldPlugins.Count, newPlugins.Count);
foreach (var plugin in oldPlugins)
{
Console.WriteLine("~removed: {0}.", plugin.Name);
plugin.Dispose();
}
foreach (var plugin in newPlugins)
{
Console.WriteLine("~added: {0}.", plugin.Name);
}
}
private static void pluginService_PluginAdded(PluginService<IPlugin> sender, List<IPlugin> plugins)
{
foreach (var plugin in plugins)
{
Console.WriteLine("PluginAdded: {0}.", plugin.Name);
Console.WriteLine(plugin.SayHelloTo("Tony Stark"));
}
}
#endregion
}
}
[Export(typeof(IPlugin))]
public class Plugin : BasePlugin
{
[ImportingConstructor]
public Plugin(CustomParameters parameters) : base("Plugin1")
{
Console.WriteLine("ctor_{0}", Name);
}
public override string SayHelloTo(string personName)
{
string hello = string.Format("Hello {0} from {1}.", personName, Name);
return hello;
}
public override void Dispose()
{
Console.WriteLine("dispose_{0}", Name);
}
}