本系列目錄:Abp介紹和經驗分享-目錄
Abp的模塊系統支持插件機制,可以在指定目錄中放置模塊程序集,然后應用程序啟動時會搜索該目錄,加載其中所有程序集中的模塊。
如何使用這套機制進行功能插件化開發?
首先,插件程序集和應用程序是毫無關系的,應用程序不依賴這個程序集,所以我們要解決幾個常見問題:
- 插件中提供的功能需要權限認證,如何自動注冊權限到使用了該插件的應用程序?
- 插件中提供的功能在展現層中需要菜單導航,如何自動注冊菜單項目?
- 插件中提供的功能需要配置,如何讓插件自已能進行簡單的配置管理,而不用去改宿主的配置文件?
- 插件中提供了新的Mvc Controller,當然也需要注冊路由。
以下代碼從本系列QuickStartA中的Personball.Demo解決方案開始
開始我們的第一個插件程序集的開發
首先啟動QuickStartA中一切就緒的HelloWorld,並且登陸進去看看首頁
一切正常!
Step1 新建插件程序集
- 在解決方案中新建目錄
PlugIns,新建程序集項目Personball.PlugIns.PlugInZero; - 修改默認命名空間,由於這是一個插件,我選擇默認命名空間為
Personball.PlugIns,將PlugInZero作為這個示例插件的名稱; - 在程序包管理其控制台,選擇默認項目
PlugIns\Personball.PlugIns.PlugInZero,執行Install-Package Abp.Web.Mvc -Version 2.3(保證和宿主使用的Abp框架版本一致,可以減少很多不必要的麻煩,這里安裝Abp.Web.Mvc是因為我們將在插件中實現一個MvcController); - 在插件程序集中添加一個目錄
PlugInZero,移除默認的Class1.cs。
Step2 注冊權限
Abp中權限構建基本都是通過繼承AuthorizationProvider,實現SetPermissions方法,並添加到IAuthorizationConfiguration.Providers。
插件本身也是一個模塊,只要實現自己的AuthorizationProvider,並注冊進Providers即可。
在PlugInZero目錄中定義我們的插件模塊PlugInZeroModule,代碼如下:
因本博客樣式原因,源代碼排版容易亂,下文大段代碼全部貼圖,文末附最終代碼壓縮包供下載。
在PlugInZero目錄下新建常量定義文件PlugInZeroConsts,代碼如下:
namespace Personball.PlugIns.PlugInZero
{
public static class PlugInZeroConsts
{
public static class PermissionNames
{
public const string PlugIns = "Personball.PlugIns";
public const string PlugInZero = "Personball.PlugIns.PlugInZero";
}
}
}
在PlugInZero目錄下新建目錄Authorization,新建類PlugInZeroAuthorizationProvider,代碼如下:
再到插件模塊PlugInZeroModule中注冊上述權限:
public override void PreInitialize()
{
Configuration.Authorization.Providers
.Add<PlugInZeroAuthorizationProvider>();
//TODO 等會要用
}
Step3 等不及要先看看效果了,讓我們啟用Personball.Demo.Web的插件加載!
讓我們打開Personball.Demo.Web項目的Global.asax文件,添加代碼后如下:
- 為
Personball.Demo.Web項目添加一個目錄PlugIns; - 生成插件程序集,將插件項目bin\debug目錄中的
Personball.PlugIns.PlugInZero.dll和Personball.PlugIns.PlugInZero.pdb復制到Personball.Demo.Web的PlugIns目錄下,啟動Personball.Demo.Web! - 啟動后,登陸,點開
Roles菜單,點開角色Admin的編輯彈窗,看權限的選項,確實增加了我們剛才在插件中定義的新權限!
如果你有Asp.Net Zero的代碼(收費的),那么權限編輯功能是可以直接使用的,這里官網免費生成的項目僅包含了module-zero基礎功能,UI部分並未實現權限編輯功能。
Step4 注冊菜單
和權限類似,菜單通過繼承NavigationProvider,實現SetNavigation方法,並添加到INavigationConfiguration.Providers。
同上,插件可以實現自己的NavigationProvider,並注冊。
在PlugInZero目錄下新建目錄Navigation,新建類PlugInZeroNavigationProvider,代碼如下:
再到插件模塊PlugInZeroModule中注冊菜單:
public override void PreInitialize()
{
//注冊權限
Configuration.Authorization.Providers
.Add<PlugInZeroAuthorizationProvider>();
//注冊菜單
Configuration.Navigation.Providers
.Add<Navigation.PlugInZeroNavigationProvider>();
}
來看看效果(生成插件程序集,並復制替換到Web項目的PlugIns目錄下):
權限和菜單相對簡單,接下來是重點。
Step5 注冊路由和尋找Controller
首先我們提供一個PlugInZeroController,簡單起見,僅有一個返回字符串的Action。
在PlugInZero目錄下新建目錄Controller,新建類PlugInZeroController,代碼如下:
public class PlugInZeroController : AbpController
{
public PlugInZeroController()
{
LocalizationSourceName = "Abp";
}
public Task<string> Hello()
{
return Task.FromResult($"hello at {DateTime.Now}");
}
}
接着,我們注冊下路由(在PlugInZeroModule的PreInitialize方法中):
//注冊權限
Configuration.Authorization.Providers
.Add<PlugInZeroAuthorizationProvider>();
//注冊菜單
Configuration.Navigation.Providers
.Add<Navigation.PlugInZeroNavigationProvider>();
//注冊路由
RouteTable.Routes.MapRoute(
"Plugins",
url: "PlugIns/{controller}/{action}",
defaults: new { controller = "Home", action = "Index" });
修改一下菜單項上的url,並指定鏈接的target(在PlugInZeroNavigationProvider中):
plugInRoot.AddItem(
new MenuItemDefinition(
"PlugInZero",
new FixedLocalizableString("PlugInZero(插件)"),
//指定Controller的url
url: "PlugIns/PlugInZero/Hello",
icon: "",
target: "_blank"//新開一個窗口
));
更新插件程序集,啟動,點擊之前的插件菜單(如果遇到404,請參考下方Tip01):
Step6 插件配置
OK,Last Question:如何提供插件單獨的配置?
思路是,從程序集的App.config入手!
如果插件自身使用數據庫,有個DbContext,怎么讀取配置並讓IoC構建DbContext時使用這個配置?
右鍵插件項目,新增項目,選擇應用程序配置文件,文件名自動就是App.config,添加!
編輯App.config,如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="PlugInZeroDB" connectionString="localhost" providerName="System.Data.SqlClient"/>
</connectionStrings>
<appSettings>
<add key="PlugInZeroSettingKey" value="Wahoo, wahoo"/>
</appSettings>
</configuration>
改下PlugInZeroController的代碼嘗試讀取PlugInZeroSettingKey並輸出:
public Task<string> Hello()
{
var config = ConfigurationManager.OpenExeConfiguration(
Assembly.GetExecutingAssembly().Location);
var value = config.AppSettings
.Settings["PlugInZeroSettingKey"].Value;
return Task.FromResult($"hello at {DateTime.Now} {value}");
}
重新生成插件程序集,並復制三個文件(含Personball.PlugIns.PlugInZero.dll.config)到PlugIns目錄中,啟動:
讀取數據庫連接字符串,並讓指定的DbContext(插件自己的)使用該配置,這里僅給出示例代碼,並不實際運行演示。
//插件配置(直接從當前執行的程序集的config文件讀取數據庫連接串)
var config = ConfigurationManager.OpenExeConfiguration(
Assembly.GetExecutingAssembly().Location);
string connectStr = config.ConnectionStrings.ConnectionStrings["PlugInZeroDB"].ConnectionString;
//注冊DbContext,構建時使用指定參數
IocManager.IocContainer.Register(
Component.For<PlugInZeroDbContext>()
.DependsOn(
Dependency.OnValue(
"connectionString", connectStr)));
Tip01
注意!注意!注意!
BuildManager對於ControllerType有緩存,在服務器上僅僅加載插件,注冊路由,可能還是會遇到404(找不到Controller)。
這種時候必須改動對於iis敏感的幾個路徑(bin目錄)或Web.config文件,BuildManager才會更新ControllerType的緩存(這是個文件緩存!),將插件內的Controller類型也算進去。
這里雖然只有寥寥數語,卻是各種心酸血淚之后的總結,期間甚至自己擴展過一個ControllerFactory,那也是一套可行的方案,不贅述了。
搜了一個MVC-ControllerTypeCache.xml,內容如下(這里並未包含插件中的PlugInZeroController):
<?xml version="1.0" encoding="utf-8"?>
<!--This file is automatically generated. Please do not modify the contents of this file.-->
<typeCache lastModified="8/22/2017 12:00:44 AM" mvcVersionId="cc73190b-ab9d-435c-8315-10ff295c572a">
<assembly name="Personball.Demo.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<module versionId="9c27c37d-a073-42aa-b339-b5887549b123">
<type>Personball.Demo.Web.Controllers.AboutController</type>
<type>Personball.Demo.Web.Controllers.AccountController</type>
<type>Personball.Demo.Web.Controllers.HomeController</type>
<type>Personball.Demo.Web.Controllers.RolesController</type>
<type>Personball.Demo.Web.Controllers.TenantsController</type>
<type>Personball.Demo.Web.Controllers.UsersController</type>
<type>Personball.Demo.Web.Controllers.LayoutController</type>
</module>
</assembly>
<assembly name="Abp.Web.Mvc, Version=2.3.0.0, Culture=neutral, PublicKeyToken=null">
<module versionId="6bcf306f-dc82-4ad6-99be-7efe88288f89">
<type>Abp.Web.Mvc.Controllers.AbpAppViewController</type>
<type>Abp.Web.Mvc.Controllers.AbpScriptsController</type>
<type>Abp.Web.Mvc.Controllers.AbpUserConfigurationController</type>
<type>Abp.Web.Mvc.Controllers.Localization.AbpLocalizationController</type>
</module>
</assembly>
</typeCache>
Tip02
插件的dll文件替換時遇到進程鎖定問題
請先停止iis站點或者應用程序池,替換插件dll后再啟動
如果配置了CI,比如tfs使用webdeploy發布
務必請在發布時指定webdeploy的選項,忽略插件目錄-skip:Directory="PlugIns"。
插件一般考慮手動更新(大多是非核心的功能,變更極少),如果CI每次都要考慮重新build插件並更新,就得不償失了。
假如非要CI每次更新插件程序集,那就需要先用webdeploy停止目標站點的應用程序池,發完后再啟動應用程序池。
詳情請參見下方參考條目:Operations on application pools as admin and non-admin
參考
Taming the BuildManager, ASP.Net Temp files and AppDomain restarts
Operations on application pools as admin and non-admin
