笔者在 Asp.Net MVC 插件化开发简化方案 研究了基于 .NET Framework 的 ASP.NET 插件化开发之后,又在 ASP.NET Core 2.0 下进行插件化开发 中研究了基于 ASP.NET Core 2.0 的插件化开发,以及在 ASP.NET Core 2.0 中基于 Razor Page 的插件化开发。不过最近在基于 .NET Core 2.1 的插件化开发时遇到个新问题:
ASP.NET Core 2.1 可以将视图编译在动态库中,生成一个 proj.views.dll 这样的动态库,发布时就不需要再发布 Views 目录了。然而使用上述插件化方法,即使 .views.dll 正确的拷贝到目标目录,甚至 Shadow Copy 和 Assembly 加载都没有问题的情况下,运行时仍然要在 Views 目录下去查找视图文件。
虽然可以像 2.0 版本那样拷贝 Views 目录达到正常运行的效果,但是既然拷贝所有 .dll 就能解决的问题,谁还愿意再去多拷贝一个 Views 呢?
笔者查阅了大量资料之后,总算找到了问题的根源:.views.dll 不能采用默认的加载方式,而必须使用 CompiledRazorAssemblyApplicationPartFactory 来加载。CompiledRazorAssemblyApplicationPartFactory 可以将 Assebmly 加载成 ApplicationPart,再添加到 ApplicationPartManager 中去。因此,需要在 IMvcBuilder.ConfigureApplicationPartManager() 中来配置处理(参阅:Stack Overflow 上的 ASP.NET Core MVC 2.1 mvc Views in plugin)
不过插件化框架中,为了解耦平台和插件,插件 Assembly 是动态搜索并加载的,并不能直接写硬代码。这在之前的博客中也曾提到,需要通过 Startup 中 Configure() 和 ConfigureServices() 配合,并通过一个 mvcBuilder 成员变量来处理。加载仍然要在 LoadPlugins() 中进行(参阅:ASP.NET Core 2.0 下进行插件化开发),而 ConfigureApplicationPartManager() 也需要搬到 LoadPlugins() 中去:
private void LoadPlugins(IHostringEnvironment env)
{
// 这里是之前进行 Shadow Copy 的代码
// ……
// 接下来需要把 dll 按是否 `.views.dll` 来分别处理
// 从 Shadow Copy 目录加载 Assembly 并注册到 Mvc 中
var groups = Directory.EnumerateFiles(target, “*.dll”)
.GroupBy(path => path.EndsWith(“.views.dll”, StringComparison.OrdinalIgnoreCase))
.ToDictionary(group => group.Key);
// 非 .views.dll 直接加载到为 ApplicationPart
groups[false]
.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath)
.ForEach(mvcBuilder.AddApplicationPart);
// .views.dll 需要通过 CompiledRazorAssemblyApplicationPartFactory 来加载
mvcBuilder.ConfigureApplicationPartManager(manager =>
{
var razorPartFactory = new CompiledRazorAssemblyApplicationPartFactory();
groups[true]
.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath)
.SelectMany(assembly => razorPartFactory.GetApplicationParts(assembly))
.ForEach(manager.ApplicationParts.Add);
});
}
相关代码在 Gitee 上:aspnet-mvc-plugin-sample/asp.net_core_22