ABP是為新的現代Web應用程式使用最佳實務和使用最流行工具的一個起點。可作為一般用途的應用程式的基礎架構或項目模板。接下來通過本文給大家詳細介紹ABP入門教程,感興趣的朋友一起看看吧
ABP是“ASP.NET Boilerplate Project (ASP.NET樣板項目)”的簡稱。
ASP.NET Boilerplate是一個用最佳實務和流行技術開發現代WEB應用程式的新起點,它旨在成為一個通用的WEB應用程式架構和項目模板。
ABP的官方網站:http://www.aspnetboilerplate.com
ABP在Github上的開源項目:https://github.com/aspnetboilerplate
ABP 的由來
“DRY——避免重複代碼”是一個優秀的開發人員在開發軟體時所具備的最重要的思想之一。我們在開發企業WEB應用程式時都有一些類似的需求,例如:都需要登入頁面、使用者/角色管理、許可權驗證、資料有效性驗證、多語言/本地化等等。一個高品質的大型軟體都會運用一些最佳實務,例如分層體繫結構、領域驅動設計、依賴注入等。我們也可能會採用ORM、資料庫遷移(Database Migrations)、日誌記錄(Logging)等工具。
從零開始建立一個公司專屬應用程式程式是一件繁瑣的事,因為需要重複做很多常見的基礎工作。許多公司都在開發自己的應用程式架構來重用於不同的項目,然後在架構的基礎上開發一些新的功能。但並不是每個公司都有這樣的實力。假如我們可以分享的更多,也許可以避免每個公司或每個項目的重複編寫類似的代碼。作者之所以把項目命名為“ASP.NET Boilerplate”,就是希望它能成為開發一般企業WEB應用的新起點,直接把ABP作為項目模板。
ABP是什嗎?
ABP是為新的現代Web應用程式使用最佳實務和使用最流行工具的一個起點。可作為一般用途的應用程式的基礎架構或項目模板。它的功能包括:
伺服器端:
基於最新的.NET技術 (目前是ASP.NET MVC 5、Web API 2、C# 5.0,在ASP.NET 5正式發布後會升級)
實現領域驅動設計(實體、倉儲、領網域服務、領域事件、應用服務、資料轉送對象,工作單元等等)
實現分層體繫結構(領域層,應用程式層,展現層和基礎設施層)提供了一個基礎架構來開發可重用可配置的模組整合一些最流行的開源架構/庫,也許有些是你正在使用的。
提供了一個基礎架構讓我們很方便地使用依賴注入(使用Castle Windsor作為依賴注入的容器)
提供Repository倉儲模式支援不同的ORM(已實現Entity Framework 、NHibernate、MangoDb和記憶體資料庫)
支援並實現資料庫遷移(EF 的 Code first)模組化開發(每個模組有獨立的EF DbContext,可單獨指定資料庫)
包括一個簡單的和靈活的多語言/本地化系統
包括一個 EventBus來實現伺服器端全域的領域事件統一的異常處理(應用程式層幾乎不需要處理自己寫異常處理代碼)
資料有效性驗證(Asp.NET MVC只能做到Action方法的參數驗證,ABP實現了Application層方法的參數有效性驗證)
通過Application Services自動建立Web Api層(不需要寫ApiController層了)
提供基類和協助類讓我們方便地實現一些常見的任務
使用“約定優於配置原則”
用戶端:
Bootstrap、Less、AngularJs、jQuery、Modernizr和其他JS庫: jQuery.validate、jQuery.form、jQuery.blockUI、json2等
為單頁面應用程式(AngularJs、Durandaljs)和多頁面應用程式(Bootstrap+Jquery)提供了項目模板。
自動建立Javascript 的代理層來更方便使用Web Api封裝一些Javascript 函數,更方便地使用ajax、訊息框、通知群組件、忙狀態的遮罩層等等
除ABP架構項目以外,還開發了名叫“Zero”的模組,實現了以下功能:
ABP不是什嗎?
ABP提供了一個應用程式開發模型用於最佳實務。它擁有基礎類、介面和工具使我們容易建立起可維護的大規模的應用程式。
然而:
它不是RAD工具之一,RAD工具的目的是無需編碼建立應用程式。相反,ABP提供了一種編碼的最佳實務。
它不是一個代碼產生工具。在運行時雖然它有一些特性構建動態代碼,但它不能產生代碼。
它不是一個一體化的架構。相反,它使用流行的工具/庫來完成特定的任務(例如用EF做ORM,用Log4Net做日誌記錄,使得Castle Windsor作為賴注入容器, AngularJs 用於SPA 架構)。
就我使用了ABP幾個月的經驗來看,雖然ABP不是RAD,但是用它開發項目絕對比傳統三層架構要快很多。
雖然ABP不是代碼產生工具,但因為有了它,使我們項目的代碼更簡潔規範,這有利於使用代碼產生工具。
我自己使用VS2013的Scaffolder+T4開發的代碼產生器,可根據領域對象的UML類圖自動產生全部前後端代碼和資料庫,簡單的CURD模組幾乎不需要編寫代碼,有複雜商務邏輯的模組主要補充領域層代碼即可。這樣就能把時間多花在領域模型的設計上,減少寫代碼的時間。
下面通過原作者的“簡單任務系統”例子,示範如何運用ABP開發項目
從模板建立空的web應用程式
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類型。
建立DbContext
使用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) { }}
通過Database Migrations建立資料庫表
我們使用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>{
它繼承自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 Services)
在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方法中寫自訂驗證的代碼。
建立Web Api服務
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)。
總結