- 从 Github 下载源代码 - 961 KB
从3.0版开始,ASP.NET Core提供了一种使用Application Parts将应用程序拆分为模块的方法。
一个解决方案可能包含一个Web应用程序和任意数量的程序集库,其中可能包含控制器、视图、页面、静态文件(如 JavaScript 和 CSS 文件)等。这些库称为Razor 类库或 RCL。
有人希望在解决方案中使用Razor库的原因有很多。
但最有价值的情况是动态加载库时,作为插件。想象一个电子商务解决方案,它提供了许多税收或运费计算插件或付款插件,供管理员选择。
不过也有一些困难。可以肯定的是,该文档未能提供完整的描述性示例。
但最令人沮丧的是,Application Parts和RCL似乎不是为了与动态加载的库(即插件)一起使用而创建的。
特别是对于静态文件,即JavaScript和CSS文件,动态加载的RCL是失败的。
本练习的内容在本文中,我们将研究这两个用例:
- 主应用程序静态引用的RCL
- 由主应用程序动态加载的RCL
两个RCL都包含静态文件,即JavaSript和CSS文件。
我们将使用一个ASP.NET Core MVC Web应用程序和两个RCL。
首先,创建一个ASP.NET Core MVC Web应用程序并将其命名为WebApp.
引用RCL按照文档提供的说明创建RCL 。
命名RCL StaticRCL。我们稍后会看到为什么这个名字很重要。
从项目中删除所有文件和文件夹,并添加三个新文件夹:Controllers、Views和wwwroot。
在Controllers文件夹中创建一个控制器类。
public class LibController : Controller
{
[Route("/static")]
public IActionResult Index()
{
return View();
}
}
在Views文件夹中创建一个Lib文件夹。添加一个Index.cshtml视图文件。
STATICALLY referenced Razor Class Library
Click Me!
在wwwroot文件夹内创建一个js文件夹。添加一个script.js文件。
function StaticRCL_ShowMessage() {
alert('Hi from Statically refernced Razor Class Library javascript');
}
可动态加载的RCL
使用与上述类似的结构和文件创建另一个RCL。命名为DynamicRCL。
控制器
public class LibDynamicController : Controller
{
[Route("/dynamic")]
public IActionResult Index()
{
return View();
}
}
DYNAMICALLY loaded Razor Class Library
Click Me!
function DynamicRCL_ShowMessage() {
alert('Hi from Dynamically loaded Razor Class Library javascript');
}
我们还需要做到以下几点。
- 在项目的程序集名称中添加一个rcl_前缀,即rcl_DynamicRCL
- 添加一个GenerateEmbeddedFilesManifest,即true
- 添加Microsoft.Extensions.FileProviders.Embedded NuGet包,即,
- 设置输出路径到主Web Application的bin文件夹,即..\WebApp\bin\Debug\
- 指示项目使用wwwroot文件夹中的所有文件作为嵌入资源,即,
这是整个项目的源文件:
netcoreapp3.1
true
rcl_DynamicRCL
true
true
..\WebApp\bin\Debug\
WebApp Web应用程序应该有一个对第一个RCL的project reference,即StaticRCL。
HomeController的Index.cshtml如下:
@{
ViewData["Title"] = "Home Page";
}
Welcome
Statically referenced Razor Class Library View
Dynamically loaded Razor Class Library View
如您所见,有两个锚元素调用相应的RCL路由。
Startup类的ConfigureServices()方法处理静态引用和动态加载的库。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().
ConfigureApplicationPartManager((PartManager) => {
ConfigureStaticLibraries(PartManager); // static RCLs
LoadDynamicLibraries(PartManager); // dynamic RCLs
});
}
ApplicationPartManager类管理部件和ASP.NET MVC的核心或Razor Pages应用程序的功能。
其逻辑是获取对已经被Web应用程序引用的Assembly的引用,为该Assembly创建一个AssemblyPart,然后调用ApplicationPartManager注册该AssemblyPart。
void ConfigureStaticLibraries(ApplicationPartManager PartManager)
{
Assembly Assembly = typeof(StaticRCL.Controllers.LibController).Assembly;
ApplicationPart ApplicationPart = new AssemblyPart(Assembly);
PartManager.ApplicationParts.Add(ApplicationPart);
}
以上适用于路由到Razor视图(和Razor页面)。随着扭曲,当涉及到静态文件,如JavaScript和CSS文件等。
以下是文档中的内容:
RCL的wwwroot文件夹中包含的文件在前缀_content/{LIBRARY NAME}/下暴露给RCL或消费应用程序。例如,名为Razor.Class.Lib的库会生成_content/Razor.Class.Lib/处的静态内容路径。
以下是StaticRCL项目的Index.cshtml所做的,符合上述。
根据相关文档,需要AssemblyLoadContext类的派生类才能加载插件库。
这里是:
public class LibraryLoadContext: AssemblyLoadContext
{
private AssemblyDependencyResolver fResolver;
public LibraryLoadContext(string BinFolder)
{
fResolver = new AssemblyDependencyResolver(BinFolder);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = fResolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string FilePath = fResolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (FilePath != null)
{
return LoadUnmanagedDllFromPath(FilePath);
}
return IntPtr.Zero;
}
}
我们在加载插件库时使用LibraryLoadContext。
下面从ConfigureServices()调用的LoadDynamicLibraries()基于前缀(在本例中是rcl_)加载库,即插件程序集。这就是为什么我们将DynamicRCL项目的程序集名称更改为rcl_DynamicRCL的原因。
我希望代码易于理解。
void LoadDynamicLibraries(ApplicationPartManager PartManager)
{
// get the output folder of this application
string BinFolder = this.GetType().Assembly.ManifestModule.FullyQualifiedName;
BinFolder = Path.GetDirectoryName(BinFolder);
// get the full filepath of any dll starting with the rcl_ prefix
string Prefix = "rcl_";
string SearchPattern = $"{Prefix}*.dll";
string[] LibraryPaths = Directory.GetFiles(BinFolder, SearchPattern);
if (LibraryPaths != null && LibraryPaths.Length > 0)
{
// create the load context
LibraryLoadContext LoadContext = new LibraryLoadContext(BinFolder);
Assembly Assembly;
ApplicationPart ApplicationPart;
foreach (string LibraryPath in LibraryPaths)
{
// load each assembly using its filepath
Assembly = LoadContext.LoadFromAssemblyPath(LibraryPath);
// create an application part for that assembly
ApplicationPart = LibraryPath.EndsWith(".Views.dll") ?
new CompiledRazorAssemblyPart(Assembly)
as ApplicationPart : new AssemblyPart(Assembly);
// register the application part
PartManager.ApplicationParts.Add(ApplicationPart);
// if it is NOT the *.Views.dll add it to a list for later use
if (!LibraryPath.EndsWith(".Views.dll"))
DynamicallyLoadedLibraries.Add(Assembly);
}
}
}
现在是棘手的部分。
我们已经将JavaScript、CSS 和其他static资源配置为嵌入到DynamicRCL。此外,我们要求该库为这些嵌入文件创建一个清单。
现在我们必须读取该清单,在该DynamicRCL程序集及其wwwroot文件夹上创建一个IFileProvider,然后向系统注册该文件提供程序。
void RegisterDynamicLibariesStaticFiles(IWebHostEnvironment env)
{
IFileProvider FileProvider;
foreach (Assembly A in DynamicallyLoadedLibraries)
{
// create a "web root" file provider for the embedded static files
// found on wwwroot folder
FileProvider = new ManifestEmbeddedFileProvider(A, "wwwroot");
// register a new composite provider containing
// the old web root file provider
// and the new one we just created
env.WebRootFileProvider = new CompositeFileProvider
(env.WebRootFileProvider, FileProvider);
}
}
上面的方法在app.UseStaticFiles()调用之前被Startup类的Configure()方法调用。
app.UseHttpsRedirection();
// register file providers for the dynamically loaded libraries
if (DynamicallyLoadedLibraries.Count > 0)
RegisterDynamicLibariesStaticFiles(env);
app.UseStaticFiles();
下面是DynamicRCL项目的Index.cshtml为了使用JavaScript文件所做的事情。
这里没有使用_content/{LIBRARY NAME}/方案。我们只使用该js文件夹,因为我们已将DynamicRCL程序集的wwwroot文件夹注册为Web 根文件夹。
就这样。
测试于:
- Windows 10
- ASP.NET Core 3.1
- Microsoft Visual Studio 2019 Preview, Version 16.9.0 Preview 5.0
https://www.codeproject.com/Articles/5296270/ASP-NET-Core-3-x-Dynamically-Loadable-Plugins-with