AspNetCore-3x-RCL-plugins
kelvin 发布于 2021-03-16
介绍
Asp.NetCore从3.0版开始,提供了一种通过使用应用程序部件将应用程序拆分为模块的方法。
一个解决方案可以由一个Web应用程序和任意数量的程序集库组成,这些程序集库可以包含控制器、视图、页面、静态文件(比如javascript和css文件)等等。这些库被称为Razor类库或RCL。
为什么有人想在解决方案中使用Razor库,有很多原因。
但最有价值的情况是当库作为插件动态加载时。设想一个电子商务解决方案提供了许多税务或运费计算插件或支付插件,供管理员选择。
但也有一些困难。可以肯定的是,文件未能提供完整的描述性例子和样本。
但最令人沮丧的是,似乎创建应用程序部件和rcl并不是为了与动态加载的库(即插件)一起使用。
特别是对于静态文件,即javasript和css文件,动态加载的RCLs是一个失败。

这个练习的内容
在本文中,我们将研究这两个用例:
主应用程序静态引用的RCL
由主应用程序动态加载的RCL。
两个rcl都包含静态文件,即javasript和css文件。
我们将使用Asp.Net核心MVC Web应用程序和两个rcl。
为开始创建Asp.Net核心MVC Web应用程序,并将其命名为WebApp。


参考RCL
按照文档提供的说明创建RCL。
将RCL命名为StaticRCL。我们稍后再看为什么名字很重要。
从项目中删除所有文件和文件夹,并添加三个新文件夹、Controller、Views和wwwroot。
在Controllers文件夹中创建控制器类。

 public class LibController : Controller
 {
        [Route("/static")]
        public IActionResult Index()
        {
            return View();
        }
 }

在Views文件夹中创建Lib文件夹。添加Index.cshtml查看文件。

 <script src="~/_content/StaticRCL/js/script.js"></script>

    <div>
        <strong>STATICALLY</strong> referenced Razor Class Library
    </div>

    <div>
        <button onclick="StaticRCL_ShowMessage();">Click Me!</button>
    </div>
在wwwroot文件夹中创建一个js文件夹。添加script.js文件。

 function StaticRCL_ShowMessage() {
        alert('Hi from Statically refernced Razor Class Library javascript');
    }
一种动态可加载的RCL
创建另一个具有类似结构和文件的RCL,如上所示。命名为DynamicCrcl。


Controller

public class LibDynamicController : Controller
{
        [Route("/dynamic")]
        public IActionResult Index()
        {
            return View();
        }
}
View.

 <script src="js/script.js"></script>

<div>
    <strong>DYNAMICALLY</strong> loaded Razor Class Library
</div>

<div>
    <button onclick="DynamicRCL_ShowMessage();">Click Me!</button>
</div>
Javascript 文件

function DynamicRCL_ShowMessage() {
    alert('Hi from Dynamically loaded Razor Class Library javascript');
}
我们还需要做以下工作。

在项目的程序集名称中添加rcl_ prefix,即<AssemblyName>rcl_DynamicRCL</AssemblyName>
添加generateembeddedfilesmifest,即<generateembeddedfilesmifest>true</generateembeddedfilesmifest>
添加Microsoft.Extensions.FileProviders.Embedded NuGet包,即<PackageReference Include='Microsoft.Extensions.FileProviders.Embedded“版本=“3.1.0”/>
将输出路径设置为主Web应用程序的bin文件夹,即<OutputPath>。\WebApp\bin\Debug\</OutputPath>
指示项目将wwwroot文件夹中的所有文件用作嵌入资源,即<EmbeddedResource Include=“wwwroot\**\*”/>
这是整个项目的源文件。
<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
    <AssemblyName>rcl_DynamicRCL</AssemblyName>
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>     
    <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <OutputPath>..\WebApp\bin\Debug\</OutputPath>
  </PropertyGroup>

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.0" />
  </ItemGroup> 
    
   <ItemGroup>
      <EmbeddedResource Include="wwwroot\**\*" />
   </ItemGroup>

</Project>
处理参考RCL

webappweb应用程序应该有一个对第一个RCL(StaticRCL)的项目引用。
这个Index.cshtml HomeController的功能如下所示。
@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <div><a href="/static">Statically referenced Razor Class Library View</a></div>
    <div><a href="/dynamic">Dynamically loaded Razor Class Library View</a></div>
</div>
如您所见,有两个锚元素调用相应的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页面应用程序。
逻辑是获取对Web应用程序已引用的程序集的引用,为该程序集创建一个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文件夹中包含的文件以prefix _content{content/{LIBRARY NAME}/暴露给RCL或消费应用程序。例如,一个名为Razor.Class.Lib导致指向静态内容的路径位于_content/Razor.Class.Lib/.
这里是什么Index.cshtml符合上述要求。
<script src="~/_content/StaticRCL/js/script.js"></script>
处理动态加载的RCL
根据相关文档,需要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和其他静态资源配置为嵌入到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); 
        }
    }
上面的方法由Startup类的Configure()方法在app.UseStaticFiles文件()调用之前调用。
 app.UseHttpsRedirection();

    // register file providers for the dynamically loaded libraries
    if (DynamicallyLoadedLibraries.Count > 0)
        RegisterDynamicLibariesStaticFiles(env);

    app.UseStaticFiles();

这是DynamicRCL项目中的Index.cshtml做的,为了使用javascript文件

<script src="js/script.js"></script>

这里没有使用_content/{LIBRARY NAME}/方案。我们只使用js文件夹,因为我们已经将DynamicRCL程序集的wwwroot文件夹注册为web根文件夹。

测试在:
Windows 10
Asp.Net  Core 3.1
Microsoft Visual Studio 2019预览版,版本16.9.0预览版5.0

kelvin
关注 私信
文章
98
关注
0
粉丝
0