从零开始实现ASP.NET Core MVC的插件式开发(八) - Razor视图相关问题及解决方案 (2)

这说明默认AssemblyLoadContext中的程序集正常加载了,只是和视图中需要的类型不匹配,所以此处也可以说明Razor视图的运行时编译使用的是默认AssemblyLoadContext

Notes: 这个场景在前几篇中遇到过,在不同AssemblyLoadContext加载相同的程序集,系统会将严格的将他们区分开,插件1中的AssemblyPart引用是插件1所在AssemblyLoadContext中的DemoPlugin1.Models.TestClass类型,这与默认AssemblyLoadContext中加载的DemoPlugin1.Models.TestClass不符。

在之前系列文章中,我介绍过两次,在ASP.NET Core的设计文档中,针对AssemblyLoadContext部分的是这样设计的

每个ASP.NET Core程序启动后,都会创建出一个唯一的默认AssemblyLoadContext

开发人员可以自定义AssemblyLoadContext, 当在自定义AssemblyLoadContext加载某个程序集的时候,如果在当前自定义的AssemlyLoadContext中找不到该程序集,系统会尝试在默认AssemblyLoadContext中加载。

从零开始实现ASP.NET Core MVC的插件式开发(八) - Razor视图相关问题及解决方案

但是这种程序集加载流程只是单向的,如果默认AssemblyLoadContext未加载某个程序集,但某个自定义AssemblyLoadContext中加载了该程序集,你是不能从默认AssemblyLoadContext中加载到这个程序集的。

这也就是我们现在遇到的问题,如果你有兴趣的话,可以去Review一下ASP.NET Core的针对RuntimeCompilation源码部分,你会发现当ASP.NET Core的Razor视图引擎会使用Roslyn来编译视图,这里直接使用了默认的AssemblyLoadContext加载视图所需的程序集引用。

绿线是我们期望的加载方式,红线是实际的加载方式

从零开始实现ASP.NET Core MVC的插件式开发(八) - Razor视图相关问题及解决方案

为什么不直接用默认AssemblyLoadContext来加载插件?

可能会有同学问,为什么不用默认的AssemblyLoadContext来加载插件,这里有2个主要原因。

首先如果都使用默认的AssemblyLoadContext来加载插件,当不同插件使用了两个不同版本、相同名称的程序集时, 程序加载会出错,因为一个AssemblyLoadContext不能加载不同版本,相同名称的程序集,所以在之前我们才设计成了这种使用自定义程序集加载不同插件的方式。

从零开始实现ASP.NET Core MVC的插件式开发(八) - Razor视图相关问题及解决方案

其次如果都是用默认的AssemblyLoadContext来加载插件,插件的卸载和升级会变成一个大问题,但是如果我们使用自定义AssemblyLoadContext的加载插件,当升级和卸载插件时,我们可以毫不犹豫的Unload当前的自定义AssemblyLoadContext。

临时的解决方案

既然不能使用默认AssemblyLoadContext来加载程序集了,那么是不是只能重写Razor视图运行时编译代码来满足当前需求呢?

答案当然是否定了,这里我们可以通过AssemblyLoadContext提供的Resolving事件来解决这个问题。

AssemblyLoadContext的Resolving事件是在当前AssemblyLoadContext不能加载指定程序集时触发的。所以当Razor引擎执行运行时视图编译的时候,如果在默认AssemblyLoadContext中找不到某个程序集,我们可以强制让它去自定义的AssemblyLoadContext中查找,如果能找到,就直接返回匹配的程序。这样我们的插件1视图就可以正常展示了。

public static void MystiqueSetup(this IServiceCollection services, IConfiguration configuration) { ... AssemblyLoadContext.Default.Resolving += (context, assembly) => { Func<CollectibleAssemblyLoadContext, bool> filter = p => p.Assemblies.Any(p => p.GetName().Name == assembly.Name && p.GetName().Version == assembly.Version); if (PluginsLoadContexts.All().Any(filter)) { var ass = PluginsLoadContexts.All().First(filter) .Assemblies.First(p => p.GetName().Name == assembly.Name && p.GetName().Version == assembly.Version); return ass; } return null; }; ... }

Note: 这里其实还有一个问题,如果插件1和插件2都引用了相同版本和名称的程序集,可能会出现插件1的视图匹配到插件2中程序集的问题,就会出现和前面一样的程序集冲突。这块最终的解决肯定还是要重写Razor的运行时编译代码,后续如果能完成这部分,再来更新。

临时的解决方案是,当一个相同版本和名称的程序集被2个插件共同使用时,我们可以使用默认AssemblyLoadContext来加载,并跳过自定义AssemblyLoadContext针对该程序集的加载。

现在我们重新启动项目,访问插件1路由,页面正常显示了。

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

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