演示程序截图如上所示.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: "