基于ASP.NET MVC的ABP框架入門學(xué)習(xí)教程
為什么使用ABP
我們近幾年陸續(xù)開發(fā)了一些Web應(yīng)用和桌面應(yīng)用,需求或簡單或復(fù)雜,實(shí)現(xiàn)或優(yōu)雅或丑陋。一個(gè)基本的事實(shí)是:我們只是積累了一些經(jīng)驗(yàn)或提高了對(duì),NET的熟悉程度。
隨著軟件開發(fā)經(jīng)驗(yàn)的不斷增加,我們發(fā)現(xiàn)其實(shí)很多工作都是重復(fù)機(jī)械的,而且隨著軟件復(fù)雜度的不斷提升,以往依靠經(jīng)驗(yàn)來完成一些簡單的增刪改查的做法已經(jīng)行不通了。特別是用戶的要求越來越高,希望添加的功能越來多,目前這種開發(fā)模式,已經(jīng)捉襟見肘。我很難想象如何在現(xiàn)有的模式下進(jìn)行多系統(tǒng)的持續(xù)集成并添加一些新的特性。
開發(fā)一個(gè)系統(tǒng)時(shí),我們不可避免的會(huì)使用各種框架。數(shù)據(jù)持久層實(shí)現(xiàn)、日志、ASP.NET MVC、IOC以及自動(dòng)映射等。一個(gè)高質(zhì)量的軟件系統(tǒng)往往還有全局容錯(cuò),消息隊(duì)列等組件。
把上述這些組件組合到一起的時(shí)候,其復(fù)雜度會(huì)急劇上升。一般個(gè)人和小團(tuán)隊(duì)的技術(shù)水平,很難設(shè)計(jì)出一個(gè)均衡協(xié)調(diào)的框架。對(duì)于傳統(tǒng)的所謂三層架構(gòu),我也是很持懷疑態(tài)度的。(月薪15k的程序員搞的三層架構(gòu),我也仔細(xì)讀過,也是問題多多,并不能解釋為什么要使用三層)。
其實(shí),我們無非是希望在編程的時(shí)候,把大部分的注意力全部集中到業(yè)務(wù)實(shí)現(xiàn)上。不要過多的考慮基礎(chǔ)的軟件結(jié)構(gòu)上的種種問題。應(yīng)該有一個(gè)框框或者一種范式來提供基本的服務(wù),如日志、容錯(cuò)和AOP,DI等。
稍微正規(guī)一點(diǎn)的公司經(jīng)過多年沉淀都形成了自己的內(nèi)部軟件框架,他們?cè)陂_發(fā)軟件的時(shí)候并不是從一片空白開始的。而是從一個(gè)非常牢固的基礎(chǔ)平臺(tái)上開始構(gòu)建的。這樣大大提高了開發(fā)速度,而且一種架構(gòu)往往也決定了分工協(xié)作的模式。我們目前之所以無法分工協(xié)作,根本原因也是缺少一套成熟穩(wěn)定的基礎(chǔ)開發(fā)架構(gòu)和工作流程。
目前.NET上有不少開源框架。比如Apworks和ABP。其中Apworks是中國人寫的一套開源框架。它是一個(gè)全功能的,不僅可以寫分布式應(yīng)用,也可以寫桌面應(yīng)用。ABP的全稱是Asp.net boilerplate project(asp.net樣板工程)。是github上非?;钴S的一個(gè)開源項(xiàng)目。它并沒有使用任何新的技術(shù),只是由兩名架構(gòu)師將asp.net開發(fā)中常用的一些工具整合到了一起,并且部分實(shí)現(xiàn)了DDD的概念。是一個(gè)開箱即用的框架,可以作為asp.net分布式應(yīng)用的一個(gè)良好起點(diǎn)。
使用框架當(dāng)然有代價(jià),你必須受到框架強(qiáng)API的侵入,抑或要使用他的方言。而且這個(gè)框架想要吃透,也要付出很大的學(xué)習(xí)成本。但是好處也是顯而易見的。業(yè)界頂尖的架構(gòu)師已經(jīng)為你搭建好了一套基礎(chǔ)架構(gòu),很好的回應(yīng)了關(guān)于一個(gè)軟件系統(tǒng)應(yīng)該如何設(shè)計(jì),如何規(guī)劃的問題,并且提供了一套最佳實(shí)踐和范例。
學(xué)習(xí)雖然要付出成本,但是經(jīng)過漫長的跋涉,我們從一無所知已經(jīng)站到了工業(yè)級(jí)開發(fā)的門檻上?;谶@個(gè)框架,我們可以很好的來劃分任務(wù),進(jìn)行單元測試等。大大降低了軟件出現(xiàn)BUG的幾率。
從模板創(chuàng)建空的web應(yīng)用程序
ABP的官方網(wǎng)站:http://www.aspnetboilerplate.com
ABP在Github上的開源項(xiàng)目:https://github.com/aspnetboilerplate
ABP提供了一個(gè)啟動(dòng)模板用于新建的項(xiàng)目(盡管你能手動(dòng)地創(chuàng)建項(xiàng)目并且從nuget獲得ABP包,模板的方式更容易)。
轉(zhuǎn)到www.aspnetboilerplate.com/Templates從模板創(chuàng)建你的應(yīng)用程序。
你可以選擇SPA(AngularJs或DurandalJs)或者選擇MPA(經(jīng)典的多頁面應(yīng)用程序)項(xiàng)目??梢赃x擇Entity Framework或NHibernate作為ORM框架。
這里我們選擇AngularJs和Entity Framework,填入項(xiàng)目名稱“SimpleTaskSystem”,點(diǎn)擊“CREATE MY PROJECT”按鈕可以下載一個(gè)zip壓縮包,解壓后得到VS2013的解決方案,使用的.NET版本是 4.5.1。
每個(gè)項(xiàng)目里引用了Abp組件和其他第三方組件,需要從Nuget下載。
黃色感嘆號(hào)圖標(biāo),表示這個(gè)組件在本地文件夾中不存在,需要從Nuget上還原。操作如下:
要讓項(xiàng)目運(yùn)行起來,還得創(chuàng)建一個(gè)數(shù)據(jù)庫。這個(gè)模板假設(shè)你正在使用SQL2008或者更新的版本。當(dāng)然也可以很方便地?fù)Q成其他的關(guān)系型數(shù)據(jù)庫。
打開Web.Config文件可以查看和配置鏈接字符串:
<add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystemDb; Trusted_Connection=True;" />
就這樣,項(xiàng)目已經(jīng)準(zhǔn)備好運(yùn)行了!打開VS2013并且按F5:

創(chuàng)建實(shí)體
把實(shí)體類寫在Core項(xiàng)目中,因?yàn)閷?shí)體是領(lǐng)域?qū)拥囊徊糠帧?br />
一個(gè)簡單的應(yīng)用場景:創(chuàng)建一些任務(wù)(tasks)并分配給人。 我們需要Task和Person這兩個(gè)實(shí)體。
Task實(shí)體有幾個(gè)屬性:描述(Description)、創(chuàng)建時(shí)間(CreationTime)、任務(wù)狀態(tài)(State),還有可選的導(dǎo)航屬性(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; } }
public class Person : Entity { public virtual string Name { get; set; } }
創(chuàng)建DbContext
使用EntityFramework需要先定義DbContext類,ABP的模板已經(jīng)創(chuàng)建了DbContext文件,我們只需要把Task和Person類添加到IDbSet,請(qǐng)看代碼:
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創(chuàng)建數(shù)據(jù)庫表
我們使用EntityFramework的Code First模式創(chuàng)建數(shù)據(jù)庫架構(gòu)。ABP模板生成的項(xiàng)目已經(jīng)默認(rèn)開啟了數(shù)據(jù)遷移功能,我們修改SimpleTaskSystem.EntityFramework項(xiàng)目下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底部的“程序包管理器控制臺(tái)”窗口中,選擇默認(rèn)項(xiàng)目并執(zhí)行命令“Add-Migration InitialCreate”
會(huì)在Migrations文件夾下生成一個(gè)xxxx-InitialCreate.cs文件,內(nèi)容如下:
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
數(shù)據(jù)庫顯示如下:
(以后修改了實(shí)體,可以再次執(zhí)行Add-Migration和Update-Database,就能很輕松的讓數(shù)據(jù)庫結(jié)構(gòu)與實(shí)體類的同步)
定義倉儲(chǔ)接口
通過倉儲(chǔ)模式,可以更好把業(yè)務(wù)代碼與數(shù)據(jù)庫操作代碼更好的分離,可以針對(duì)不同的數(shù)據(jù)庫有不同的實(shí)現(xiàn)類,而業(yè)務(wù)代碼不需要修改。
定義倉儲(chǔ)接口的代碼寫到Core項(xiàng)目中,因?yàn)閭}儲(chǔ)接口是領(lǐng)域?qū)拥囊徊糠帧?br />
我們先定義Task的倉儲(chǔ)接口:
public interface ITaskRepository : IRepository<Task, long> { List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state); }
它繼承自ABP框架中的IRepository泛型接口。
在IRepository中已經(jīng)定義了常用的增刪改查方法:
所以ITaskRepository默認(rèn)就有了上面那些方法??梢栽偌由纤?dú)有的方法GetAllWithPeople(...)。
不需要為Person類創(chuàng)建一個(gè)倉儲(chǔ)類,因?yàn)槟J(rèn)的方法已經(jīng)夠用了。ABP提供了一種注入通用倉儲(chǔ)的方式,將在后面“創(chuàng)建應(yīng)用服務(wù)”一節(jié)的TaskAppService類中看到。
實(shí)現(xiàn)倉儲(chǔ)類
我們將在EntityFramework項(xiàng)目中實(shí)現(xiàn)上面定義的ITaskRepository倉儲(chǔ)接口。
通過模板建立的項(xiàng)目已經(jīng)定義了一個(gè)倉儲(chǔ)基類:SimpleTaskSystemRepositoryBase(這是一種比較好的實(shí)踐,因?yàn)橐院罂梢栽谶@個(gè)基類中添加通用的方法)。
public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository { public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state) { //在倉儲(chǔ)方法中,不用處理數(shù)據(jù)庫連接、DbContext和數(shù)據(jù)事務(wù),ABP框架會(huì)自動(dòng)處理。 var query = GetAll(); //GetAll() 返回一個(gè) 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并且實(shí)現(xiàn)了上面定義的ITaskRepository接口。
創(chuàng)建應(yīng)用服務(wù)(Application Services)
在Application項(xiàng)目中定義應(yīng)用服務(wù)。首先定義Task的應(yīng)用服務(wù)層的接口:
public interface ITaskAppService : IApplicationService { GetTasksOutput GetTasks(GetTasksInput input); void UpdateTask(UpdateTaskInput input); void CreateTask(CreateTaskInput input); }
ITaskAppService繼承自IApplicationService,ABP自動(dòng)為這個(gè)類提供一些功能特性(比如依賴注入和參數(shù)有效性驗(yàn)證)。
然后,我們寫TaskAppService類來實(shí)現(xiàn)ITaskAppService接口:
public class TaskAppService : ApplicationService, ITaskAppService { private readonly ITaskRepository _taskRepository; private readonly IRepository<Person> _personRepository; /// <summary> /// 構(gòu)造函數(shù)自動(dòng)注入我們所需要的類或接口 /// </summary> public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository) { _taskRepository = taskRepository; _personRepository = personRepository; } public GetTasksOutput GetTasks(GetTasksInput input) { //調(diào)用Task倉儲(chǔ)的特定方法GetAllWithPeople var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State); //用AutoMapper自動(dòng)將List<Task>轉(zhuǎn)換成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); //通過倉儲(chǔ)基類的通用方法Get,獲取指定Id的Task實(shí)體對(duì)象 var task = _taskRepository.Get(input.TaskId); //修改task實(shí)體的屬性值 if (input.State.HasValue) { task.State = input.State.Value; } if (input.AssignedPersonId.HasValue) { task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value); } //我們都不需要調(diào)用Update方法 //因?yàn)閼?yīng)用服務(wù)層的方法默認(rèn)開啟了工作單元模式(Unit of Work) //ABP框架會(huì)工作單元完成時(shí)自動(dòng)保存對(duì)實(shí)體的所有更改,除非有異常拋出。有異常時(shí)會(huì)自動(dòng)回滾,因?yàn)楣ぷ鲉卧J(rèn)開啟數(shù)據(jù)庫事務(wù)。 } public void CreateTask(CreateTaskInput input) { Logger.Info("Creating a task for input: " + input); //通過輸入?yún)?shù),創(chuàng)建一個(gè)新的Task實(shí)體 var task = new Task { Description = input.Description }; if (input.AssignedPersonId.HasValue) { task.AssignedPersonId = input.AssignedPersonId.Value; } //調(diào)用倉儲(chǔ)基類的Insert方法把實(shí)體保存到數(shù)據(jù)庫中 _taskRepository.Insert(task); } }
TaskAppService使用倉儲(chǔ)進(jìn)行數(shù)據(jù)庫操作,它通往構(gòu)造函數(shù)注入倉儲(chǔ)對(duì)象的引用。
數(shù)據(jù)驗(yàn)證
如果應(yīng)用服務(wù)(Application Service)方法的參數(shù)對(duì)象實(shí)現(xiàn)了IInputDto或IValidate接口,ABP會(huì)自動(dòng)進(jìn)行參數(shù)有效性驗(yàn)證。
CreateTask方法有一個(gè)CreateTaskInput參數(shù),定義如下:
public class CreateTaskInput : IInputDto { public int? AssignedPersonId { get; set; } [Required] public string Description { get; set; } }
Description屬性通過注解指定它是必填項(xiàng)。也可以使用其他 Data Annotation 特性。
如果你想使用自定義驗(yàn)證,你可以實(shí)現(xiàn)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不能同時(shí)為空!", new[] { "AssignedPersonId", "State" })); } } }
你可以在AddValidationErrors方法中寫自定義驗(yàn)證的代碼。
創(chuàng)建Web Api服務(wù)
ABP可以非常輕松地把Application Service的public方法發(fā)布成Web Api接口,可以供客戶端通過ajax調(diào)用。
DynamicApiControllerBuilder .ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem") .Build();
SimpleTaskSystemApplicationModule這個(gè)程序集中所有繼承了IApplicationService接口的類,都會(huì)自動(dòng)創(chuàng)建相應(yīng)的ApiController,其中的公開方法,就會(huì)轉(zhuǎn)換成WebApi接口方法。
可以通過http://xxx/api/services/tasksystem/Task/GetTasks這樣的路由地址進(jìn)行調(diào)用。
通過上面的案例,大致介紹了領(lǐng)域?qū)?、基礎(chǔ)設(shè)施層、應(yīng)用服務(wù)層的用法。
現(xiàn)在,可以在ASP.NET MVC的Controller的Action方法中直接調(diào)用Application Service的方法了。
如果用SPA單頁編程,可以直接在客戶端通過ajax調(diào)用相應(yīng)的Application Service的方法了(通過創(chuàng)建了動(dòng)態(tài)Web Api)。
相關(guān)文章
jQuery Data Linking 對(duì)象與對(duì)象之間屬性的關(guān)聯(lián)
ASP.NET團(tuán)隊(duì)最近還向jQuery社區(qū)提交了被稱為data linking的技術(shù),Data Linking可以幫助你實(shí)現(xiàn)對(duì)象與對(duì)象之間屬性的關(guān)聯(lián)——當(dāng)其中一方發(fā)生改變時(shí)另一方也隨之改變。2010-12-12asp.net使用原生控件實(shí)現(xiàn)自定義列導(dǎo)出功能的方法
這篇文章主要給大家介紹了關(guān)于asp.net使用原生控件實(shí)現(xiàn)自定義列導(dǎo)出功能的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01ASP.NET自帶對(duì)象JSON字符串與實(shí)體類的轉(zhuǎn)換
這篇文章主要介紹了ASP.NET自帶對(duì)象JSON字符串與實(shí)體類的轉(zhuǎn)換,感興趣的小伙伴們可以參考一下2016-07-07NLog路由規(guī)則和上下文信息知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給各位整理的是關(guān)于NLog路由規(guī)則和上下文信息的相關(guān)文章,有需要的朋友們學(xué)習(xí)下。2019-10-10詳細(xì)介紹.NET中的動(dòng)態(tài)編譯技術(shù)
這篇文章詳細(xì)介紹了.NET中的動(dòng)態(tài)編譯技術(shù),有需要的朋友可以參考一下2013-11-11ASP.NET Core使用AutoMapper實(shí)現(xiàn)實(shí)體映射
本文詳細(xì)講解了ASP.NET Core使用AutoMapper實(shí)現(xiàn)實(shí)體映射的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03