演示程序截圖如上所示.
內容摘要
- 介紹
- 使用 boilerplate 模板創建程序
- 創建實體對象
- 創建DbContext
- 創建數據庫遷移
- 定義庫
- 實現庫
- 構建應用程序服務
- 構建 Web API 服務
- 開發SPA
- 本地化
- 單元測試
- 摘要
- 文章歷史
- 參考
介紹
在這篇文章, 我將基於以下框架演示如何開發單頁面的(SPA) 應用程序 :
- ASP.NET MVC 和 ASP.NET Web API – web站點的基礎框架.
- Angularjs – SPA 框架.
- EntityFramework – ORM (Object-Relational Mapping) 框架
- Castle Windsor – 依賴注入框架.
- Twitter Bootstrap – 前端框架.
- Log4Net 來記錄日志, AutoMapper 實體對象映射.
- 和 ASP.NET Boilerplate 作為應用程序模板框架.
ASP.NET Boilerplate [1] 是一個開源的應用程序框架,它包含了一些常用的組件讓您能夠快速簡單的開發應用. 它集成一些常用的基礎框架. 比如依賴注入, 領域驅動設計 和分層架構. 本應用演示ABP如何實現驗證,異常處理,本地化和響應式設計.
使用 boilerplate 模板創建程序
ASP.NET Boilerplate給我們提供了一個非常好的構建企業應用的模板,以便節約我們構建應用程序的時間。
在www.aspnetboilerplate.com/Templates目錄,我們可以使用模板創建應用。
這里我選擇 SPA(單頁面程序)使用AngularJs , EntityFramework框架. 然后輸入項目名稱SimpleTaskSystem.來創建和下載應用模版.下載的模版解決方案包含5個項目. Core 項目是領域 (業務) 層, Application 項目是應用層, WebApi 項目實現了 Web Api 控制器, Web 項目是展示層,最后EntityFramework 項目實現了EntityFramework框架.
Note: 如果您下載本文演示實例, 你會看到解決方案有7個項目. 我把NHibernate和Durandal都放到本演示中.如果你對NHibernate,Durandal不感興趣的話,可以忽略它們.
創建實體對象
我將創建一個簡單的應用程序來演示任務和分配任務的人. 所以我需要創建Task實體對象和Person實體對象.
Task實體對象簡單的定義了一些描述:CreationTime和Task的狀態. 它同樣包含了Person(AssignedPerson)的關聯引用:
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; } }
ASP.NET Boilerplate 給 Entity 類定義了 Id 屬性. 從Entity 類派生實體對象將繼承Id屬性. Task 類從 Entity<long>派生將包含 long 類型的ID. Person 類包含 int 類型的ID. 因為int是默認的主鍵類型, 這里我不需要特殊指定.
我在這個 Core 項目里添加實體對象因為實體對象是屬於領域/業務層的.
創建 DbContext
眾所周知, EntityFramework使用DbContext 類工作. 我們首先得定義它. ASP.NET Boilerplate創建了一個DbContext模板給我們. 我們只需要添加 IDbSets給 Task and Person. 完整的 DbContext 類如下:
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) { } }
我們還需要在web.config添加默認的連接字符串. 如下:
<add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystem; Trusted_Connection=True;" providerName="System.Data.SqlClient" />
創建數據庫遷移
我們將使用EntityFramework的Code First模式來遷移和創建數據庫. ASP.NET Boilerplate模板默認支持簽約但需要我們添加如下的Configuration 類:
internalinternal 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"} ); } }
另一種方法, 是在初始化的是添加4個Person. 我將創建初始遷移.打開包管理控台程序並輸入以下命令:
Add-Migration “InitialCreate” 命令創建 InitialCreate 類如下:
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"); } }
我們已經創建了數據庫類, 但是還沒創建那數據庫. 下面來創建數據庫,命令如下:
PM> Update-Database
這個命令幫我們創建好了數據庫並填充了初始數據:
當我們修改實體類時, 我們可以通過 Add-Migration 命令很容易的創建遷移類。需要更新數據庫的時候則可以通過 Update-Database 命令. 關於更多的數據庫遷移, 可以查看 entity framework的官方文檔.
定義庫
在領域驅動設計中, repositories 用於實現特定的代碼. ASP.NET Boilerplate 使用 IRepository 接口給每個實體自動的創建 repository . IRepository 定義了常用的方法如 select, insert, update, delete 等,更多如下:
我們還可以根據我們的需要來擴展 repository . 如果需要單獨的實現接口的話,首先需要繼承 repositories 接口. Task repository 接口如下:
public interface ITaskRepository : IRepository<Task, long> { List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state); }
這繼承了ASP.NET Boilerplate 的 IRepository 接口. ITaskRepository 默認定義了這些方法. 我們也可以添加自己的方法 GetAllWithPeople(…).
這里不需要再為Person創建 repository 了,因為默認的方法已經足夠. ASP.NET Boilerplate 提供了通用的 repositories 而不需要創建 repository 類. 在’構建應用程序服務層’ 章節中的TaskAppService 類中將演示這些..
repository 接口被定義在Core 項目中因為它們是屬於領域/業務層的.
實現庫
我們需要實現上述的 ITaskRepository 接口. 我們在EntityFramework 項目實現 repositories. 因此,領域層完全獨立於 EntityFramework.
當我們創建項目模板, ASP.NET Boilerplate 在項目中為 repositories 定義了一些基本類: SimpleTaskSystemRepositoryBase. 這是一個非常好的方式來添加基本類,因為我們可以為repositories稍后添加方法. 你可以看下面代碼定義的這個類.定義TaskRepository 從它派生:
public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository { public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state) { //In repository methods, we do not deal with create/dispose DB connections, DbContexes and transactions. ABP handles it. var query = GetAll(); //GetAll() returns IQueryable<T>, so we can query over it. //var query = Context.Tasks.AsQueryable(); //Alternatively, we can directly use EF's DbContext object. //var query = Table.AsQueryable(); //Another alternative: We can directly use 'Table' property instead of 'Context.Tasks', they are identical. //Add some Where conditions... 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) //Include assigned person in a single query .ToList(); } }
上述代碼 TaskRepository 派生自 SimpleTaskSystemRepositoryBase 並實現了 ITaskRepository.
構建應用程序服務層
應用程序服務層被用於分離表示層和領域層並提供一些界面的樣式方法. 在 Application 組件中定義應用程序服務. 首先定義 task 應用程序服務的接口:
public interface ITaskAppService : IApplicationService { GetTasksOutput GetTasks(GetTasksInput input); void UpdateTask(UpdateTaskInput input); void CreateTask(CreateTaskInput input); }
ITaskAppService繼承自IApplicationService. 因此ASP.NET Boilerplate自動的提供了一些類的特性(像依賴注入和驗證).現在我們來實現ITaskAppService:
public class TaskAppService : ApplicationService, ITaskAppService { //These members set in constructor using constructor injection. private readonly ITaskRepository _taskRepository; private readonly IRepository<Person> _personRepository; /// <summary> ///In constructor, we can get needed classes/interfaces. ///They are sent here by dependency injection system automatically. /// </summary> public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository) { _taskRepository = taskRepository; _personRepository = personRepository; } public GetTasksOutput GetTasks(GetTasksInput input) { //Called specific GetAllWithPeople method of task repository. var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State); //Used AutoMapper to automatically convert List<Task> to List<TaskDto>. return new GetTasksOutput { Tasks = Mapper.Map<List<TaskDto>>(tasks) }; } public void UpdateTask(UpdateTaskInput input) { //We can use Logger, it's defined in ApplicationService base class. Logger.Info("Updating a task for input: " + input); //Retrieving a task entity with given id using standard Get method of repositories. var task = _taskRepository.Get(input.TaskId); //Updating changed properties of the retrieved task entity. if (input.State.HasValue) { task.State = input.State.Value; } if (input.AssignedPersonId.HasValue) { task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value); } //We even do not call Update method of the repository. //Because an application service method is a 'unit of work' scope as default. //ABP automatically saves all changes when a 'unit of work' scope ends (without any exception). } public void CreateTask(CreateTaskInput input) { //We can use Logger, it's defined in ApplicationService class. Logger.Info(