上一篇 我們介紹了什么是ABP,這一篇我們通過原作者的”簡單任務系統”例子,演示如何運用ABP開發項目
創建實體
一般來說任務是需要分配給人來做的,所以我們創建兩個實體模型類:Task和Persion
using Abp.Domain.Entities; using Abp.Domain.Entities.Auditing; using System.ComponentModel.DataAnnotations.Schema; using System; namespace SimpleTaskSystem { public class Task : Entity, IHasCreationTime { [ForeignKey("AssignedPersonId")] public Person AssignedPerson { get; set; } public long? AssignedPersonId { get; set; } public string Description { get; set; } public TaskState State { get; set; } public DateTime CreationTime { get; set; } public Task() { State = TaskState.Active; } } }
using System; using Abp.Domain.Entities; using Abp.Domain.Entities.Auditing; namespace SimpleTaskSystem { public class Person : Entity, IHasCreationTime { public string Name { get; set; } public DateTime CreationTime { get; set; } } }
創建DbContext
使用EntityFramework需要先定義DbContext類,ABP的模板已經創建了DbContext文件,我們只需要把Task和Person類添加到IDbSet
using Abp.EntityFramework; using System.Data.Entity; namespace SimpleTaskSystem.EntityFramework { public class SimpleTaskSystemDbContext : AbpDbContext { //TODO: Define an IDbSet for each Entity... //Example: //public virtual IDbSet Users { get; set; } public virtual IDbSet Tasks { get; set; } public virtual IDbSet People { get; set; } /* NOTE: * Setting "Default" to base class helps us when working migration commands on Package Manager Console. * But it may cause problems when working Migrate.exe of EF. If you will apply migrations on command line, do not * pass connection string name to base classes. ABP works either way. */ public SimpleTaskSystemDbContext() : base("Default") { } /* NOTE: * This constructor is used by ABP to pass connection string defined in SimpleTaskSystemDataModule.PreInitialize. * Notice that, actually you will not directly create an instance of SimpleTaskSystemDbContext since ABP automatically handles it. */ public SimpleTaskSystemDbContext(string nameOrConnectionString) : base(nameOrConnectionString) { } } }
通過Database Migrations創建數據庫
我們使用EntityFramework的Code First模式創建數據庫架構。ABP模板生成的項目已經默認開啟了數據遷移功能,我們添加一些默認數據進去。
using System.Data.Entity.Migrations; namespace SimpleTaskSystem.Migrations { internal sealed class Configuration : DbMigrationsConfiguration { public Configuration() { AutomaticMigrationsEnabled = false; ContextKey = "SimpleTaskSystem"; } protected override void Seed(SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext context) { // This method will be called every time after migrating to the latest version. // You can add any seed data here... context.People.AddOrUpdate( p => p.Name, new Person { Name = "張三" }, new Person { Name = "王二" }, new Person { Name = "李四" }, new Person { Name = "老王" } ); } } }
然后打開 程序包管理器控制台 ,選擇默認項目並執行命令”Add-Migration InitialCreate”,此時會在Migrations目錄下生成一個以 當前時間_InitialCreate.cs 的文件。
然后繼續在 程序包管理器控制台 執行 Update-Database,等執行完畢則會在數據庫自動創建相應的數據表
定義倉儲接口
通過倉儲模式,可以更好把業務代碼與數據庫操作代碼更好的分離。我們把倉儲的代碼寫到 Core 項目中
using Abp.Domain.Repositories; using System.Collections.Generic; namespace SimpleTaskSystem.Repositorys { public interface ITaskRepository : IRepository<Task, long> { List GetAllWithPeople(long? assignedPersonId, TaskState? state); } }
我們可以為Persion類定義一個倉儲,也可以不定義,這里我選擇定義,雖然ABP默認提供的倉儲在本例中已經夠用了,但考慮到以后擴展方便,我們還是也給它定義一個。
using Abp.Domain.Repositories; namespace SimpleTaskSystem.Repositorys { public interface IPersionRepository : IRepository<Person, long> { } }
這樣后期我們要加一個自定義方法直接加在這里面就行了,不用改動太多。這里也介紹一下ABP自帶的倉儲方法,我們可以直接使用。
實現倉儲類
因為本例用的是EntityFramework,所以我們將在EntityFramework項目中實現上面定義的ITaskRepository倉儲接口。如果后期想換個ORM框架,比如NHibernate,我們就只要更換倉儲實現的這個項目即可。
TaskRepository.cs
using System.Collections.Generic;
using Abp.EntityFramework;
using SimpleTaskSystem.EntityFramework.Repositories;
using SimpleTaskSystem.Repositorys;
using System.Linq;
using System.Data.Entity;
namespace SimpleTaskSystem.EntityFramework.Repositorys
{
public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository { public TaskRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } public List GetAllWithPeople(long? assignedPersonId, TaskState? state) { //在倉儲方法中,不用處理數據庫連接、DbContext和數據事務,ABP框架會自動處理。 var query = GetAll(); //返回一個 IQueryable接口類型 //添加一些Where條件 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) .ToList(); } } }
PersionRepository.cs
using SimpleTaskSystem.Repositorys; using Abp.EntityFramework; namespace SimpleTaskSystem.EntityFramework.Repositories { public class PersionRepository : SimpleTaskSystemRepositoryBase<Person, long>, IPersionRepository { public PersionRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } } }
創建服務(Services)
首先在Application項目中定義Task的應用服務層的接口,各dto的類請下載源碼查看。
ITaskAppService.cs
using Abp.Application.Services; namespace SimpleTaskSystem.Services { public interface ITaskAppService : IApplicationService { GetTasksOutput GetTasks(GetTasksInput input); void UpdateTask(UpdateTaskInput input); void CreateTask(CreateTaskInput input); } }
然后,我們寫TaskAppService類來實現ITaskAppService接口
TaskAppService.cs
using Abp.Application.Services; using AutoMapper; using SimpleTaskSystem.Repositorys; using System.Collections.Generic; namespace SimpleTaskSystem.Services { public class TaskAppService : ApplicationService, ITaskAppService { private readonly ITaskRepository _taskRepository; private readonly IPersionRepository _personRepository; /// /// 構造函數自動注入我們所需要的類或接口 /// public TaskAppService(ITaskRepository taskRepository, IPersionRepository personRepository) { _taskRepository = taskRepository; _personRepository = personRepository; } public void CreateTask(CreateTaskInput input) { Logger.Info("Creating a task for input: " + input); //通過輸入參數,創建一個新的Task實體 var task = new Task { Description = input.Description }; if (input.AssignedPersonId.HasValue) { task.AssignedPersonId = input.AssignedPersonId.Value; } //調用倉儲基類的Insert方法把實體保存到數據庫中 _taskRepository.Insert(task); } public GetTasksOutput GetTasks(GetTasksInput input) { //調用Task倉儲的特定方法GetAllWithPeople var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State); //用AutoMapper自動將List轉換成List return new GetTasksOutput { Tasks = Mapper.Map<list>(tasks) }; } public void UpdateTask(UpdateTaskInput input) { //可以直接Logger,它在ApplicationService基類中定義的 Logger.Info("Updating a task for input: " + input); //通過倉儲基類的通用方法Get,獲取指定Id的Task實體對象 var task = _taskRepository.Get(input.Id); //修改task實體的屬性值 if (input.State.HasValue) { task.State = input.State.Value; } if (input.AssignedPersonId.HasValue) { task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value); } //我們都不需要調用Update方法 //因為應用服務層的方法默認開啟了工作單元模式(Unit of Work) //ABP框架會工作單元完成時自動保存對實體的所有更改,除非有異常拋出。有異常時會自動回滾,因為工作單元默認開啟數據庫事務。 } } }
數據驗證
如果應用服務(Application Service)方法的參數對象實現了IInputDto或IValidate接口,ABP會自動進行參數有效性驗證。
CreateTaskInput.cs
using Abp.Application.Services.Dto; using System.ComponentModel.DataAnnotations; namespace SimpleTaskSystem.Services { public class CreateTaskInput : IInputDto { [Required] public string Description { get; set; } public long? AssignedPersonId { get; set; } } }
如果你想使用自定義驗證,你可以實現ICustomValidate 接口
UpdateTaskInput.cs
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Abp.Application.Services.Dto; using Abp.Runtime.Validation; namespace SimpleTaskSystem.Services { public class UpdateTaskInput : IInputDto, ICustomValidate { public int Id { get; set; } public string Description { get; set; } public long? AssignedPersonId { get; set; } public TaskState? State { get; set; } public void AddValidationErrors(List results) { if (AssignedPersonId == null && State == null) { results.Add(new ValidationResult("AssignedPersonId和State不能同時為空!", new[] { "AssignedPersonId", "State" })); } } } }
創建Web Api服務
ABP默認已經開戶了動態API,我們不需要任何設置即可非常輕松地把Application Service的public方法發布成Web Api接口,可以供客戶端通過ajax調用。
下一篇我們將在此基礎上構建前台頁面,實現在瀏覽器中對任務進行簡單的增刪改查,靜請期待……
本節源碼鏈接: http://pan.baidu.com/s/1jIvZPSM 密碼: njk5