ABP(現代ASP.NET樣板開發框架)系列之二、ABP入門教程

點這裏進入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數據庫

 

ABP 的由來

 「DRY——避免重複代碼」是一個優秀的開發者在開發軟件時所具有的最重要的思想之一。咱們在開發企業WEB應用程序時都有一些相似的需求,例如:都須要登陸頁面、用戶/角色管理、權限驗證、數據有效性驗證、多語言/本地化等等。一個高品質的大型軟件都會運用一些最佳實踐,例如分層體系結構、領域驅動設計、依賴注入等。咱們也可能會採用ORM、數據庫遷移(Database Migrations)、日誌記錄(Logging)等工具。編程

從零開始建立一個企業應用程序是一件繁瑣的事,由於須要重複作不少常見的基礎工做。許多公司都在開發本身的應用程序框架來重用於不一樣的項目,而後在框架的基礎上開發一些新的功能。但並非每一個公司都有這樣的實力。假如咱們能夠分享的更多,也許能夠避免每一個公司或每一個項目的重複編寫相似的代碼。做者之因此把項目命名爲「ASP.NET Boilerplate」,就是但願它能成爲開發通常企業WEB應用的新起點,直接把ABP做爲項目模板。json

 

ABP是什麼?

ABP是爲新的現代Web應用程序使用最佳實踐和使用最流行工具的一個起點。可做爲通常用途的應用程序的基礎框架或項目模板。它的功能包括:後端

服務器端:

  • 基於最新的.NET技術 (目前是ASP.NET MVC 五、Web API 二、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」的模塊,實現瞭如下功能:

  • 身份驗證與受權管理(經過ASP.NET Identity實現的)
  • 用戶&角色管理
  • 系統設置存取管理(系統級、租戶級、用戶級,做用範圍自動管理)
  • 審計日誌(自動記錄每一次接口的調用者和參數)

 

ABP不是什麼?

ABP 提供了一個應用程序開發模型用於最佳實踐。它擁有基礎類、接口和工具使咱們容易創建起可維護的大規模的應用程序。api

然而:

它不是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)並分配給人。 咱們須要TaskPerson這兩個實體。

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文件,咱們只須要把TaskPerson類添加到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-MigrationUpdate-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 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)方法的參數對象實現了IInputDtoIValidate接口,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)。

 

因爲時間關係,展示層沒有在本文中介紹,將放到之後的文章介紹。後續文章中也將會詳細介紹每一層的具體知識要點。

若是想當即看到更多展現,能夠查看我之前的文章《新思想、新技術、新架構——更好更快的開發現代ASP.NET應用程序(續1)》比較完整的演示了一個簡單模塊的開發,包括先後端各層的代碼。(我本身項目用的ABP框架是在原做者的基礎上作了一些修改,因此有些地方可能跟原做者的ABP不徹底相同。)

 

因爲演示一個完整的開發流程工做量巨大,寫文章很難說得清楚,忙過這段時間我會準備用視頻或YY在線的方式來分享,到時也能夠分享我使用VS2013的Scaffolder+T4開發的代碼生成器。

 

但願更多國內的架構師能關注到ABP這個項目,也許這其中有能幫助到您的地方,也許有您的參與,這個項目能夠發展得更好。

歡迎加ABP架構設計交流QQ羣:134710707

ABP架構設計交流羣

 

點這裏進入ABP系列文章總目錄

相關文章
相關標籤/搜索