ABP 教程文档 1-1 手把手引进门之 AngularJs, ASP.NET MVC, Web API 和 EntityFramework(官方教程翻译版 版本3.2.5)含学习资料
Posted yabu007
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ABP 教程文档 1-1 手把手引进门之 AngularJs, ASP.NET MVC, Web API 和 EntityFramework(官方教程翻译版 版本3.2.5)含学习资料相关的知识,希望对你有一定的参考价值。
本文是ABP官方文档翻译版,翻译基于 3.2.5 版本 转载请注明出处:http://www.cnblogs.com/yabu007/ 谢谢
官方文档分四部分
一、 教程文档
二、ABP 框架
三、zero 模块
四、其他(中文翻译资源)
本篇是第一部分的第一篇。
第一部分分三篇
1-1 手把手引进门
1-2 进阶
1-3 杂项 (相关理论知识)
第一篇含两个步骤。
1-1-1 ASP.NET Core & Entity Framework Core 后端(内核)含两篇 ( 第一篇链接 第二篇链接)
1-1-2 ASP.NET MVC, Web API, EntityFramework & AngularJs 前端
现在进入正文
使用 AngularJs, ASP.NET MVC, Web API 和 EntityFramework 创建N层单页Web应用
译者注:本文的最新更新时间是2016年10月,文章的内容与实际最新的样例模版已经不同。请读者注意区别。
翻译末尾我会再加一个栏目,提供推荐的 Angular 学习资料,毕竟2017年9月已经发布 Angular5 了。
土牛语录:
使用 AngularJs , ASP.NET MVC , Web API , EntityFramework 和 ASP.NET Boileplate 创建一个 N 层的,本地化的,良好架构的单页面 Web 应用。
样例程序的截图如上。
目录
介绍
用模板创建应用
创建实体 entities
创建数据库上下文 DbContext
创建数据库迁移
定义仓储 repositories
实现仓储
创建应用服务
创建 Web API 服务
开发单页程序 SPA
本地化
单元测试
总结
文章更改历史
引用
版权所有
Angular 学习资料
介绍
在这篇文章中,我们会展示如何用以下工具从底层到顶层逐步的开发一个单页面 Web 应用程序(SPA)
- ASP.NET MVC 和 ASP.NET Web API 作为 web 框架
- Angularjs 作为单页面 SPA 框架
- EntityFramework 作为 ORM (对象关系映射) 框架
- Castle Windsor 作为依赖注射框架
- Twitter Bootstrap 作为 html/CSS 框架
- Log4Net 作为日志记录, AutoMapper 作为对象映射工具。
- ASP.NET Boilerplate 作为启动模板和应用程序框架
ASP.NET Boilerplate [1] 是一个开源的应用程序框架,它把以上所有的框架和类库合并到一起,让我们可以轻松的开始开发我们的应用程序。这是一个最佳实践的基础框架,我们可以用它来开发应用程序。它天然的支持依赖注射 Dependency Injection ,领域模型设计 Domain Driven Design 和 分层架构 Layered Architecture 。样例应用程序夜实现了验证 Validation , 异常处理 exception handling , 本地化 localization 和 响应式设计 responsive design。
用模板创建应用
ASP.NET Boilerplate 会生成模板,模板绑定并配置好很多搭建企业级 web 应用最好的工具,这让我们在开始创建一个新应用程序时可以节约很多时间。
让我们从 aspnetboilerplate.com/Templates 开始生成模板创建我们的应用程序吧。
译者注:友情提示:上面的页面是旧版本的。新版本请参考 ASP.NET Core & Entity Framework Core 第一篇链接
在上图中,我们选择 SPA (单页面程序)AngularJs 和 EntityFramework 。依然使用 SimpleTaskSystem 作为我们的项目名字。点击创建后会生成并下载我们的解决方案。
解决方案中有5个项目。 .core 项目是领域(业务)层, Application 项目是应用层, WebApi 项目实现 Web Api 控制器, Web 项目是展现层,最后 EntityFramework 项目是 EntityFramework 数据库的实现,是基础设施层。
友情提示:如果你下载本文的样例解决方案(译者注:文章开头的样例下载链接),你会看到解决方案有7个项目。那是因为我们将样例修改为支持 NHibernate 和 Durandal 。如果你对 NHibernate 或者 Durandal 不感兴趣,请忽略。
(译者注:如果从官网下载,.Net Core 必须选择带 zero 模块才能下载 Angular 版本,不选择 zero 模块必须使用 .Net MVC 5.X,如图)
创建实体 entities
我们创建一个简单的应用服务,这个服务创建任务并把任务指派给责任人。所以,我们需要任务 Task 和责任人 People 实体。
任务 Task 实体定义很简单,包含任务的描述 Description , 创建时间 CreationTime ,状态 State 。还有一个可选的责任人 Person (责任人 AssignedPerson)引用:
代码如下
1 public class Task : Entity<long> 2 { 3 [ForeignKey("AssignedPersonId")] 4 public virtual Person AssignedPerson { get; set; } 5 6 public virtual int? AssignedPersonId { get; set; } 7 8 public virtual string Description { get; set; } 9 10 public virtual DateTime CreationTime { get; set; } 11 12 public virtual TaskState State { get; set; } 13 14 public Task() 15 { 16 CreationTime = DateTime.Now; 17 State = TaskState.Active; 18 } 19 }
责任人 Person 实体仅仅定义了责任人的名字 :
代码如下
1 public class Person : Entity 2 { 3 public virtual string Name { get; set; } 4 }
ASP.NET Bolierplate 提供预定了 Id 属性的 Entity 类。我们的实体从 Entity 类继承。任务 Task 类从 Entity<long> 继承,所以它的 Id 类型是 long 。责任人 Person 类的 Id 类型是 int 。因为我们没有指定其他类型,所以责任人类使用了默认的主键类型 int 。
由于实体属于领域层/业务层,所以我们在 Core 项目下定义实体。
创建数据库上下文 DbContext
总所周知,EntityFramework 通过 DbContext 类与数据库连接。我们首先来定义 DbContext 。 ASP.NET Boilerplate 模版已经为我们创建了 DbContext 模板。我们只需要把实体 Task 和 Person 的 IDbSets 加上去就可以。这是我们的 DbContext 类:
代码如下
1 public class SimpleTaskSystemDbContext : AbpDbContext 2 { 3 public virtual IDbSet<Task> Tasks { get; set; } 4 5 public virtual IDbSet<Person> People { get; set; } 6 7 public SimpleTaskSystemDbContext() 8 : base("Default") 9 { 10 11 } 12 13 public SimpleTaskSystemDbContext(string nameOrConnectionString) 14 : base(nameOrConnectionString) 15 { 16 17 } 18 }
它将会使用 web.config 中的默认 Default 连接字符串。 它定义如下:
代码如下
1 <add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystem; Trusted_Connection=True;" providerName="System.Data.SqlClient" />
创建数据库迁移
我们使用 EntityFramework 的 代码优先模式 Code First 迁移来创建和维护数据库结构。 ASP.NET Boilerplate 模版支持默认迁移并使用配置 Configuration 类。
代码如下
1 internalinternal sealed class Configuration : DbMigrationsConfiguration<SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext> 2 { 3 public Configuration() 4 { 5 AutomaticMigrationsEnabled = false; 6 } 7 8 protected override void Seed(SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext context) 9 { 10 context.People.AddOrUpdate( 11 p => p.Name, 12 new Person {Name = "Isaac Asimov"}, 13 new Person {Name = "Thomas More"}, 14 new Person {Name = "George Orwell"}, 15 new Person {Name = "Douglas Adams"} 16 ); 17 } 18 }
在 Seed 方法中,我们加入4个人作为初始数据。 然后,我们开始创建 初始迁移 initial migration 。 我们打开程序包管理控制台 Package Manager Console 并输入以下命令,如图:
(译者注:如果这部分有问题,请参照ASP.NET Core MVC 的第一篇,默认项目必须选 EntityFramework 项目)
输入命令 Add-Migration "InitialCreate" 创建一个名为 InitialCreate 的类。
代码如下
1 public partial class InitialCreate : DbMigration 2 { 3 public override void Up() 4 { 5 CreateTable( 6 "dbo.StsPeople", 7 c => new 8 { 9 Id = c.Int(nullable: false, identity: true), 10 Name = c.String(), 11 }) 12 .PrimaryKey(t => t.Id); 13 14 CreateTable( 15 "dbo.StsTasks", 16 c => new 17 { 18 Id = c.Long(nullable: false, identity: true), 19 AssignedPersonId = c.Int(), 20 Description = c.String(), 21 CreationTime = c.DateTime(nullable: false), 22 State = c.Byte(nullable: false), 23 }) 24 .PrimaryKey(t => t.Id) 25 .ForeignKey("dbo.StsPeople", t => t.AssignedPersonId) 26 .Index(t => t.AssignedPersonId); 27 } 28 29 public override void Down() 30 { 31 DropForeignKey("dbo.StsTasks", "AssignedPersonId", "dbo.StsPeople"); 32 DropIndex("dbo.StsTasks", new[] { "AssignedPersonId" }); 33 DropTable("dbo.StsTasks"); 34 DropTable("dbo.StsPeople"); 35 } 36 }
我们已经完成了创建数据库所必需的类了,但是我们还没创建数据库。创建数据库必需输入以下的命令:
代码如下
1 PM> Update-Database
(译者注:命令在刚才的程序包管理控制台里输入。即输入 Add-Migration 的地方)
这个命令执行迁移,创建数据库并填充初始数据。
当我们修改实体类后,我们可以很容易的创建新的迁移类,输入 Add-Migration 命令然后再输入 Update-Database 命令来更新数据库。 如果对数据库迁移感兴趣的,可以参考 entity framework 文档。
定义仓储 repositories
在领域设计模式中,仓储是用于实现数据库操作的指定代码。 ASP.NETBoilerplate 定义了范型的 IRepository 接口, 它为每个实体创建了自动化的仓储。 IRepository 定义了很多公用方法,比如 select ,insert ,update , delete 等等,如图:
在我们需要的时候我们可以扩展这些仓储 。 我们来扩展它并创建一个任务 Task 仓储。依据接口实现分离约定,我们首先声明仓储的借口, 任务 Task 的仓储接口如下:
代码如下
1 public interface ITaskRepository : IRepository<Task, long> 2 { 3 List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state); 4 }
代码拓展了 ASP.NETBoilerplate 的范型 IRepository 接口。所以, ITaskRepository 默认就包含所有这些方法的定义。它只需要编码自定义的方法 GetAllWithPeople(...).
至于责任人 Person 仓储就无需再创建了,因为默认的方法我们就够用了。 ASP.NET boilerplate 无需创建仓储类,通过反射范型仓储就可以用了。我们将在创建应用服务章节的任务应用服务 TaskAppService 类进行展示。
仓储接口是领域层/应用层的一部分,所以我们在 Core 项目下进行定义。
实现仓储
我们来实现刚才定义的 ITaskRepository 接口。我们将在 EntityFramework 项目实现仓储类。 这样,领域层将完全独立于基础设施层 EntityFramework 。
当我们创建模版时, ASP.NET Boilerplate 在我们的项目中自动创建了仓储范型类 : SimpleTaskSystemRepositoryBase 。 创建这个基类是一种最佳实践的做法,我们可以在以后为我们的仓储类添加一些公用的方法。我们可以在代码里看到这个基类的定义。我们实现的 TaskRepository 就从这个基类继承。
代码如下
1 public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository 2 { 3 public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state) 4 { 5 //In repository methods, we do not deal with create/dispose DB connections, DbContexes and transactions. ABP handles it. 6 7 var query = GetAll(); //GetAll() returns IQueryable<T>, so we can query over it. 8 //var query = Context.Tasks.AsQueryable(); //Alternatively, we can directly use EF\'s DbContext object. 9 //var query = Table.AsQueryable(); //Another alternative: We can directly use \'Table\' property instead of \'Context.Tasks\', they are identical. 10 11 //Add some Where conditions... 12 13 if (assignedPersonId.HasValue) 14 { 15 query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value); 16 } 17 18 if (state.HasValue) 19 { 20 query = query.Where(task => task.State == state); 21 } 22 23 return query 24 .OrderByDescending(task => task.CreationTime) 25 .Include(task => task.AssignedPerson) //Include assigned person in a single query 26 .ToList(); 27 } 28 }
任务仓储 TaskRepository 继承于 SimpleTaskSystemRepositoryBase 并实现了 ITaskRepository 接口。
GetAllWithPeople 是我们自定义的方法,该方法获取任务,并附带任务的责任人(预先绑定),该任务可通过设定条件进行过滤。我们可以在仓储里自由的使用数据库和数据库上下文 Context ( EF 的 DbContext )对象。ASP.NET Boilerplate 为我们管理数据库连接,事务,创建和销毁数据库上下文 DbContext (详情参见 documentation)
创建应用服务
应用服务通过分层方法把展示层和领域层分开。 我们在项目的应用程序集里定义了应用服务。首先,我们为任务应用服务定义接口:
代码如下
1 public interface ITaskAppService : IApplicationService 2 { 3 GetTasksOutput GetTasks(GetTasksInput input); 4 void UpdateTask(UpdateTaskInput input); 5 void CreateTask(CreateTaskInput input); 6 }
(译者注:在.net core 系列里, input 和 output 都已经替换为 ResultDto 了。 建议官网下载最新的版本)
接口 ITaskAppService 拓展了 IApplicationService 。 ASP.NET Boilerplate 自动为这个类提供了一些特性 (比如依赖注入 dependency injection 和 验证 validation )。现在,让我们来实现 ITaskAppService 接口
代码如下
1 public class TaskAppService : ApplicationService, ITaskAppService 2 { 3 //These members set in constructor using constructor injection. 4 5 private readonly ITaskRepository _taskRepository; 6 private readonly IRepository<Person> _personRepository; 7 8 /// <summary> 9 ///In constructor, we can get needed classes/interfaces. 10 ///They are sent here by dependency injection system automatically. 11 /// </summary> 12 public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository) 13 { 14 _taskRepository = taskRepository; 15 _personRepository = personRepository; 16 } 17 18 public GetTasksOutput GetTasks(GetTasksInput input) 19 { 20 //Called specific GetAllWithPeople method of task repository. 21 var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State); 22 23 //Used AutoMapper to automatically convert List<Task> to List<TaskDto>. 24 return new GetTasksOutput 25 { 26 Tasks = Mapper.Map<List<TaskDto>>(tasks) 27 }; 28 } 29 30 public void UpdateTask(UpdateTaskInput input) 31 { 32 //We can use Logger, it\'s defined in ApplicationService base class. 33 Logger.Info("Updating a task for input: " + input); 34 35 //Retrieving a task entity with given id using standard Get method of repositories. 36 var task = _taskRepository.Get(input.TaskId); 37 38 //Updating changed properties of the retrieved task entity. 39 40 if (input.State.HasValue) 41 { 42 task.State = input.State.Value; 43 } 44 45 if (input.AssignedPersonId.HasValue) 46 { 47 task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value); 48 } 49 50 //We even do not call Update method of the repository. 51 //Because an application service method is a \'unit of work\' scope as default. 52 //ABP automatically saves all changes when a \'unit of work\' scope ends (without any exception). 53 } 54 55 public void CreateTask(CreateTaskInput input) 56 { 57 //We can use Logger, it\'s defined in ApplicationService class. 58 Logger.Info("Creating a task for input: " + input); 59 60 //Creating a new Task entity with given input\'s properties 61 var task = new Task { Description = input.Description }; 62 63 if (input.AssignedPersonId.HasValue) 64 { 65 task.AssignedPersonId = input.AssignedPersonId.Value; 66 } 67 68 //Saving entity with standard Insert method of repositories. 69 _taskRepository.Insert(task); 70 } 71 }
任务应用服务 TaskAppService 使用仓储来操作数据库。它通过构造函数注入模式从构造函数获得引用。 ASP.NET Boilerplate 天然实现依赖注入,所以我们可以自由的使用构造函数注入和属性注入(更多依赖注入详情请参照 ASP.NET Boilerplate documentation 文档)
友情提示,我们通过 反射 IRepository<Person> 使用责任人仓储 PersonRepository 。 ASP.NET Boilerplate 自动为我们的实体创建了仓储。我们无需创建仓储类因为默认的 IRepository 接口的方法已经足够我们用了。
应用服务方法使用数据传输对象 Data Transfer Objects (DTOs)。 这是最佳实践的一种。我们强烈推荐使用这种方法。如果你在将实体暴露给展示层这个问题上有自己的处理方式的话,请按你自己的方式来,无需一定使用 DTO。
在 GetTasks 方法里, 我们使用 GetAllWithPeople 方法。它会返回一个 LIst<Task> , 但我需要返回给展示层的是 ListMTaskDto> 。AutoMapper 可以帮我们自动的将 Task 对象转换为 TaskDto 对象。 GetTasksInput 和 GetTasksOutput 是专门为 GetTasks 方法定义的特殊 DTOs 。
在 CreateTask 方法里, 我们简单的创建了一个新的任务并使用 IRepository 的插入方法将它插入了数据库。
ASP.NET Boilerplate 的 ApplicationService 类有一些方法可以让我们更容易的开发应用服务。例如,它定义了记录日志的 Logger 属性。所以,由于我们的 TaskAppService 是从 ApplicationService 继承的,我们可以直接使用 Logger 属性。是否从这个类继承是可选的,但必需实现 IApplicationService (友情提示,ITaskAppService 拓展了 IApplicationService ,由于这个类实现了 ITaskAppService 也就实现了 IApplicationService )
验证
ASP.NET Boilerplate 自动验证应用服务方法的输入参数。 CreateTask 方法将 CreateTaskInput 作为输入参数
代码如下
1 public class CreateTaskInput 2 { 3 public int? AssignedPersonId { get; set; } 4 5 [Required] 6 public string Description { get; set; } 7 }
在这,描述 Description 被标记为必需的 Required 。 你可以使用更多的数据注释属性,请参考 Data Annotation attributes 。 如果你想做一些定制验证,你可以实现 ICustomValidate , 就像我在 UpdateTaskInput 里实现的
代码如下
1 public class UpdateTaskInput : ICustomValidate 2 { 3 [Range(1, long.MaxValue)] 4 public long TaskId { get; set; } 5 6 public int? AssignedPersonId { get; set; } 7 8 public TaskState? State { get; set; } 9 10 public void AddValidationErrors(List<ValidationResult> results) 11 { 12 if (AssignedPersonId == null && State == null) 13 { 14 results.Add(new ValidationResult("Both of AssignedPersonId and State can not be null in order to update a Task!", new[] { "AssignedPersonId", "State" })); 15 } 16 } 17 18 public override string ToString() 19 { 20 return string.Format("[UpdateTask > TaskId = {0}, AssignedPersonId = {1}, State = {2}]", TaskId, AssignedPersonId, State); 21 } 22 }
AddValidationErrors 方法是你编写你自己的定制验证代码的地方。
处理异常
友情提示,我们不处理任何异常。 ASP.NET Boilerplate 自动处理异常,日志并返回一个恰当的错误信息给客户端。同理,在客户端,自动的处理这些错误信息并展示给客户。实际上,这对 ASP.NET MVC 和 Web API 控制器操作来说是合理的。 我们将使用 Web API 来暴露任务管理服务 TaskAppService , 我们无需处理异常。 细节请参考 exception handling 文档。
创建 Web API 服务
我们将我们的应用服务暴露给远程客户端。这样,我们的 Angularjs 应用程序可以使用 AJAX 轻松的调用这些服务方法。
ASP.NET Boilerplate 提供了自动化方法将我们的应用服务方法暴露为 ASP.NET Web API 。我们使用 DynamicApiControllerBuilder
代码如下
1 DynamicApiControllerBuilder 2 .ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem") 3 .Build();
在这个例子里, ASP.NET Boilerplate 在应用层程序集里查找所有继承了 IApplicationService 接口的接口,然后为每个应用服务类创建一个 web api 控制器。这是精细控制的替代语法。我们来看看怎么使用 AJAX 调用这些服务。
开发单页程
以上是关于ABP 教程文档 1-1 手把手引进门之 AngularJs, ASP.NET MVC, Web API 和 EntityFramework(官方教程翻译版 版本3.2.5)含学习资料的主要内容,如果未能解决你的问题,请参考以下文章