演示程序截圖如上所示.javascript
在這篇文章, 我將基於如下框架演示如何開發單頁面的(SPA) 應用程序 :html
ASP.NET Boilerplate [1] 是一個開源的應用程序框架,它包含了一些經常使用的組件讓您可以快速簡單的開發應用. 它集成一些經常使用的基礎框架. 好比依賴注入, 領域驅動設計 和分層架構. 本應用演示ABP如何實現驗證,異常處理,本地化和響應式設計.前端
ASP.NET Boilerplate給咱們提供了一個很是好的構建企業應用的模板,以便節約咱們構建應用程序的時間。
在www.aspnetboilerplate.com/Templates目錄,咱們可使用模板建立應用。java
這裏我選擇 SPA(單頁面程序)使用AngularJs , EntityFramework框架. 而後輸入項目名稱SimpleTaskSystem.來建立和下載應用模版.下載的模版解決方案包含5個項目. Core 項目是領域 (業務) 層, Application 項目是應用層, WebApi 項目實現了 Web Api 控制器, Web 項目是展現層,最後EntityFramework 項目實現了EntityFramework框架.git
Note: 若是您下載本文演示實例, 你會看到解決方案有7個項目. 我把NHibernate和Durandal都放到本演示中.若是你對NHibernate,Durandal不感興趣的話,能夠忽略它們.github
我將建立一個簡單的應用程序來演示任務和分配任務的人. 因此我須要建立Task實體對象和Person實體對象.web
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:c#
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是默認的主鍵類型, 這裏我不須要特殊指定.api
我在這個 Core 項目裏添加實體對象由於實體對象是屬於領域/業務層的.
衆所周知, 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("Creating a task for input: "