点这里进入ABP系列文章总目录

基于DDD的现代ASP.NET开发框架--ABP系列之2、ABP入门教程

ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。

ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板。

ABP的官方网站:http://www.aspnetboilerplate.com

ABP在Github上的开源项目:https://github.com/aspnetboilerplate

ABP 的由来

“DRY——避免重复代码”是一个优秀的开发者在开发软件时所具备的最重要的思想之一。我们在开发企业WEB应用程序时都有一些类似的需求,例如:都需要登录页面、用户/角色管理、权限验证、数据有效性验证、多语言/本地化等等。一个高品质的大型软件都会运用一些最佳实践,例如分层体系结构、领域驱动设计、依赖注入等。我们也可能会采用ORM、数据库迁移(Database Migrations)、日志记录(Logging)等工具。

从零开始创建一个企业应用程序是一件繁琐的事,因为需要重复做很多常见的基础工作。许多公司都在开发自己的应用程序框架来重用于不同的项目,然后在框架的基础上开发一些新的功能。但并不是每个公司都有这样的实力。假如我们可以分享的更多,也许可以避免每个公司或每个项目的重复编写类似的代码。作者之所以把项目命名为“ASP.NET Boilerplate”,就是希望它能成为开发一般企业WEB应用的新起点,直接把ABP作为项目模板。

ABP是什么?

ABP是为新的现代Web应用程序使用最佳实践和使用最流行工具的一个起点。可作为一般用途的应用程序的基础框架或项目模板。它的功能包括:

服务器端:

  • 基于最新的.NET技术 (目前是ASP.NET MVC 5、Web API 2、C# 5.0,在ASP.NET 5正式发布后会升级)
  • 实现领域驱动设计(实体、仓储、领域服务、领域事件、应用服务、数据传输对象,工作单元等等)
  • 实现分层体系结构(领域层,应用层,展现层和基础设施层)
  • 提供了一个基础架构来开发可重用可配置的模块
  • 集成一些最流行的开源框架/库,也许有些是你正在使用的。
  • 提供了一个基础架构让我们很方便地使用依赖注入(使用Castle Windsor作为依赖注入的容器)
  • 提供Repository仓储模式支持不同的ORM(已实现Entity Framework 、NHibernate、MangoDb和内存数据库)
  • 支持并实现数据库迁移(EF 的 Code first)
  • 模块化开发(每个模块有独立的EF DbContext,可单独指定数据库)
  • 包括一个简单的和灵活的多语言/本地化系统
  • 包括一个 EventBus来实现服务器端全局的领域事件
  • 统一的异常处理(应用层几乎不需要处理自己写异常处理代码)
  • 数据有效性验证(Asp.NET MVC只能做到Action方法的参数验证,ABP实现了Application层方法的参数有效性验证)
  • 通过Application Services自动创建Web Api层(不需要写ApiController层了)
  • 提供基类和帮助类让我们方便地实现一些常见的任务
  • 使用“约定优于配置原则”

客户端:

  • Bootstrap、Less、AngularJs、jQuery、Modernizr和其他JS库: jQuery.validate、jQuery.form、jQuery.blockUI、json2等
  • 为单页面应用程序(AngularJs、Durandaljs)和多页面应用程序(Bootstrap+Jquery)提供了项目模板。
  • 自动创建Javascript 的代理层来更方便使用Web Api
  • 封装一些Javascript 函数,更方便地使用ajax、消息框、通知组件、忙状态的遮罩层等等

除ABP框架项目以外,还开发了名叫“Zero”的模块,实现了以下功能:

  • 身份验证与授权管理(通过ASP.NET Identity实现的)
  • 用户&角色管理
  • 系统设置存取管理(系统级、租户级、用户级,作用范围自动管理)
  • 审计日志(自动记录每一次接口的调用者和参数)

ABP不是什么?

ABP 提供了一个应用程序开发模型用于最佳实践。它拥有基础类、接口和工具使我们容易建立起可维护的大规模的应用程序。

然而:

它不是RAD工具之一,RAD工具的目的是无需编码创建应用程序。相反,ABP提供了一种编码的最佳实践。

它不是一个代码生成工具。在运行时虽然它有一些特性构建动态代码,但它不能生成代码。

它不是一个一体化的框架。相反,它使用流行的工具/库来完成特定的任务(例如用EF做ORM,用Log4Net做日志记录,使得Castle Windsor作为赖注入容器, AngularJs 用于SPA 框架)。

就我使用了ABP几个月的经验来看,虽然ABP不是RAD,但是用它开发项目绝对比传统三层架构要快很多。

虽然ABP不是代码生成工具,但因为有了它,使我们项目的代码更简洁规范,这有利于使用代码生成工具。

我自己使用VS2013的Scaffolder+T4开发的代码生成器,可根据领域对象的UML类图自动生成全部前后端代码和数据库,简单的CURD模块几乎不需要编写代码,有复杂业务逻辑的模块主要补充领域层代码即可。这样就能把时间多花在领域模型的设计上,减少写代码的时间。

下面通过原作者的“简单任务系统”例子,演示如何运用ABP开发项目

从模板创建空的web应用程序

ABP提供了一个启动模板用于新建的项目(尽管你能手动地创建项目并且从nuget获得ABP包,模板的方式更容易)。

转到www.aspnetboilerplate.com/Templates从模板创建你的应用程序。

你可以选择SPA(AngularJs或DurandalJs)或者选择MPA(经典的多页面应用程序)项目。可以选择Entity Framework或NHibernate作为ORM框架。

这里我们选择AngularJs和Entity Framework,填入项目名称“SimpleTaskSystem”,点击“CREATE MY PROJECT”按钮可以下载一个zip压缩包,解压后得到VS2013的解决方案,使用的.NET版本是 4.5.1。

每个项目里引用了Abp组件和其他第三方组件,需要从Nuget下载。

黄色感叹号图标,表示这个组件在本地文件夹中不存在,需要从Nuget上还原。操作如下:

要让项目运行起来,还得创建一个数据库。这个模板假设你正在使用SQL2008或者更新的版本。当然也可以很方便地换成其他的关系型数据库。

打开Web.Config文件可以查看和配置链接字符串:

<add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystemDb; Trusted_Connection=True;" />

(在后面用到EF的Code first数据迁移时,会自动在SQL Server数据库中创建一个名为SimpleTaskSystemDb的数据库。)

就这样,项目已经准备好运行了!打开VS2013并且按F5:

下面将逐步实现这个简单的任务系统程序

创建实体

把实体类写在Core项目中,因为实体是领域层的一部分。

一个简单的应用场景:创建一些任务(tasks)并分配给人。 我们需要TaskPerson这两个实体。

Task实体有几个属性:描述(Description)、创建时间(CreationTime)、任务状态(State),还有可选的导航属性(AssignedPerson)来引用Person。

public class Task : Entity<long>
{[ForeignKey("AssignedPersonId")]public virtual Person AssignedPerson { get; set; }public virtual int? AssignedPersonId { get; set; }public virtual string Description { get; set; }public virtual DateTime CreationTime { get; set; }public virtual TaskState State { get; set; }public Task(){CreationTime = DateTime.Now;State = TaskState.Active;}
}

Person实体更简单,只定义了一个Name属性:

public class Person : Entity
{public virtual string Name { get; set; }
}

在ABP框架中,有一个Entity基类,它有一个Id属性。因为Task类继承自Entity<long>,所以它有一个long类型的Id。Person类有一个int类型的Id,因为int类型是Entity基类Id的默认类型,没有特别指定类型时,实体的Id就是int类型。

创建DbContext

使用EntityFramework需要先定义DbContext类,ABP的模板已经创建了DbContext文件,我们只需要把TaskPerson类添加到IDbSet,请看代码:

public class SimpleTaskSystemDbContext : AbpDbContext
{public virtual IDbSet<Task> Tasks { get; set; }public virtual IDbSet<Person> People { get; set; }public SimpleTaskSystemDbContext(): base("Default"){}public SimpleTaskSystemDbContext(string nameOrConnectionString): base(nameOrConnectionString){}
}

通过Database Migrations创建数据库表

我们使用EntityFramework的Code First模式创建数据库架构。ABP模板生成的项目已经默认开启了数据迁移功能,我们修改SimpleTaskSystem.EntityFramework项目下Migrations文件夹下的Configuration.cs文件:

internal sealed class Configuration : DbMigrationsConfiguration<SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext>
{public Configuration(){AutomaticMigrationsEnabled = false;}protected override void Seed(SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext context){context.People.AddOrUpdate(p => p.Name,new Person {Name = "Isaac Asimov"},new Person {Name = "Thomas More"},new Person {Name = "George Orwell"},new Person {Name = "Douglas Adams"});}
}

在VS2013底部的“程序包管理器控制台”窗口中,选择默认项目并执行命令“Add-Migration InitialCreate

会在Migrations文件夹下生成一个xxxx-InitialCreate.cs文件,内容如下:

public partial class InitialCreate : DbMigration
{public override void Up(){CreateTable("dbo.StsPeople",c => new{Id = c.Int(nullable: false, identity: true),Name = c.String(),}).PrimaryKey(t => t.Id);CreateTable("dbo.StsTasks",c => new{Id = c.Long(nullable: false, identity: true),AssignedPersonId = c.Int(),Description = c.String(),CreationTime = c.DateTime(nullable: false),State = c.Byte(nullable: false),}).PrimaryKey(t => t.Id).ForeignKey("dbo.StsPeople", t => t.AssignedPersonId).Index(t => t.AssignedPersonId);            }public override void Down(){DropForeignKey("dbo.StsTasks", "AssignedPersonId", "dbo.StsPeople");DropIndex("dbo.StsTasks", new[] { "AssignedPersonId" });DropTable("dbo.StsTasks");DropTable("dbo.StsPeople");}
}

然后继续在“程序包管理器控制台”执行“Update-Database”,会自动在数据库创建相应的数据表:

PM> Update-Database

数据库显示如下:

(以后修改了实体,可以再次执行Add-MigrationUpdate-Database,就能很轻松的让数据库结构与实体类的同步)

定义仓储接口

通过仓储模式,可以更好把业务代码与数据库操作代码更好的分离,可以针对不同的数据库有不同的实现类,而业务代码不需要修改。

定义仓储接口的代码写到Core项目中,因为仓储接口是领域层的一部分。

我们先定义Task的仓储接口:

public interface ITaskRepository : IRepository<Task, long>
{List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
}

它继承自ABP框架中的IRepository泛型接口。

IRepository中已经定义了常用的增删改查方法:

所以ITaskRepository默认就有了上面那些方法。可以再加上它独有的方法GetAllWithPeople(...)。

不需要为Person类创建一个仓储类,因为默认的方法已经够用了。ABP提供了一种注入通用仓储的方式,将在后面“创建应用服务”一节的TaskAppService类中看到。

实现仓储类

我们将在EntityFramework项目中实现上面定义的ITaskRepository仓储接口。

通过模板建立的项目已经定义了一个仓储基类:SimpleTaskSystemRepositoryBase(这是一种比较好的实践,因为以后可以在这个基类中添加通用的方法)。

public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state){//在仓储方法中,不用处理数据库连接、DbContext和数据事务,ABP框架会自动处理。var query = GetAll(); //GetAll() 返回一个 IQueryable<T>接口类型//添加一些Where条件if (assignedPersonId.HasValue){query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);}if (state.HasValue){query = query.Where(task => task.State == state);}return query.OrderByDescending(task => task.CreationTime).Include(task => task.AssignedPerson).ToList();}
}

TaskRepository继承自SimpleTaskSystemRepositoryBase并且实现了上面定义的ITaskRepository接口。

创建应用服务(Application Services)

Application项目中定义应用服务。首先定义Task的应用服务层的接口:

public interface ITaskAppService : IApplicationService
{GetTasksOutput GetTasks(GetTasksInput input);void UpdateTask(UpdateTaskInput input);void CreateTask(CreateTaskInput input);
}

ITaskAppService继承自IApplicationService,ABP自动为这个类提供一些功能特性(比如依赖注入和参数有效性验证)。

然后,我们写TaskAppService类来实现ITaskAppService接口:

public class TaskAppService : ApplicationService, ITaskAppService
{private readonly ITaskRepository _taskRepository;private readonly IRepository<Person> _personRepository;/// <summary>/// 构造函数自动注入我们所需要的类或接口/// </summary>public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository){_taskRepository = taskRepository;_personRepository = personRepository;}public GetTasksOutput GetTasks(GetTasksInput input){//调用Task仓储的特定方法GetAllWithPeoplevar tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);//用AutoMapper自动将List<Task>转换成List<TaskDto>return new GetTasksOutput{Tasks = Mapper.Map<List<TaskDto>>(tasks)};}public void UpdateTask(UpdateTaskInput input){//可以直接Logger,它在ApplicationService基类中定义的Logger.Info("Updating a task for input: " + input);//通过仓储基类的通用方法Get,获取指定Id的Task实体对象var task = _taskRepository.Get(input.TaskId);//修改task实体的属性值if (input.State.HasValue){task.State = input.State.Value;}if (input.AssignedPersonId.HasValue){task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);}//我们都不需要调用Update方法//因为应用服务层的方法默认开启了工作单元模式(Unit of Work)//ABP框架会工作单元完成时自动保存对实体的所有更改,除非有异常抛出。有异常时会自动回滚,因为工作单元默认开启数据库事务。
    }public void CreateTask(CreateTaskInput input){Logger.Info("Creating a task for input: " + input);//通过输入参数,创建一个新的Task实体var task = new Task { Description = input.Description };if (input.AssignedPersonId.HasValue){task.AssignedPersonId = input.AssignedPersonId.Value;}//调用仓储基类的Insert方法把实体保存到数据库中
        _taskRepository.Insert(task);}
}

TaskAppService使用仓储进行数据库操作,它通往构造函数注入仓储对象的引用。

数据验证

如果应用服务(Application Service)方法的参数对象实现了IInputDtoIValidate接口,ABP会自动进行参数有效性验证。

CreateTask方法有一个CreateTaskInput参数,定义如下:

public class CreateTaskInput : IInputDto
{public int? AssignedPersonId { get; set; }[Required]public string Description { get; set; }
}

Description属性通过注解指定它是必填项。也可以使用其他 Data Annotation 特性。

如果你想使用自定义验证,你可以实现ICustomValidate 接口:

public class UpdateTaskInput : IInputDto, ICustomValidate
{[Range(1, long.MaxValue)]public long TaskId { get; set; }public int? AssignedPersonId { get; set; }public TaskState? State { get; set; }public void AddValidationErrors(List<ValidationResult> results){if (AssignedPersonId == null && State == null){results.Add(new ValidationResult("AssignedPersonId和State不能同时为空!", new[] { "AssignedPersonId", "State" }));}}
}

你可以在AddValidationErrors方法中写自定义验证的代码。

创建Web Api服务

ABP可以非常轻松地把Application Service的public方法发布成Web Api接口,可以供客户端通过ajax调用。

DynamicApiControllerBuilder.ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem").Build();

SimpleTaskSystemApplicationModule这个程序集中所有继承了IApplicationService接口的类,都会自动创建相应的ApiController,其中的公开方法,就会转换成WebApi接口方法。

可以通过http://xxx/api/services/tasksystem/Task/GetTasks这样的路由地址进行调用。


通过上面的案例,大致介绍了领域层、基础设施层、应用服务层的用法。

现在,可以在ASP.NET MVC的Controller的Action方法中直接调用Application Service的方法了。

如果用SPA单页编程,可以直接在客户端通过ajax调用相应的Application Service的方法了(通过创建了动态Web Api)。

由于时间关系,展现层没有在本文中介绍,将放到以后的文章介绍。后续文章中也将会详细介绍每一层的具体知识要点。

如果想立即看到更多展示,可以查看我以前的文章《新思想、新技术、新架构——更好更快的开发现代ASP.NET应用程序(续1)》,比较完整的演示了一个简单模块的开发,包括前后端各层的代码。(我自己项目用的ABP框架是在原作者的基础上做了一些修改,所以有些地方可能跟原作者的ABP不完全相同。)

由于演示一个完整的开发流程工作量巨大,写文章很难说得清楚,忙过这段时间我会准备用视频或YY在线的方式来分享,到时也可以分享我使用VS2013的Scaffolder+T4开发的代码生成器。

希望更多国内的架构师能关注到ABP这个项目,也许这其中有能帮助到您的地方,也许有您的参与,这个项目可以发展得更好。

欢迎加ABP架构设计交流QQ群:134710707

点这里进入ABP系列文章总目录

转载于:https://www.cnblogs.com/mienreal/p/4532077.html

ABP(现代ASP.NET样板开发框架)系列之2、ABP入门教程相关推荐

  1. Typescript系列(一):TS入门教程之简介、安装、编译、监听

    一个比java更script的语言,它是js的超集,它是Typescript. 一.typescript是什么? Typescript是javascript的一个超集,主要提供了类型系统和对ES6的支 ...

  2. Microchip PIC系列8位单片机入门教程(一)开发环境建立

    Microchip PIC系列8位单片机入门教程(一)开发环境建立 作者:Greg 第一节 开发环境建立条件 软件: (1)MPLAB X:集成开发环境下载地址:https://www.microch ...

  3. Microchip PIC系列8位单片机入门教程(四)中断

    Microchip PIC系列8位单片机入门教程(三) 第一节 知识点 (1)中断 PIC18F2420/2520/4420/4520 器件提供多个中断源及一个中断优先级功能,可以给大多数中断源分配高 ...

  4. MATLAB编程与应用系列-关于MATLAB编程入门教程的总体编写安排

    本系列教程来源于出版设计<基于MATLAB编程基础与典型应用书籍>,如涉及版权问题,请联系:156204968@qq.com. 出版社:人民邮电出版社, 页数:525. 本系列教程目前基于 ...

  5. pytorch 指定卡1_[原创][深度][PyTorch] DDP系列第一篇:入门教程

    引言 DistributedDataParallel(DDP)是一个支持多机多卡.分布式训练的深度学习工程方法.PyTorch现已原生支持DDP,可以直接通过torch.distributed使用,超 ...

  6. Asp.net三层结构原理与用意学习入门教程(一)

    接解asp.net已很久了,一直采用一个解决方案一个项目的方式去开发,所有的数据操作全放在一个类文件里,以为这就是所谓的三层结构,后来听说真正的大型网站的三层结构,应该是三个项目,或是更多的项目组成的 ...

  7. 《Hive系列》Hive详细入门教程

    目录 1 Hive基本概念 1.1 什么是Hive Hive简介 Hive:由FaceBook开源用于解决海量结构化日志的数据统计工具 Hive:基于Hadoop的一个数据仓库工具,可以将结构化的数据 ...

  8. Lucene系列之全局搜索引擎入门教程

    Lucene简介 Lucent:Apache软件基金会Jakarta项目组的一个子项目,Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻.在Java开发环境里Lucene是一个成熟 ...

  9. Nilearn教程系列(4)-fMRI解码入门教程(一)

    这是使用Nilearn解码的教程,它以Haxby 2001研究中猫辨别任务的数据为基础. fMRI解码入门教程 功能性磁共振成像(FMRI,functional magnetic resonance ...

最新文章

  1. android控件属性
  2. 用动态数组模拟双向循环链表
  3. python中的input函数怎么用_python input()函数怎么用,_Python_ 少侠科技
  4. caffe的python接口学习(2)生成solver文件
  5. 北斗导航 | 坐标转换:ECEF转LLA:GPS坐标系:WGS84(matlab代码)
  6. cocos2d-x游戏实例(25)-简易动作游戏(3)
  7. 根据RTL图编写Verilog程序
  8. 用递归计算一个数字每一位相加的结果。_leetcode 2 两数相加(c++)
  9. 常见数据结构与算法整理总结(下)
  10. java 注解 target_详解JDK 5 Annotation 注解之@Target的用法
  11. springboot启动时报错:Failed to load property source from location 'classpath:/application.yml'
  12. 车轮轨迹原理_倒车轮子原理是什么 倒车原理动画演示很有用
  13. 唐宇迪学习笔记2:Python数据分析处理库——pandas
  14. 通达信报计算机内存不足,导入通达信股票数据至内存数组中的方法和代码
  15. 【Web后端架构】2022年10个最佳Web开发后端框架
  16. Android 系统鼠标
  17. 什么是CC攻击,CC攻击的防御方式有哪些
  18. 【技术文章】Understanding Spectre Meltdown Vulnerability 1
  19. vue点击把某个区域变成图片数据
  20. 用python画小黄人

热门文章

  1. 报错:Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2
  2. 2019牛客暑期多校训练营(第七场)A String(暴力)
  3. Unity基础之:UnityAPI的学习
  4. (双指针、二分Binary Search) leetcode 658. Find K closest Elements
  5. django ORM相关的那些操作汇总
  6. Linux中的SELinux详解--16
  7. Android简化xml sax解析
  8. Spring的IOC原理
  9. 小幽默也有大道理:哲理幽默15则
  10. python3.6+qt designer一系列配置