从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用 (2)

这里简单来说,意思就是当在一个自定义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程序集中加载出来了。

从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用

使用插件缓存

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyydjd.html