本文是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)引用:
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 实体仅仅定义了责任人的名字 :
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 类:
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 连接字符串。 它定义如下:
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 <add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystem; Trusted_Connection=True;" providerName="System.Data.SqlClient" />
创建数据库迁移
我们使用 EntityFramework 的 代码优先模式 Code First 迁移来创建和维护数据库结构。 ASP.NET Boilerplate 模版支持默认迁移并使用配置 Configuration 类。
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 的类。
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 }
我们已经完成了创建数据库所必需的类了,但是我们还没创建数据库。创建数据库必需输入以下的命令:
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 PM> Update-Database
(译者注:命令在刚才的程序包管理控制台里输入。即输入 Add-Migration 的地方)
这个命令执行迁移,创建数据库并填充初始数据。
当我们修改实体类后,我们可以很容易的创建新的迁移类,输入 Add-Migration 命令然后再输入 Update-Database 命令来更新数据库。 如果对数据库迁移感兴趣的,可以参考 entity framework 文档。
定义仓储 repositories
在领域设计模式中,仓储是用于实现数据库操作的指定代码。 ASP.NETBoilerplate 定义了范型的 IRepository 接口, 它为每个实体创建了自动化的仓储。 IRepository 定义了很多公用方法,比如 select ,insert ,update , delete 等等,如图:
在我们需要的时候我们可以扩展这些仓储 。 我们来扩展它并创建一个任务 Task 仓储。依据接口实现分离约定,我们首先声明仓储的借口, 任务 Task 的仓储接口如下:
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 就从这个基类继承。
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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)
创建应用服务
应用服务通过分层方法把展示层和领域层分开。 我们在项目的应用程序集里定义了应用服务。首先,我们为任务应用服务定义接口:
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 接口
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 作为输入参数
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 里实现的
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 DynamicApiControllerBuilder 2 .ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem") 3 .Build();
在这个例子里, ASP.NET Boilerplate 在应用层程序集里查找所有继承了 IApplicationService 接口的接口,然后为每个应用服务类创建一个 web api 控制器。这是精细控制的替代语法。我们来看看怎么使用 AJAX 调用这些服务。
开发单页程序 SPA
我们来实现一个单页面(SPA) Web 应用来作为我们项目的用户界面。 AngularJS (Google 出品)是 SPA 框架最有用的一个(也许是最好的一个)。
ASP.NET Boilerplate 提供一个模版,使我们可以轻松的开始使用 AngularJs 。 模版有可以平滑切换的两个页面(主页和关于页)。 使用 Twitter 的 Bootstrap 作为 HTML/CSS 框架。(所以,这是基于响应模式的。)。 当然,ASP.NET Boilerplate 的本地化系统可以让我们自由切换英语和土耳其语 (我们也可以很容易的添加其他语言或者删除掉)
我们首先修改模版的路由。 ASP.NET Boilerplate 模版使用 AngularUI-Router 路由器。这是 AngularJs 的标准路由器 de-facto 。 它是基于状态的路由模式。我们将有两个视图: 任务列表 task list 和新任务 new task 。 所以, 我们需要修改 app.js 中定义的路由。
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 app.config([ 2 '$stateProvider', '$urlRouterProvider', 3 function ($stateProvider, $urlRouterProvider) { 4 $urlRouterProvider.otherwise('/'); 5 $stateProvider 6 .state('tasklist', { 7 url: '/', 8 templateUrl: '/App/Main/views/task/list.cshtml', 9 menu: 'TaskList' //Matches to name of 'TaskList' menu in SimpleTaskSystemNavigationProvider 10 }) 11 .state('newtask', { 12 url: '/new', 13 templateUrl: '/App/Main/views/task/new.cshtml', 14 menu: 'NewTask' //Matches to name of 'NewTask' menu in SimpleTaskSystemNavigationProvider 15 }); 16 } 17 ]);
app.js 是主要的 javascript 文件, 它配置和启动我们的 SPA 。友情提示, 我们使用 cshtml 文件作为视图。通常,在 AngularJs 中,我们使用 html 文件作为视图。 ASP.NET Boilerplate 让我们可以使用 cshtml 文件。所以我们可以使用 razor 引擎来生成 HTML 。
ASP.NET Boilerplate 基础架构支持在应用程序里创建和展示菜单 menus ,而且这很简单。你可以使用 C# 定义菜单,使用 C# 和 javascript 来使用菜单。请查看 SimpleTaskSystemNavigationProvide 类的代码,这里我们创建了菜单,然后请查看 header.js/header.cshtml 的代码, 这里我们展示了如何使用 angular 来展示菜单。
首先,我们为 任务列表 task list 视图创建一个 Angular 控制器 controller
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 (function() { 2 var app = angular.module('app'); 3 4 var controllerId = 'sts.views.task.list'; 5 app.controller(controllerId, [ 6 '$scope', 'abp.services.tasksystem.task', 7 function($scope, taskService) { 8 var vm = this; 9 10 vm.localize = abp.localization.getSource('SimpleTaskSystem'); 11 12 vm.tasks = []; 13 14 $scope.selectedTaskState = 0; 15 16 $scope.$watch('selectedTaskState', function(value) { 17 vm.refreshTasks(); 18 }); 19 20 vm.refreshTasks = function() { 21 abp.ui.setBusy( //Set whole page busy until getTasks complete 22 null, 23 taskService.getTasks({ //Call application service method directly from javascript 24 state: $scope.selectedTaskState > 0 ? $scope.selectedTaskState : null 25 }).success(function(data) { 26 vm.tasks = data.tasks; 27 }) 28 ); 29 }; 30 31 vm.changeTaskState = function(task) { 32 var newState; 33 if (task.state == 1) { 34 newState = 2; //Completed 35 } else { 36 newState = 1; //Active 37 } 38 39 taskService.updateTask({ 40 taskId: task.id, 41 state: newState 42 }).success(function() { 43 task.state = newState; 44 abp.notify.info(vm.localize('TaskUpdatedMessage')); 45 }); 46 }; 47 48 vm.getTaskCountText = function() { 49 return abp.utils.formatString(vm.localize('Xtasks'), vm.tasks.length); 50 }; 51 } 52 ]); 53 })();
我们将控制器 controller 命名为 ‘sts.views.task.list’ 。这么命名是我的习惯(方便拓展或重构代码),但你可以简单的命名为 ‘ListController’ 。 AngularJs 一样可以使用依赖注入。 我们在这反射 ‘$scope’ 和 ‘abp.services.tasksystem.task’ 。 第一个是 Angular 的 scope 变量,第二个是自动生成的 ITaskAppService (我们在创建 Web API 服务章节创建了这个接口) 的 javascript 服务代理 。
ASP.NET Boilerplate 的基础架构支持在服务端和客户端都使用相同的本地化 localization 文本 (详情请参照 localization 文档)
vm.tasks 是即将在视图里展示的任务列表。 vm.refreshTasks 方法调用 taskService 获取任务并填充到队列里。当 selectedTaskState 改变的时候(使用 $scope.$watch 监控)就会调用 vm.refreshTasks 方法。
就像你所看到的。调用应用服务方法是如此的容易和直接!这是 ASP.NET Boilerplate 的特性之一。它生成 Web API 层和与 Web API 层通讯的 Javascript 代理层。所以,我们调用应用服务方法就想调用 javascript 方法一样简单。这是与 AngularJs 的完整集成 (通过使用 Angular 的 $http 服务)。
让我们看下任务列表的视图代码。
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 <div class="panel panel-default" ng-controller="sts.views.task.list as vm"> 2 3 <div class="panel-heading" style="position: relative;"> 4 <div class="row"> 5 6 <!-- Title --> 7 <h3 class="panel-title col-xs-6"> 8 @L("TaskList") - <span>{{vm.getTaskCountText()}}</span> 9 </h3> 10 11 <!-- Task state combobox --> 12 <div class="col-xs-6 text-right"> 13 <select ng-model="selectedTaskState"> 14 <option value="0">@L("AllTasks")</option> 15 <option value="1">@L("ActiveTasks")</option> 16 <option value="2">@L("CompletedTasks")</option> 17 </select> 18 </div> 19 </div> 20 </div> 21 22 <!-- Task list --> 23 <ul class="list-group" ng-repeat="task in vm.tasks"> 24 <div class="list-group-item"> 25 <span class="task-state-icon glyphicon" ng-click="vm.changeTaskState(task)" ng-class="{'glyphicon-minus': task.state == 1, 'glyphicon-ok': task.state == 2}"></span> 26 <span ng-class="{'task-description-active': task.state == 1, 'task-description-completed': task.state == 2 }">{{task.description}}</span> 27 <br /> 28 <span ng-show="task.assignedPersonId > 0"> 29 <span class="task-assignedto">{{task.assignedPersonName}}</span> 30 </span> 31 <span class="task-creationtime">{{task.creationTime}}</span> 32 </div> 33 </ul> 34 35 </div>
ng-controller 特性(参见第一行)将视图和控制器绑在了一起。 @L("TaskList") 用于将 “任务列表 task list”进行本地化 (在服务端对 HTML 进行渲染)。这是 cshtml 文件可以做到的。
ng-model 将下拉框和 javascript 变量绑定在了一起。当变量改变时,下拉框就会更新。当下拉框改变了,变量也就更新了。这是 AngularJs 的双向绑定。
ng-repeat 是 Angular 中的另一个“指令”,用于传送相同的 HTML 给队列里的每个值。当队列改变时(比如添加了一个项目),它会自动反射到视图里。这是 AngularJs 另一个很强大的特性。
友情提示,当你添加一个 javascript 文件时(例如,为 ‘任务列表’控制器),你应该把它加到你的页面上。你可以在模版里的 Home\Index.cshtml 里添加它。
本地化
ASP.NET Boilerplate 拥有一个灵活且强大的本地化系统。你可以 XML 文件或资源文件作为本地化源文件。你也可以自定义本地化源文件。详情请参加 documentation 文档。在这个样例应用程序里,我们使用 XML 文件(路径是 web 应用程序的本地化文件夹里 Localization):
代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 <?xml version="1.0" encoding="utf-8" ?> 2 <localizationDictionary culture="en"> 3 <texts> 4 <text name="TaskSystem" value="Task System" /> 5 <text name="TaskList" value="Task List" /> 6 <text name="NewTask" value="New Task" /> 7 <text name="Xtasks" value="{0} tasks" /> 8 <text name="AllTasks" value="All tasks" /> 9 <text name="ActiveTasks" value="Active tasks" /> 10 <text name="CompletedTasks" value="Completed tasks" /> 11 <text name="TaskDescription" value="Task description" /> 12 <text name="EnterDescriptionHere" value="Task description" /> 13 <text name="AssignTo" value="Assign to" /> 14 <text name="SelectPerson" value="Select person" /> 15 <text name="CreateTheTask" value="Create the task" /> 16 <text name="TaskUpdatedMessage" value="Task has been successfully updated." /> 17 <text name="TaskCreatedMessage" value="Task {0} has been created successfully." /> 18 </texts> 19 </localizationDictionary>
单元测试
ASP.NET Boilerplate 的设计是可测试的。我们编写了一篇文章来展示 ABP 基本项目的单元测试和集成测试。文章请参见 Unit testing in C# using xUnit, Entity Framework, Effort and ASP.NET Boilerplate
总结
在这片文章里,我们展示了如何开发一个 N 层单页面的 SPA 基于响应式用户界面的 ASP.NET MVC web 应用程序。我们使用 ASP.NET Boilerplate 作为基础架构,因为它是基于最佳实践的,我们不止很容易就可以开发应用程序,而且相当的节约时间。更多信息请参考以下链接:
- 官网及文档 aspnetboilerplate.com
- 官方论坛 forum.aspnetboilerplate.com
- Github 仓库 github.com/aspnetboilerplate
- twitter @aspboilerplate
- 企业版模版 aspnetzero.com
文章更改历史
- 2016-10-26:升级样例项目到 ABP v1.0
- 2016-07-19:升级文章和样例项目到 ABP v0.10
- 2015-06-08:升级文章和样例项目到 ABP v0.6.3.1
- 2015-02-20:增加单元测试文章的链接,更新样例项目
- 2015-01-05: 升级样例项目到 ABP v0.5
- 2014-11-03:升级文章和样例项目到 ABP v0.4.1
- 2014-09-08:升级文章和样例项目到 ABP v0.3.2
- 2014-08-17: 升级样例项目到 ABP v0.3.1.2
- 2014-07-22:升级样例项目到 ABP v0.3.0.1
- 2014-07-08:添加 “Enable-Migrations”的命令行截图
- 2014-07-01: 首次发布文章
引用
[1] ABP.NET Boilerplate 官网 : http://www.aspnetboilerplate.com
版权所有
该文章和其中的任何源代码和文件的版权均归 The Code Project Open License (CPOL) 所有
Angular 学习资料
此部分信息为译者学习中使用的资料,与本文及作者无关。请按需使用。
Angular 英文官网 : https://angular.io/
Angular 中文官网 :https://angular.cn/ 中文官网在大版本上与英文官网同步,小版本上会落后一些。
阿里的 Angular 组件NG-ZORRO:https://ng.ant.design/#/docs/angular/introduce 分为 NG5 和 NG4 两个版本
百度的 Echarts 数据可视化组件 : http://echarts.baidu.com/
书籍推荐看官方的英文或中文文档:
推荐看 ng-book 最新版本,英文的 https://www.ng-book.com/2/
中文的只有 ng-book2 的翻译版 《Angular 权威教程》
最后推荐看 雪狼的 《AngularJS深度剖析与最佳实践》这本书写的是 Angular 1.0 ,感兴趣的可以重温一下
群和博客请参考
Nice Angular社区:QQ群
- 278252889( Nice Angular社区,2000人已满)
- 305739270( Nice Angular社区二群,2000人)
- 207542263( Nice Angular社区三群,1000人)
- 200242234( Angular 互助组,500人)
- 110455272( Ionic 开发实践,500人)
Nice Angular社区:微信公众号
- shuang_lang_shuo(由破狼、雪狼等人维护)