[2017-08-21]Abp系列——如何使用Abp插件机制(注册权限、菜单、路由)
本系列目录: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
本文源码下载
[2017-08-21]Abp系列——如何使用Abp插件机制(注册权限、菜单、路由)的更多相关文章
- 基于DDD的现代ASP.NET开发框架--ABP系列文章总目录
ABP相关岗位招聘:给热爱.NET新技术和ABP框架的朋友带来一个高薪的工作机会 ABP交流会录像视频:ABP架构设计交流群-7月18日上海线下交流会的内容分享(有高清录像视频的链接) 代码自动生成: ...
- 基于DDD的现代ASP.NET开发框架--ABP系列文章总目录(转)
出处:http://www.cnblogs.com/mienreal/p/4528470.html ABP相关岗位招聘:给热爱.NET新技术和ABP框架的朋友带来一个高薪的工作机会 ABP交流会录像视 ...
- 浅入 ABP 系列(4):事件总线
浅入 ABP 系列(4):事件总线 版权护体作者:痴者工良,微信公众号转载文章需要 <NCC开源社区>同意. 目录 浅入 ABP 系列(4):事件总线 事件总线 关于事件总线 为什么需要这 ...
- 基于DDD的现代ASP.NET开发框架--ABP系列之3、ABP分层架构
基于DDD的现代ASP.NET开发框架--ABP系列之3.ABP分层架构 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP的官方网站:ht ...
- 基于DDD的现代ASP.NET开发框架--ABP系列之2、ABP入门教程
基于DDD的现代ASP.NET开发框架--ABP系列之2.ABP入门教程 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boi ...
- 点这里进入ABP系列文章总目录
基于DDD的现代ASP.NET开发框架--ABP系列之1.ABP总体介绍 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boi ...
- [2017-09-04]Abp系列——为什么值对象必须设计成不可变的
本系列目录:Abp介绍和经验分享-目录 这篇是之前翻备忘录发现漏了的,前阵子刚好同事又提及过这个问题,这里补上. 本文重点在于理解什么是值对象的不可变性. Abp的ValueObject以及EF的Co ...
- [2017-08-28]Abp系列——业务异常与错误码设计及提示语的本地化
本系列目录:Abp介绍和经验分享-目录 前言 ABP中有个异常UserFriendlyException经常被使用,但是它所在的命名空间是Abp.UI,总觉得和展现层联系过于紧密,在AppServic ...
- [2017-08-16]ABP系列——QuickStartB:正确理解Abp解决方案的代码组织方式、分层和命名空间
本系列目录:Abp介绍和经验分享-目录 介绍ABP的文章,大多会提到ABP框架吸收了很多最佳实践,比如: 1.N层 (复用一下上篇的图) 展现层(Personball.Demo.Web):asp.ne ...
随机推荐
- (转)sql通配符
背景:一次搞清sql查询中的通配符问题. 1 sql通配符 通配符主要以下几种:%._.[].[^] . 在搜索数据库中的数据时,SQL 通配符可以替代一个或多个字符.SQL 通配符必须与 LIKE ...
- Android学习笔记- ProgressBar(进度条)
本节引言: 本节给大家带来的是Android基本UI控件中的ProgressBar(进度条),ProgressBar的应用场景很多,比如 用户登录时,后台在发请求,以及等待服务器返回信息,这个时候会用 ...
- javascript编程解决黑化的牛牛问题
问题描述 时间限制:1秒 空间限制:32768K 牛牛变得黑化了,想要摧毁掉地球.但他忘记了开启地球毁灭器的密码.牛牛手里有一个字符串S,牛牛还记得从S中去掉一个字符就恰好是正确的密码,请你帮牛牛求出 ...
- 独立ip的优势
独立ip的建站优势 我想很多人都想知道,那我就在这里给大家简单介绍下独立ip的优势有那些. 网站设计是需要很多专业知识的结合,整站不是那么容易就可以设计完成的 ...
- Python 文件的处理
简单的读取文件 f.read() 是读取这个文件的所有内容 f.readline() 是读取文件的一行 .write() 会去检查这个文件是否存在,不存在则创建,存在的话,则以覆盖的方式将内容写 ...
- java自学找工作经历
断断续续的,折腾了7个多月,学完了在网上购买的培训机构J2EE的全套课程.期间各种蛋疼看另一篇博客 http://www.cnblogs.com/work396/p/6791488.html 直接说找 ...
- input file样式修改,图片预览删除功能
本篇对input file进行了修改,改成自己需要的样式,类似验证身份上传身份证图片的功能. 效果图如下: 这里主要展示上传预览图片功能,对于删除功能的html及css写的比较粗糙,对于想要精细表现这 ...
- AsyncTask用法解析-下载文件动态更新进度条
1. 泛型 AysncTask<Params, Progress, Result> Params:启动任务时传入的参数,通过调用asyncTask.execute(param)方法传入. ...
- yhTriangle_LinkQueue(队列实现杨辉三角)
#include"LinkQueue.h" void yhTriangle(int n) { LinkQueue<int> A; int s,t; A.Inqueue( ...
- 关于ftp出现425错误
在centos上搭建一个ftp,一切都配置好之后,我去访问时仍然会出现425 Failed to establish connection.这个错误,经过一番查找,原来是这个 -A INPUT -j ...