ASP.NET 是. NET FrameWork 的一部分, 是一项微软公司的技术, 是一种使嵌入网页中的脚本可由因特网服务器执行的服务器端脚本技术, 它可以在通过 HTTP 请求文档时再在 web 服务器上动态创建它们 指 Active Server Pages(动态服务器页面) , 运行于 IIS(Internet Information Server 服务, 是 Windows 开发的 Web 服务器)之中的程序
ABP 是基于 Windows 系统上. NET Framework 环境的 Web 开发框架, 这里我们基于. NET 的 Visual Studio 开发环境, 来共同进入基于 ASP.NET MVC 的 ABP 框架入门学习教程
为什么使用 ABP
我们近几年陆续开发了一些 Web 应用和桌面应用, 需求或简单或复杂, 实现或优雅或丑陋一个基本的事实是: 我们只是积累了一些经验或提高了对, NET 的熟悉程度
随着软件开发经验的不断增加, 我们发现其实很多工作都是重复机械的, 而且随着软件复杂度的不断提升, 以往依靠经验来完成一些简单的增删改查的做法已经行不通了特别是用户的要求越来越高, 希望添加的功能越来多, 目前这种开发模式, 已经捉襟见肘我很难想象如何在现有的模式下进行多系统的持续集成并添加一些新的特性
开发一个系统时, 我们不可避免的会使用各种框架数据持久层实现日志 ASP.NET MVCIOC 以及自动映射等一个高质量的软件系统往往还有全局容错, 消息队列等组件
把上述这些组件组合到一起的时候, 其复杂度会急剧上升一般个人和小团队的技术水平, 很难设计出一个均衡协调的框架对于传统的所谓三层架构, 我也是很持怀疑态度的(月薪 15k 的程序员搞的三层架构, 我也仔细读过, 也是问题多多, 并不能解释为什么要使用三层)
其实, 我们无非是希望在编程的时候, 把大部分的注意力全部集中到业务实现上不要过多的考虑基础的软件结构上的种种问题应该有一个框框或者一种范式来提供基本的服务, 如日志容错和 AOP,DI 等
稍微正规一点的公司经过多年沉淀都形成了自己的内部软件框架, 他们在开发软件的时候并不是从一片空白开始的而是从一个非常牢固的基础平台上开始构建的这样大大提高了开发速度, 而且一种架构往往也决定了分工协作的模式我们目前之所以无法分工协作, 根本原因也是缺少一套成熟稳定的基础开发架构和工作流程
目前. NET 上有不少开源框架比如 Apworks 和 ABP 其中 Apworks 是中国人写的一套开源框架它是一个全功能的, 不仅可以写分布式应用, 也可以写桌面应用 ABP 的全称是 Asp.net boilerplate project(asp.net 样板工程)是 github 上非常活跃的一个开源项目它并没有使用任何新的技术, 只是由两名架构师将 asp.net 开发中常用的一些工具整合到了一起, 并且部分实现了 DDD 的概念是一个开箱即用的框架, 可以作为 asp.net 分布式应用的一个良好起点
使用框架当然有代价, 你必须受到框架强 API 的侵入, 抑或要使用他的方言而且这个框架想要吃透, 也要付出很大的学习成本但是好处也是显而易见的业界顶尖的架构师已经为你搭建好了一套基础架构, 很好的回应了关于一个软件系统应该如何设计, 如何规划的问题, 并且提供了一套最佳实践和范例
学习虽然要付出成本, 但是经过漫长的跋涉, 我们从一无所知已经站到了工业级开发的门槛上基于这个框架, 我们可以很好的来划分任务, 进行单元测试等大大降低了软件出现 BUG 的几率
从模板创建空的 web 应用程序
ABP 的官方网站: http://www.aspnetboilerplate.com
ABP 在 Github 上的开源项目: https://github.com/aspnetboilerplate
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) 并分配给人 我们需要 Task 和 Person 这两个实体
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 类型的 IdPerson 类有一个 int 类型的 Id, 因为 int 类型是 Entity 基类 Id 的默认类型, 没有特别指定类型时, 实体的 Id 就是 int 类型
创建 DbContext
使用 EntityFramework 需要先定义 DbContext 类, ABP 的模板已经创建了 DbContext 文件, 我们只需要把 Task 和 Person 类添加到 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-Migration 和 Update-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 仓储的特定方法 GetAllWithPeople
- var 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) 方法的参数对象实现了 IInputDto 或 IValidate 接口, 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)
来源: http://www.phperz.com/article/18/0318/352715.html