首頁  >  文章  >  後端開發  >  ASP.NET樣板開發框架ABP系列之ABP入門教學詳解

ASP.NET樣板開發框架ABP系列之ABP入門教學詳解

黄舟
黄舟原創
2017-10-14 10:00:278244瀏覽

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」的模組,實現了以下功能:

  • 驗證與授權管理(透過ASP.NET Identity實現的)

  • 使用者&角色管理系統設定存取管理(系統層級、租用戶級、使用者層級,作用範圍自動管理)

  • 審計日誌(自動記錄每一次介面的呼叫者和參數)

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類別繼承自Entity4db15037c2d45d75b28ec2b6a696f099,所以它有一個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)。

總結

#

以上是ASP.NET樣板開發框架ABP系列之ABP入門教學詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn