本文是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 調用這些服務。
開發單頁程序 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 中定義的路由。
代碼如下

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
代碼如下

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 服務)。
讓我們看下任務列表的視圖代碼。
代碼如下

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):
代碼如下

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(由破狼、雪狼等人維護)