點這裏進入ABP系列文章總目錄html
基於DDD的現代ASP.NET開發框架--ABP系列之二、ABP入門教程
git
ABP是「ASP.NET Boilerplate Project (ASP.NET樣板項目)」的簡稱。github
ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程序的新起點,它旨在成爲一個通用的WEB應用程序框架和項目模板。web
ABP的官方網站:http://www.aspnetboilerplate.comajax
ABP在Github上的開源項目:https://github.com/aspnetboilerplate數據庫
「DRY——避免重複代碼」是一個優秀的開發者在開發軟件時所具有的最重要的思想之一。咱們在開發企業WEB應用程序時都有一些相似的需求,例如:都須要登陸頁面、用戶/角色管理、權限驗證、數據有效性驗證、多語言/本地化等等。一個高品質的大型軟件都會運用一些最佳實踐,例如分層體系結構、領域驅動設計、依賴注入等。咱們也可能會採用ORM、數據庫遷移(Database Migrations)、日誌記錄(Logging)等工具。編程
從零開始建立一個企業應用程序是一件繁瑣的事,由於須要重複作不少常見的基礎工做。許多公司都在開發本身的應用程序框架來重用於不一樣的項目,而後在框架的基礎上開發一些新的功能。但並非每一個公司都有這樣的實力。假如咱們能夠分享的更多,也許能夠避免每一個公司或每一個項目的重複編寫相似的代碼。做者之因此把項目命名爲「ASP.NET Boilerplate」,就是但願它能成爲開發通常企業WEB應用的新起點,直接把ABP做爲項目模板。json
ABP是爲新的現代Web應用程序使用最佳實踐和使用最流行工具的一個起點。可做爲通常用途的應用程序的基礎框架或項目模板。它的功能包括:後端
ABP 提供了一個應用程序開發模型用於最佳實踐。它擁有基礎類、接口和工具使咱們容易創建起可維護的大規模的應用程序。api
然而:
它不是RAD工具之一,RAD工具的目的是無需編碼建立應用程序。相反,ABP提供了一種編碼的最佳實踐。
它不是一個代碼生成工具。在運行時雖然它有一些特性構建動態代碼,但它不能生成代碼。
它不是一個一體化的框架。相反,它使用流行的工具/庫來完成特定的任務(例如用EF作ORM,用Log4Net作日誌記錄,使得Castle Windsor做爲賴注入容器, AngularJs 用於SPA 框架)。
就我使用了ABP幾個月的經驗來看,雖然ABP不是RAD,可是用它開發項目絕對比傳統三層架構要快不少。
雖然ABP不是代碼生成工具,但由於有了它,使咱們項目的代碼更簡潔規範,這有利於使用代碼生成工具。
我本身使用VS2013的Scaffolder+T4開發的代碼生成器,可根據領域對象的UML類圖自動生成所有先後端代碼和數據庫,簡單的CURD模塊幾乎不須要編寫代碼,有複雜業務邏輯的模塊主要補充領域層代碼便可。這樣就能把時間多花在領域模型的設計上,減小寫代碼的時間。
下面經過原做者的「簡單任務系統」例子,演示如何運用ABP開發項目
ABP提供了一個啓動模板用於新建的項目(儘管你能手動地建立項目而且從nuget得到ABP包,模板的方式更容易)。
轉到www.aspnetboilerplate.com/Templates從模板建立你的應用程序。
你能夠選擇SPA(AngularJs或DurandalJs)或者選擇MPA(經典的多頁面應用程序)項目。能夠選擇Entity Framework或NHibernate做爲ORM框架。
這裏咱們選擇AngularJs和Entity Framework,填入項目名稱「SimpleTaskSystem」,點擊「CREATE MY PROJECT」按鈕能夠下載一個zip壓縮包,解壓後獲得VS2013的解決方案,使用的.NET版本是 4.5.1。
每一個項目裏引用了Abp組件和其餘第三方組件,須要從Nuget下載。
黃色感嘆號圖標,表示這個組件在本地文件夾中不存在,須要從Nuget上還原。操做以下:
要讓項目運行起來,還得建立一個數據庫。這個模板假設你正在使用SQL2008或者更新的版本。固然也能夠很方便地換成其餘的關係型數據庫。
打開Web.Config文件能夠查看和配置連接字符串:
<add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystemDb; Trusted_Connection=True;" />
(在後面用到EF的Code first數據遷移時,會自動在SQL Server數據庫中建立一個名爲SimpleTaskSystemDb的數據庫。)
就這樣,項目已經準備好運行了!打開VS2013而且按F5:
下面將逐步實現這個簡單的任務系統程序
把實體類寫在Core項目中,由於實體是領域層的一部分。
一個簡單的應用場景:建立一些任務(tasks)並分配給人。 咱們須要Task和Person這兩個實體。
Task實體有幾個屬性:描述(Description)、建立時間(CreationTime)、任務狀態(State),還有可選的導航屬性(AssignedPerson)來引用Person。
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; } }
在ABP框架中,有一個Entity基類,它有一個Id屬性。由於Task類繼承自Entity<long>,因此它有一個long類型的Id。Person類有一個int類型的Id,由於int類型是Entity基類Id的默認類型,沒有特別指定類型時,實體的Id就是int類型。
使用EntityFramework須要先定義DbContext類,ABP的模板已經建立了DbContext文件,咱們只須要把Task和Person類添加到IDbSet,請看代碼:
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) { } }
咱們使用EntityFramework的Code First模式建立數據庫架構。ABP模板生成的項目已經默認開啓了數據遷移功能,咱們修改SimpleTaskSystem.EntityFramework項目下Migrations文件夾下的Configuration.cs文件:
internal 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"} ); } }
在VS2013底部的「程序包管理器控制檯」窗口中,選擇默認項目並執行命令「Add-Migration InitialCreate」
會在Migrations文件夾下生成一個xxxx-InitialCreate.cs文件,內容以下:
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"); } }
而後繼續在「程序包管理器控制檯」執行「Update-Database」,會自動在數據庫建立相應的數據表:
PM> Update-Database
數據庫顯示以下:
(之後修改了實體,能夠再次執行Add-Migration和Update-Database,就能很輕鬆的讓數據庫結構與實體類的同步)
經過倉儲模式,能夠更好把業務代碼與數據庫操做代碼更好的分離,能夠針對不一樣的數據庫有不一樣的實現類,而業務代碼不須要修改。
定義倉儲接口的代碼寫到Core項目中,由於倉儲接口是領域層的一部分。
咱們先定義Task的倉儲接口:
public interface ITaskRepository : IRepository<Task, long> { List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state); }
它繼承自ABP框架中的IRepository泛型接口。
在IRepository中已經定義了經常使用的增刪改查方法:
因此ITaskRepository默認就有了上面那些方法。能夠再加上它獨有的方法GetAllWithPeople(...)。
不須要爲Person類建立一個倉儲類,由於默認的方法已經夠用了。ABP提供了一種注入通用倉儲的方式,將在後面「建立應用服務」一節的TaskAppService類中看到。
咱們將在EntityFramework項目中實現上面定義的ITaskRepository倉儲接口。
經過模板創建的項目已經定義了一個倉儲基類:SimpleTaskSystemRepositoryBase(這是一種比較好的實踐,由於之後能夠在這個基類中添加通用的方法)。
public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository { public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state) { //在倉儲方法中,不用處理數據庫鏈接、DbContext和數據事務,ABP框架會自動處理。 var query = GetAll(); //GetAll() 返回一個 IQueryable<T>接口類型 //添加一些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(); } }
TaskRepository繼承自SimpleTaskSystemRepositoryBase而且實現了上面定義的ITaskRepository接口。
在Application項目中定義應用服務。首先定義Task的應用服務層的接口:
public interface ITaskAppService : IApplicationService { GetTasksOutput GetTasks(GetTasksInput input); void UpdateTask(UpdateTaskInput input); void CreateTask(CreateTaskInput input); }
ITaskAppService繼承自IApplicationService,ABP自動爲這個類提供一些功能特性(好比依賴注入和參數有效性驗證)。
而後,咱們寫TaskAppService類來實現ITaskAppService接口:
public class TaskAppService : ApplicationService, ITaskAppService { private readonly ITaskRepository _taskRepository; private readonly IRepository<Person> _personRepository; /// <summary> /// 構造函數自動注入咱們所須要的類或接口 /// </summary> public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository) { _taskRepository = taskRepository; _personRepository = personRepository; } public GetTasksOutput GetTasks(GetTasksInput input) { //調用Task倉儲的特定方法GetAllWithPeople var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State); //用AutoMapper自動將List<Task>轉換成List<TaskDto> return new GetTasksOutput { Tasks = Mapper.Map<List<TaskDto>>(tasks) }; } public void UpdateTask(UpdateTaskInput input) { //能夠直接Logger,它在ApplicationService基類中定義的 Logger.Info("Updating a task for input: " + input); //經過倉儲基類的通用方法Get,獲取指定Id的Task實體對象 var task = _taskRepository.Get(input.TaskId); //修改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框架會工做單元完成時自動保存對實體的全部更改,除非有異常拋出。有異常時會自動回滾,由於工做單元默認開啓數據庫事務。 } 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); } }
TaskAppService使用倉儲進行數據庫操做,它通往構造函數注入倉儲對象的引用。
若是應用服務(Application Service)方法的參數對象實現了IInputDto或IValidate接口,ABP會自動進行參數有效性驗證。
CreateTask方法有一個CreateTaskInput參數,定義以下:
public class CreateTaskInput : IInputDto { public int? AssignedPersonId { get; set; } [Required] public string Description { get; set; } }
Description屬性經過註解指定它是必填項。也可使用其餘 Data Annotation 特性。
若是你想使用自定義驗證,你能夠實現ICustomValidate 接口:
public class UpdateTaskInput : IInputDto, ICustomValidate { [Range(1, long.MaxValue)] public long TaskId { get; set; } public int? AssignedPersonId { get; set; } public TaskState? State { get; set; } public void AddValidationErrors(List<ValidationResult> results) { if (AssignedPersonId == null && State == null) { results.Add(new ValidationResult("AssignedPersonId和State不能同時爲空!", new[] { "AssignedPersonId", "State" })); } } }
你能夠在AddValidationErrors方法中寫自定義驗證的代碼。
ABP能夠很是輕鬆地把Application Service的public方法發佈成Web Api接口,能夠供客戶端經過ajax調用。
DynamicApiControllerBuilder .ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem") .Build();
SimpleTaskSystemApplicationModule這個程序集中全部繼承了IApplicationService接口的類,都會自動建立相應的ApiController,其中的公開方法,就會轉換成WebApi接口方法。
能夠經過http://xxx/api/services/tasksystem/Task/GetTasks這樣的路由地址進行調用。
經過上面的案例,大體介紹了領域層、基礎設施層、應用服務層的用法。
如今,能夠在ASP.NET MVC的Controller的Action方法中直接調用Application Service的方法了。
若是用SPA單頁編程,能夠直接在客戶端經過ajax調用相應的Application Service的方法了(經過建立了動態Web Api)。
因爲時間關係,展示層沒有在本文中介紹,將放到之後的文章介紹。後續文章中也將會詳細介紹每一層的具體知識要點。
若是想當即看到更多展現,能夠查看我之前的文章《新思想、新技術、新架構——更好更快的開發現代ASP.NET應用程序(續1)》,比較完整的演示了一個簡單模塊的開發,包括先後端各層的代碼。(我本身項目用的ABP框架是在原做者的基礎上作了一些修改,因此有些地方可能跟原做者的ABP不徹底相同。)
因爲演示一個完整的開發流程工做量巨大,寫文章很難說得清楚,忙過這段時間我會準備用視頻或YY在線的方式來分享,到時也能夠分享我使用VS2013的Scaffolder+T4開發的代碼生成器。
但願更多國內的架構師能關注到ABP這個項目,也許這其中有能幫助到您的地方,也許有您的參與,這個項目能夠發展得更好。
歡迎加ABP架構設計交流QQ羣:134710707