具有完整静态文件(JS、CSS)支持的 ASP.NET Core 3.x 动态可加载插件

 

介绍

3.0版开始,ASP.NET Core提供了一种使用Application Parts将应用程序拆分为模块的方法。

一个解决方案可能包含一个Web应用程序和任意数量的程序集库,其中可能包含控制器、视图、页面、静态文件(如 JavaScript CSS 文件)等。这些库称为Razor 类库 RCL

有人希望在解决方案中使用Razor库的原因有很多。

但最有价值的情况是动态加载库时,作为插件。想象一个电子商务解决方案,它提供了许多税收或运费计算插件或付款插件,供管理员选择。

不过也有一些困难。可以肯定的是,该文档未能提供完整的描述性示例。

但最令人沮丧的是,Application PartsRCL似乎不是为了与动态加载的库(即插件)一起使用而创建的。

特别是对于静态文件,即JavaScriptCSS文件,动态加载的RCL是失败的。

本练习的内容

在本文中,我们将研究这两个用例:

  • 主应用程序静态引用的RCL
  • 由主应用程序动态加载的RCL

两个RCL都包含静态文件,即JavaSriptCSS文件。

我们将使用一个ASP.NET Core MVC Web应用程序和两个RCL

首先,创建一个ASP.NET Core MVC Web应用程序并将其命名为WebApp.

引用RCL

按照文档提供的说明创建RCL 

命名RCL  StaticRCL。我们稍后会看到为什么这个名字很重要。

从项目中删除所有文件和文件夹,并添加三个新文件夹:ControllersViewswwwroot

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。命名为DynamicRCL

控制器

 

public class LibDynamicController : Controller
{
    [Route("/dynamic")]
    public IActionResult Index()
    {
        return 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_前缀,即<AssemblyName>rcl_DynamicRCL</AssemblyName>
  • 添加一个GenerateEmbeddedFilesManifest,即<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  • 添加Microsoft.Extensions.FileProviders.Embedded NuGet包,即,<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.0" />
  • 设置输出路径到主Web Applicationbin文件夹,即<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

WebApp Web应用程序应该有一个对第一个RCLproject reference,即StaticRCL

HomeControllerIndex.cshtml如下:

@{
    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 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页面)。随着扭曲,当涉及到静态文件,如JavaScriptCSS文件等。

以下是文档中的内容:

RCLwwwroot文件夹中包含的文件在前缀_content/{LIBRARY NAME}/下暴露给RCL或消费应用程序。例如,名为Razor.Class.Lib的库会生成_content/Razor.Class.Lib/处的静态内容路径。

以下是StaticRCL项目的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);
        }
    }
}

 

现在是棘手的部分。

我们已经将JavaScriptCSS 和其他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文件所做的事情。

 

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

 

这里没有使用_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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值