我正在尝试使用MEF来促进插件或插件式架构。我之前以非常类似的方式使用结构图完成了这个,但是我看到了与结构图相同的问题,我必须在app.config中添加探测目录配置或者设置AssemblyResolve事件处理程序处理动态调用代码的插件。
以下是示例MEF代码(只是一个小实验室应用程序),它显示了我如何从指定的接口调用方法:
namespace MefLab
{
class Program
{
static void Main(string[] args)
{
MefLoader loader = new MefLoader();
Type type = typeof(IConsoleWriter);
Lazy<object, object> export = loader.Container.GetExports(type, null, null).SingleOrDefault();
var writer = (IConsoleWriter)export.Value;
writer.Write();
Console.ReadKey();
}
}
public class MefLoader
{
public MefLoader()
{
var registrationBuilder = new RegistrationBuilder();
registrationBuilder.ForTypesDerivedFrom<IConsoleWriter>().SetCreationPolicy(CreationPolicy.NonShared).Export<IConsoleWriter>();
var aggregateCatalog = new AggregateCatalog();
var mefLabInfraCatalog = new AssemblyCatalog(typeof(IConsoleWriter).Assembly);
aggregateCatalog.Catalogs.Add(mefLabInfraCatalog);
foreach (string pluginDir in PluginDirctories)
{
DirectoryCatalog dirCatalog = new DirectoryCatalog(pluginDir, registrationBuilder);
aggregateCatalog.Catalogs.Add(dirCatalog);
}
var coreCatalog = new AssemblyCatalog(typeof(MefLoader).Assembly);
aggregateCatalog.Catalogs.Add(coreCatalog);
Container = new CompositionContainer(aggregateCatalog);
// used if no probing path is used in app.config
// AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
public CompositionContainer Container { get; set; }
private List<String> _pluginDirectories;
private List<String> PluginDirctories
{
get
{
if (_pluginDirectories == null)
{
_pluginDirectories = new List<string>();
DirectoryInfo di = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Plugins"));
foreach (DirectoryInfo dInfo in di.GetDirectories())
{
_pluginDirectories.Add(dInfo.FullName);
}
}
return _pluginDirectories;
}
}
private List<Assembly> _assemblies;
private List<Assembly> Assemblies
{
get
{
if (_assemblies == null)
{
_assemblies = new List<Assembly>();
foreach (string pluginDir in PluginDirctories)
{
DirectoryInfo di = new DirectoryInfo(pluginDir);
foreach (FileInfo fi in di.GetFiles("*.dll"))
{
Assembly assembly = Assembly.LoadFile(fi.FullName);
//assembly.GetTypes();
_assemblies.Add(assembly);
}
}
DirectoryInfo root = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)));
foreach (FileInfo fi in root.GetFiles("*.dll"))
{
Assembly assembly = Assembly.LoadFile(fi.FullName);
//assembly.GetTypes();
_assemblies.Add(assembly);
}
foreach (FileInfo fi in root.GetFiles("*.exe"))
{
Assembly assembly = Assembly.LoadFile(fi.FullName);
//assembly.GetTypes();
_assemblies.Add(assembly);
}
}
return _assemblies;
}
}
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Assembly resolved = Assemblies.Where(x => x.FullName == args.Name).FirstOrDefault();
return resolved;
}
}
}
当我说一个动态调用代码的插件时,我正在谈论这个。
namespace MefLabDependency.Managers
{
public class EntityConsoleManager : IConsoleWriter
{
public void Write()
{
var domProvider = new CSharpCodeProvider(new Dictionary<String, String>{{ "CompilerVersion", "v4.0" }});
List<string> assemblies = (from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
where !assembly.IsDynamic && !string.IsNullOrWhiteSpace(assembly.Location) select assembly.Location).ToList();
var parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = true;
parameters.CompilerOptions = "/optimize";
parameters.TreatWarningsAsErrors = false;
parameters.ReferencedAssemblies.AddRange(assemblies.ToArray());
CompilerResults results = domProvider.CompileAssemblyFromSource(parameters,
@"using System;
using MefLabDependency.Entities;
namespace MefLabDependency.DynamicCode
{
public class EntityBuilder {
public static string GetValue() {
Entity entity = new Entity();
var value = string.Format(@""{2}EntityId: {0}{2}Name: {1}\"", entity.Id, entity.Name, System.Environment.NewLine);
return value;
}
}
}");
Assembly dynamicAssembly = results.CompiledAssembly;
Type dynamicType = dynamicAssembly.GetType("MefLabDependency.DynamicCode.EntityBuilder");
// this line bombs out if no probing path or assembly resolver event is configured with "Exception has been thrown by the target of an invocation."
// that ends up specifying that is can't reslove that assembly the type of Entity is located in.
object objValue = dynamicType.InvokeMember("GetValue", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, null);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Dynamic code execution success!{0}", objValue);
}
}
}
我的问题是......还有其他办法吗?! (例如更好的方法)使用探测配置工作正常,但每次添加插件时,都需要在配置中添加探测目录,如果可能的话,这是我想避免的。
这是我所说的探测配置:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Plugins\MefDependencyPlugin"/>
</assemblyBinding>
</runtime>
添加AssemblyResolve事件处理程序也有效,但它似乎是hackish,我不确定它在生产中是如何工作的(我不知道每个程序集正在做什么,它实现了一个接口和方法的事实是从插件的结构上调用。)
如果这些是这个问题的唯一解决方案,那就这样吧,但我认为堆栈的集体智慧可能能够提供另一个建议。也许除了MEF或StructureMap之外还有一个更好的框架可以在没有配置或事件处理程序的情况下处理它?</ p>