返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期html
说了这么久,尚未详细说到abp框架,abp其实基于DDD(领域驱动设计)原则的细看分层以下:前端
再看咱们项目解决方案以下:web
JCmsErp.Application,应用层:进行展示层与领域层之间的协调,协调业务对象来执行特定的应用程序的任务。它不包含业务逻辑,主要包含一些模型,abp重要的数据传输DTO,包括数据库映射实体,前端视图模型转实体(Entity)对象,一个应用服务方法一般被认为是一个工做单元(Unit of Work),使用一种像AutoMapper这样的工具来进行实体与DTO之间的映射,前端参数传入有限性验证等等数据库
JCmsErp.Core:领域层:领域层就是业务层,是一个项目的核心,全部业务规则都应该在领域层实现。包括业务对象和业务规则,这是应用程序的核心层。 设计模式
实体(Entity):实体表明业务领域的数据和操做,在实践中,经过用来映射成数据库表。并发
仓储(Repository):仓储用来操做数据库进行数据存取。仓储接口在领域层定义,而仓储的实现类应该写在基础设施层。app
领域服务(Domain service):当处理的业务规则跨越两个(及以上)实体时,应该写在领域服务方法里面。框架
领域事件(Domain Event): 在领域层某些特定状况发生时能够触发领域事件,而且在相应地方捕获并处理它们。 ide
工做单元(Unit of Work):工做单元是一种设计模式,用于维护一个由已经被修改(如增长、删除和更新等)的业务对象组成的列表。它负责协调这些业务对象的持久化工做及并发问题。
函数
JCmsErp.EntityFramework:基础设施层:提供通用技术来支持更高的层。例如基础设施层的仓储(Repository)可经过ORM来实现数据库交互。当在领域层中为定义了仓储接口,应该在基础设施层中实现这些接口。可使用ORM工具,例如EntityFramework或NHibernate。ABP的基类已经提供了对这两种ORM工具的支持。还有数据迁移等。
JCmsErp.Web:展示层:提供试图用于与用户实现交互操做.
JCmsErp.WebApi:这里在abp中主要是提供接口,能够是解决方案内部使用接口,能够是与移动端等其余端口链接的接口.
二,实体(Entity)
实体是DDD(领域驱动设计)的核心概念之一。Eirc Evans是这样描述的实体的:“它根本上不是经过属性定义的,而是经过一系列连续性和标识定义的”。所以,实体都有Id属性而且都存储到数据库中。一个实体通常会映射到数据库的一张表
abp中实体是派生于Entity类,先看一下咱们在Core层新建的Users类
Users实体类,有人说这个实体为何没有id,由于Users继承自Entity类,Entity类已经定义id,
它是该Entity类的 主键。所以,全部实体的主键名都是相同的,都是Id。
Id(主键)的类型是能够改变的,默认是int(int32)的。若是你想将Id定义为其余类型,能够在<>内写,好比Guid,long也是能够的。
Entity类重写了等号运算符(==),能够轻松地检查两个实体是否相同了(实体的Id相同则认为它们相同)。它也定义了IsTransient方法来检测它是否有Id。
审计:
IHasCreationTime使得使用一个通用的属性来描述一个实体的“建立时间”信息成为可能。当实现了该接口的实体类插入到数据库中时,ABP会自动地将当前的时间设置给CreationTime。
ICreationAudited增长了CreatorUserId扩展了IHasCreationTime,当用户保存一个新的实体的时候,会自动把当前的id设置为CreatorUserId,还有相似的LastModificationTime也是同样。
当更新实体时,abp会自动为你设置这些属性。
软删除
软删除是将一个实体标记为已删除的一般使用的模式,而不是直接从数据库中删除。好比,你可能不想从数据库中硬删除一个User,由于它可能关联其余的表
ABP实现了开箱即用的软删除模式。当一个软删除实体被删除后,ABP检测到以后,会阻止删除,将IsDeleted设置为true并更新数据库中的实体。并且,它会自动地过滤数据库中软删除的实体,不会检索(select)它们。
若是使用了软删除,那么你可能想存储一些信息,好比什么时候删除以及谁删除了一个实体等等
在JCmsErp.Application建立一个Users文件夹,而后建立UserinfoDto,DTO是用于Core和 Web间的数据传输对象.有了实体了为何还要DTO呢
1,DTO保证了层与层的分离,web层改变不影响core层,core作改变也不影响web.
2,数据保护,否则敏感或者不须要的数据暴露于web层,不被别人窥见如密码,银行帐号,身份证等敏感信息
3,序列化,序列化集合,可是子集不序列化,当首次用到子集的时候才序列化.
4,惰性加载
5,DTO数据验证
6,abp还有一些扩展的接口,扩展性好,下降耦合度,使表现层和逻辑层之间耦合度下降.
这里Serializable就是支持序列化的标签, [AutoMapFrom(typeof(Users))]是指和Users之间双向自动转化的标签,并不须要每一个字段都去手动匹配.ABP提供了若干特性和扩展方法来定义映射。首先,要将Abp.AutoMapper nuget包添加到项目中。而后,AutoMap特性是双向映射方式, AutoMapFrom和 AutoMapTo是单向映射方式。最后,使用MapTo扩展方法将一个对象映射到另外一个对象
ABP框架提供了建立和组装模块的基础,一个模块可以依赖于另外一个模块。在一般状况下,一个程序集就能够当作是一个模块。在ABP框架中,一个模块经过一个类来定义,而这个类要继承自AbpModule。
模块系统当前专一于服务端而不是客户端。
若是学习过Orchard的朋友,应该知道module模块的强大了。模块的本质就是可重用性,你能够在任意的地方去调用,并且经过实现模块,你写的模块也能够给别人用。.net能够经过反射获取一个程序集中的类以及方法。
Assembly程序集:Assembly是一个用来包含程序的名称,版本号,自我描述,文件关联关系和文件位置等信息的一个集合。最简单的理解就是:一个你本身写的类库生成的dll就能够看作是一个程序集,这个程序集能够包括不少类,类又包括不少方法等。
一个派生自 AbpModule 的类就是模块的定义。咱们正在开发一个博客模块,该模块能够被使用在不一样的应用程序中。最简单的模块定义示例以下:
public class MyBlogApplicationModule : AbpModule //定义 { public override void Initialize() //初始化 { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); //这行代码的写法基本上是不变的。它的做用是把当前程序集的特定类或接口注册到依赖注入容器中。 } }
若是须要的话,模块类负责类的依赖注入(一般能够像上面同样作)。咱们能配置应用程序和其它模块,添加新的功能到应用程序等等。
在一个应用中,ABP框架调用了Module模块的一些指定的方法来进行启动和关闭模块的操做。咱们能够重载这些方法来完成咱们本身的任务。
ABP框架经过依赖关系的顺序来调用这些方法,假如:模块A依赖于模块B,那么模块B要在模块A以前初始化,模块启动的方法顺序以下:
下面是具体方法的说明:
PreInitialize
预初始化:当应用启动后,第一次运行会先调用这个方法。在初始化(Initialize)方法调用以前,该方法一般是用来配置框架以及其它模块。
在依赖注入注册以前,你能够在这个方法中指定你须要注入的自定义启动类。例如:加入你建立了某个符合约定的注册类,你应该使用 IocManager.AddConventionalRegisterer 方法在这里注册它。
Initialize
初始化:在这个方法中通常是来进行依赖注入的注册,通常咱们经过IocManager.RegisterAssemblyByConvention这个方法来实现。若是你想实现自定义的依赖注入,那么请参考依赖注入的相关文档。
PostInitialize
提交初始化:最后一个方法,这个方法用来解析依赖关系。
Shutdown
关闭:当应用关闭之后,这个方法被调用。
Abp框架会自动解析模块之间的依赖关系,可是咱们仍是建议你经过重载GetDependencies方法来明确的声明依赖关系。
[DependsOn(typeof(MyBlogCoreModule))]//经过注解来定义依赖关系 public class MyBlogApplicationModule : AbpModule { public override void Initialize() { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); } }
例如上面的代码,咱们就声明了MyBlogApplicationModule和MyBlogCoreModule的依赖关系,MyBlogApplicationModule这个应用模块依赖于MyBlogCoreModule核心模块,而且,MyBlogCoreModule核心模块会在MyBlogApplicationModule模块以前进行初始化。
ABP能够从 startup module 递归的解析依赖关系,并按需初始化它们。最后初始化的模块是启动模块(startup module)。
当模块从启动模块以及其依赖关系进行调查发现的时候,ABP也可以动态的加载其它指定模块。AbpBootstrapper 类定义了 PlugInSources 属性,咱们能用该属性添加须要动态加载的模块。插件源能够是任何实现了 IPlugInSource 接口的类。FolderPlugInSource 类实现了该接口,它能够被用来加载指定文件夹下的程序集。
ABP的ASP.NET Core模块也能够动态加载模块,你只须要在 Startup 类中使用已定义的扩展方法 AddAbp,以下所示:
services.AddAbp<MyStartupModule>(options => { options.PlugInSources.Add(new FolderPlugInSource(@"C:\MyPlugIns")); });
咱们可使用扩展方法 AddFolder 更方便的实现上述功能:
services.AddAbp<MyStartupModule>(options => { options.PlugInSources.AddFolder(@"C:\MyPlugIns"); });
了解更多关于Startup类的信息,请查看 ASP.NET 文档
对于经典的ASP.NET MVC应用,咱们能够在 global.asax 重写 Application_Start 方法来添加插件文件夹,以下所示:
public class MvcApplication : AbpWebApplication<MyStartupModule> { protected override void Application_Start(object sender, EventArgs e) { AbpBootstrapper.PlugInSources.AddFolder(@"C:\MyPlugIns"); //... base.Application_Start(sender, e); } }
若是你的模块包括了MVC或者Web API控制器,ASP.NET不能发现这些控制器。为了克服这个问题,你能够在 global.asax 中添加代码来实现,以下所示:
using System.Web; using Abp.PlugIns; using Abp.Web; using MyDemoApp.Web; [assembly: PreApplicationStartMethod(typeof(PreStarter), "Start")] namespace MyDemoApp.Web { public class MvcApplication : AbpWebApplication<MyStartupModule> { } public static class PreStarter { public static void Start() { //... MvcApplication.AbpBootstrapper.PlugInSources.AddFolder(@"C:\MyPlugIns\"); MvcApplication.AbpBootstrapper.PlugInSources.AddToBuildManager(); } } }
对于IAssemblyFinder和ITypeFinder的默认实现(这两个接口的实现被ABP用来在应用程序中发现指定的类)仅仅只用来查找模块程序集以及在这些程序集中所使用的类型。咱们能够在咱们的模块中重写 GetAdditionalAssemblies 方法来包含附加程序集。
咱们本身定义的模块中可能有方法被其余依赖于当前模块的模块调用,下面的例子,假设模块2依赖于模块1,而且想在预初始化的时候调用模块1的方法。这样,就把模块1注入到了模块2,所以,模块2就能调用模块1的方法了。
译者注:
ABP的模块系统与Orchard的模块有相似之处,但仍是有比较大的差异。Orchard的框架修改了ASP.NET程序集的默认加载方式(模块的DLL没有放在Bin文件夹下,是放在WEB项目根文件夹下面的Modules文件夹下),实现了功能模块的热插拔,而ABP的模块程序集仍是放在Bin文件夹下的,没有实现热插拔。
public class MyModule1 : AbpModule { public override void Initialize() //初始化模块 { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());//这里,进行依赖注入的注册。 } public void MyModuleMethod1() { //这里写自定义的方法。 } } [DependsOn(typeof(MyModule1))] public class MyModule2 : AbpModule { private readonly MyModule1 _myModule1; public MyModule2(MyModule1 myModule1) { _myModule1 = myModule1; } public override void PreInitialize() { _myModule1.MyModuleMethod1(); //调用MyModuleMethod1的方法。 } public override void Initialize() { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); } }
在这里,咱们经过构造函数注入MyModule1到MyModule2,因此MyModule2可以调用MyModule1的自定义方法。当且仅当MyModule2依赖于MyModule1才是可能的。
虽然自定义模块能够被用来配置模块,可是,做者建议使用启动配置来定义和配置模块。
全部的模块类都被自动的注册为单例模式。