这里简单来说,意思就是当在一个自定义LoadContext中加载程序集的时候,如果找不到这个程序集,程序会自动去默认LoadContext中查找,如果默认LoadContext中都找不到,就会返回null。
由此,我们之前的疑问就解决了,这里正是因为主站点已经加载了所需的程序集,虽然在插件的AssemblyLoadContext中找不到这个程序集,程序依然可以通过默认LoadContext来加载程序集。
那么是不是真的就没有问题了呢?其实我不是很推荐用以上的方式来加载第三方程序集。主要原因有两点
不同插件可以引用不同版本的第三方程序集,可能不同版本的第三方程序集实现不同。 而默认LoadContext只能加载一个版本,导致总有一个插件引用该程序集的功能失效。
默认LoadContext中可能加载的第三方程序集与其他插件都不同,导致其他插件功能引用该程序集的功能失效。
所以这里最正确的方式,还是放弃使用默认LoadContext加载程序集,保证每个插件的AssemblyLoadContext都完全加载所需的程序集。
那么如何加载这些第三方程序集呢?我们下面就来介绍两种方式
原始方式
使用插件缓存
原始方式原始方式比较暴力,我们可以选择加载插件程序集的同时,加载程序集所在目录中所有的dll文件。
这里首先我们创建了一个插件引用库加载器接口IReferenceLoader。
public interface IRefenerceLoader { public void LoadStreamsIntoContext(CollectibleAssemblyLoadContext context, string folderName, string excludeFile); }然后我们创建一个默认的插件引用库加载器DefaultReferenceLoader,其代码如下:
public class DefaultReferenceLoader : IRefenerceLoader { public void LoadStreamsIntoContext(CollectibleAssemblyLoadContext context, string folderName, string excludeFile) { var streams = new List<Stream>(); var di = new DirectoryInfo(folderName); var allReferences = di.GetFiles("*.dll").Where(p => p.Name != excludeFile); foreach (var file in allReferences) { using (var sr = new StreamReader(file.OpenRead())) { context.LoadFromStream(sr.BaseStream); } } } }代码解释
这里我是为了排除当前已经加载插件程序集,所以添加了一个excludeFile参数。
folderName即当前插件的所在目录,这里我们通过DirectoryInfo类的GetFiles方法,获取了当前指定folderName目录中的所有dll文件。
这里我依然通过文件流的方式加载了插件所需的第三方程序集。
完成以上代码之后,我们还需要修改启用插件的两部分代码
[MystiqueStartup.cs] - 程序启动时,注入IReferenceLoader服务,启用插件
[MvcModuleSetup.cs] - 在插件管理页面,触发启用插件操作
MystiqueStartup.cs
public static void MystiqueSetup(this IServiceCollection services, IConfiguration configuration) { ... services.AddSingleton<IReferenceLoader, DefaultReferenceLoader>(); var mvcBuilder = services.AddMvc(); var provider = services.BuildServiceProvider(); using (var scope = provider.CreateScope()) { ... foreach (var plugin in allEnabledPlugins) { var context = new CollectibleAssemblyLoadContext(); var moduleName = plugin.Name; var filePath = $"{AppDomain.CurrentDomain.BaseDirectory}Modules\\{moduleName}\\{moduleName}.dll"; var referenceFolderPath = $"{AppDomain.CurrentDomain.BaseDirectory}Modules\\{moduleName}"; _presets.Add(filePath); using (var fs = new FileStream(filePath, FileMode.Open)) { var assembly = context.LoadFromStream(fs); loader.LoadStreamsIntoContext(context, referenceFolderPath, $"{moduleName}.dll"); ... } } } ... }MvcModuleSetup.cs
public void EnableModule(string moduleName) { if (!PluginsLoadContexts.Any(moduleName)) { var context = new CollectibleAssemblyLoadContext(); var filePath = $"{AppDomain.CurrentDomain.BaseDirectory}Modules\\{moduleName}\\{moduleName}.dll"; var referenceFolderPath = $"{AppDomain.CurrentDomain.BaseDirectory}Modules\\{moduleName}"; using (var fs = new FileStream(filePath, FileMode.Open)) { var assembly = context.LoadFromStream(fs); _referenceLoader.LoadStreamsIntoContext(context, referenceFolderPath, $"{moduleName}.dll"); ... } } else { var context = PluginsLoadContexts.GetContext(moduleName); var controllerAssemblyPart = new MystiqueAssemblyPart(context.Assemblies.First()); _partManager.ApplicationParts.Add(controllerAssemblyPart); } ResetControllActions(); }现在我们重新运行之前的项目,并访问插件1的路由,你会发现页面正常显示了,并且页面内容也是从DemoReferenceLibrary程序集中加载出来了。
使用插件缓存