標題:從零開始實現ASP.NET Core MVC的插件式開發(一) - 使用Application Part動態加載控制器和視圖
作者:Lamond Lu
地址:https://www.cnblogs.com/lwqlun/p/11137788.html
源代碼:https://github.com/lamondlu/Mystique
系列文章
- 從零開始實現ASP.NET Core MVC的插件式開發(一) - 使用Application Part動態加載控制器和視圖
- 從零開始實現ASP.NET Core MVC的插件式開發(二) - 如何創建項目模板
- 從零開始實現ASP.NET Core MVC的插件式開發(三) - 如何在運行時啟用組件
- 從零開始實現ASP.NET Core MVC的插件式開發(四) - 插件安裝
- 從零開始實現ASP.NET Core MVC的插件式開發(五) - 使用AssemblyLoadContext實現插件的升級和刪除
- 從零開始實現ASP.NET Core MVC的插件式開發(六) - 如何加載插件引用
- 從零開始實現ASP.NET Core MVC的插件式開發(七) - 近期問題匯總及部分解決方案
- 從零開始實現ASP.NET Core MVC的插件式開發(八) - Razor視圖相關問題及解決方案
前言
如果你使用過一些開源CMS的話,肯定會用過其中的的插件化功能,用戶可以通過啟用或者上傳插件包的方式動態添加一些功能,那么在ASP.NET Core MVC中如何實現插件化開發呢,下面我們來探究一下。
本系列只是筆者的一些嘗試,並不表示一定正確,只是為了分享一些思路,大家可以一起討論一下,后續會不斷更新。
什么是ApplicationPart?
在ASP.NET Core中進行插件話開發,就不得不說ApplicationPart
。 ApplicationPart
是ASP.NET Core一個重要組件,它是應用程序資源的一種抽象,通過它可以發現程序集中包含的控制器、視圖組件、TagHelper和預編譯Razor視圖等MVC功能。
默認情況下,當一個ASP.NET Core MVC應用啟動時,它只會嘗試在當前應用啟動的項目及引用的項目中加載控制器,如果想從未直接引用的程序集中加載控制器和預編譯Razor視圖,我們就需要借助ApplicationPart
了。
而ASP.NET Core MVC中,有一個ApplicaitonPartManager
類, 通過ApplicationPartManager
我們可以來配置當前應用中使用哪一些ApplicationPart
。
例:
var assembly = Assembly.LoadFile("demo.dll");
var assemblyPart = new AssemblyPart(assembly);
var mvcBuilders = services.AddMvc();
mvcBuilders.ConfigureApplicationPartManager(apm =>
{
apm.ApplicationParts.Add(assemblyPart);
});
下面呢,我們通過一個最簡單的實例,給大家演示一下如何借助ApplicationPart
,動態加載第三方程序集中的控制器和預編譯視圖。
創建項目
首先我們創建一個ASP.NET Core MVC的站點,命名為DynamicPluginsDemoSite
然后我們同時創建一個.NET Core Class Library項目,命名為DemoPlugin1, 同時對該項目引用
- Microsoft.AspNetCore.App
- Microsoft.AspNetCore.Razor
- Microsoft.AspNetCore.Razor.Design
注意: 針對以上3個程序集,需要保證DynamicPluginsDemoSite和DemoPluigin1使用的相同的版本。
這里為了保證Razor視圖的預編譯,我們需要打開DemoPlugin1項目的工程文件DemoPlugin1.csproj。將項目使用的SDK從"Microsoft.NET.Sdk"改為"Microsoft.Net.Sdk.Razor"。
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>C:\Users\Lamond Lu\source\repos\DynamicPlugins\DynamicPluginsDemoSite\bin\Debug</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" />
</ItemGroup>
</Project>
注:如果不做此修改,最后項目編譯之后,不會產生預編譯的Razor視圖程序集。(這里如果有其他更優雅的修改方式,請大家留言, 我后續會先嘗試先編寫一個項目模板來避免這個重復操作)。
添加控制器和視圖
下面我們開始編寫我們的插件。
這里我們首先創建一個Plugin1Controller.cs
.
public class Plugin1Controller : Controller
{
public IActionResult HelloWorld()
{
return View();
}
}
然后我們添加一個對應的視圖文件HelloWorld.cshtml。
@{
}
<h1>This is Demo Plugin1.</h1>
最終項目文件目錄如下:
最后我們需要修改一個項目的輸出目錄,我們需要將項目編譯的dll發送到DynamicPluginsDemoSite項目的Debug目錄中。
以上我們就完成了第一個組件的所有修改,下面我們開始修改DynamicPluginsDemoSite項目。
如何動態加載插件中的控制器?
由於DynamicPluginsDemoSite項目不能直接引用DemoPlugin1, 所以當項目啟動時,不能自主發現DemoPlugin1項目中的控制器,所以這里我們需要使用ApplicationPart
將DemoPlugin1的程序集加載到當前的運行環境中。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
var assembly = Assembly.LoadFile(AppDomain.CurrentDomain.BaseDirectory + "DemoPlugin1.dll");
var mvcBuilders = services.AddMvc();
var controllerAssemblyPart = new AssemblyPart(assembly);
mvcBuilders.ConfigureApplicationPartManager(apm =>
{
apm.ApplicationParts.Add(controllerAssemblyPart);
});
mvcBuilders.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
代碼解釋:
- 由於前一步中, 我們將DemoPlugin1的程序集輸出到了DynamicPluginsDemoSite的Debug目錄中,所以這里我們可以使用
Assembly.LoadFile
方法將它加載。 - 這里我們使用
AssemblyPart
類,將加載程序集封裝成一個ApplicationPart
. mvcBuilders
對象的ConfigureApplicationPartManager
方法可以用來配置當前項目中使用的ApplicationPart
如何加載組件的預編譯Razor視圖?
加載完控制器之后,我們還需要加載插件的預編譯Razor視圖。這里和之前的稍有不同,我們需要使用CompileRazorAssemblyPart
來封裝加載的預編譯Razor視圖。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
var assembly = Assembly.LoadFile(AppDomain.CurrentDomain.BaseDirectory + "DemoPlugin1.dll");
var assemblyView = Assembly.LoadFile(AppDomain.CurrentDomain.BaseDirectory + "DemoPlugin1.Views.dll");
var viewAssemblyPart = new CompiledRazorAssemblyPart(assemblyView);
var controllerAssemblyPart = new AssemblyPart(assembly);
var mvcBuilders = services.AddMvc();
mvcBuilders.ConfigureApplicationPartManager(apm =>
{
apm.ApplicationParts.Add(controllerAssemblyPart);
apm.ApplicationParts.Add(viewAssemblyPart);
});
mvcBuilders.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
最終效果
現在我們啟動DynamicPluginsDemoSite,在瀏覽器中輸入/Plugin1/HelloWorld, 我們的插件就正常啟用了。
注意:在啟動DynamicPluginsDemoSite站點之前,請務必先編譯DemoPlugin1項目,這樣DemoPlugin1產生的程序集才會輸出到DynamicPluginsDemoSite中。
總結
以上只是實現了一個最簡單的MVC插件功能,要想完善整個項目,后續還有很多工作要做
- 需要創建一個插件模板,來避免一些重復操作。
- 需要將插件的模型和業務抽象出來。
- 需要改用數據庫來保存插件信息。
- 需要支持實現插件的管理以及插件的升級。
后續我會慢慢實現以上功能,大家敬請期待。