隨着技術的進步,各式各樣的框架層出不窮,輪子越來越多,那么有沒有哪些優秀的開發框架供我們使用呢?如果我們能夠將各方面優秀的框架集合起來,應用到項目開發中,我們的工作是不是能事半功倍呢?而且各個框架的使用方向不同,很多配置也不同,如果能夠將繁雜的基礎工作集成起來,由統一的框架來完成,那么我們就可以專注於業務邏輯,提高工作效率。現在Abp就是這么一個框架,使用流行技術開發現代web應用程序的最佳實踐。本文作為Abp框架的入門文章,僅供學習分享使用,如有不足之處,還請指正。
什么是Abp?
ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程序的新起點,它旨在成為一個通用的WEB應用程序框架和項目模板。ABP是基於最新的ASP.NET CORE,ASP.NET MVC和Web API技術的應用程序框架。並使用流行的框架和庫,它提供了便於使用的授權,依賴注入,驗證,異常處理,本地化,日志記錄,緩存等常用功能。

Abp架構

了解了Abp框架的基礎知識之后,讓我們一步一步的搭建Abp框架,並實現一個簡單的小例子。
安裝CLI
輸入cmd打開命令行窗口,然后輸入以下命令,安裝Abp.Cli,如下所示:
1 dotnet tool install -g Volo.Abp.Cli
安裝過程,如下圖所示:
創建第一個Abp項目
在命令行,切換到程序所在目錄【最好是空目錄】,然后通過命令進行創建,如下所示:
1 abp new Acme.BookStore
安裝過程,如下圖所示:
關於Abp的Cli相關命令,可參考官方文檔 。
項目創建成功后,如下所示:
通過Visual Studio打開解決方案,如下所示:
還原數據庫
在Abp解決方案中,通過運行【Acme.BookStore.DbMigrator】進行初始化數據庫。該項目是控制台程序,采用Entity Framework的Code First方式遷移數據庫。
打開項目【Acme.BookStore.DbMigrator】中 appsettings.json文件,修改數據庫連接字符串,為本機連接字符串,如下所示:
將項目設置為啟動項目,然后F5(或Ctrl + F5)運行即可。當出現以下頁面時,表示數據庫遷移成功。如下所示:
數據庫還原成功后,打開SQL Server數據庫,會多出一個數據庫【BookStore】,如下所示:
注意:最新版本的Abp版本為5.0.0,支持的Entity Framework Core版本為6.0,目前已不再支持SQL Server 2008 R2。所以需要升級數據庫版本到2012。
運行Abp程序
打開項目【Acme.BookStore.Web】中的appsettings.json文件,修改數據庫連接字符串,如下所示:
將項目【Acme.BookStore.Web】設置為啟動項目,然后按F5(或Ctrl+F5)運行項目。Visual Studio會自動打開首頁【https://localhost:44327/】,如下所示:
在首頁上,點擊登錄【默認用戶名 admin,密碼 1q2w3E* 】,如下所示:
登錄成功后,如下所示:
以上就是Abp最新默認框架示例。接下來讓我們一起開發一個圖書管理的小功能。
Abp入門示例
1. 創建Book實體類
啟動模板中的領域層分為兩個項目:
Acme.BookStore.Domain
包含你的實體, 領域服務和其他核心域對象.Acme.BookStore.Domain.Shared
包含可與客戶共享的常量,枚舉或其他域相關對象.
在解決方案的領域層(Acme.BookStore.Domain
項目)中定義實體,如下所示:
在Acme.BookStore.Domain項目中,右鍵創建文件夾Books,然后新增Book類,如下所示:
1 namespace Acme.BookStore.Books 2 { 3 public class Book : AuditedAggregateRoot<Guid> 4 { 5 public string Name { get; set; } 6 7 public BookType Type { get; set; } 8 9 public DateTime PublishDate { get; set; } 10 11 public float Price { get; set; } 12 } 13 }
其中Book繼承自AuditedAggregateRoot<Guid>。在Abp中,默認提供了兩個實體基類AggregateRoot
和Entity,而AuditedAggregateRoot<Guid>是AggregateRoot的派生類。其中Guid是主鍵類型。
上述類中用到的BookType為創建的 枚舉類型,在Acme.BookStore.Domain.Shared項目中,如下所示:
1 namespace Acme.BookStore.Books 2 { 3 public enum BookType 4 { 5 Undefined, 6 Adventure, 7 Biography, 8 Dystopia, 9 Fantastic, 10 Horror, 11 Science, 12 ScienceFiction, 13 Poetry 14 } 15 }
Book和BookType,如下所示:
2. 將Book實體添加到DbContext中
EF Core需要你將實體和 DbContext
建立關聯.最簡單的做法是在Acme.BookStore.EntityFrameworkCore
項目的BookStoreDbContext
類中添加DbSet
屬性.如下所示:
1 namespace Acme.BookStore.EntityFrameworkCore 2 { 3 [ReplaceDbContext(typeof(IIdentityDbContext))] 4 [ReplaceDbContext(typeof(ITenantManagementDbContext))] 5 [ConnectionStringName("Default")] 6 public class BookStoreDbContext : 7 AbpDbContext<BookStoreDbContext>, 8 IIdentityDbContext, 9 ITenantManagementDbContext 10 { 11 /* Add DbSet properties for your Aggregate Roots / Entities here. */ 12 13 #region Entities from the modules 14 //其他自帶的已略去 15 /// <summary> 16 /// Book示例數據庫操作 17 /// </summary> 18 public DbSet<Book> Books { get; set; } 19 20 #endregion 21 22 23 24 } 25 }
3. 將Book實體映射到數據庫表
在本示例中采用Code First方式自動生成數據庫,所以需要將實體和數據庫表進行映射。在 Acme.BookStore.EntityFrameworkCore
項目中打開 BookStoreDbContextModelCreatingExtensions.cs
文件,添加 Book
實體的映射代碼. 最終類應為:
1 namespace Acme.BookStore.EntityFrameworkCore 2 { 3 public static class BookStoreDbContextModelCreatingExtensions 4 { 5 public static void ConfigureBookStore(this ModelBuilder builder) 6 { 7 Check.NotNull(builder, nameof(builder)); 8 9 /* Configure your own tables/entities inside here */ 10 11 builder.Entity<Book>(b => 12 { 13 b.ToTable(BookStoreConsts.DbTablePrefix + "Books", 14 BookStoreConsts.DbSchema); 15 b.ConfigureByConvention(); //auto configure for the base class props 16 b.Property(x => x.Name).IsRequired().HasMaxLength(128); 17 }); 18 } 19 } 20 }
BookStoreConsts
含有用於表的架構和表前綴的常量值. 你不必使用它,但建議在單點控制表前綴.ConfigureByConvention()
方法優雅的配置/映射繼承的屬性,應始終對你所有的實體使用它.
3. 添加數據遷移
啟動模板使用EF Core Code First Migrations創建和維護數據庫架構. 我們應該創建一個新的遷移並且應用到數據庫.
在 Acme.BookStore.EntityFrameworkCore
目錄打開命令行終端輸入以下命令:
1 dotnet ef migrations add Created_Book_Entity
具體示例如下所示:
上述命令,會添加新的遷移類到項目中,如下所示:
4. 添加種子數據
如果不需要通過代碼添加種子數據,可以跳過,建議遵循步驟操作,以熟悉Abp框架。在 Acme.BookStore.Domain 項目下創建派生 IDataSeedContributor
的類,並且拷貝以下代碼:
1 namespace Acme.BookStore.Books 2 { 3 public class BookStoreDataSeederContributor 4 : IDataSeedContributor, ITransientDependency 5 { 6 private readonly IRepository<Book, Guid> _bookRepository; 7 8 public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository) 9 { 10 _bookRepository = bookRepository; 11 } 12 13 public async Task SeedAsync(DataSeedContext context) 14 { 15 if (await _bookRepository.GetCountAsync() <= 0) 16 { 17 await _bookRepository.InsertAsync( 18 new Book 19 { 20 Name = "1984", 21 Type = BookType.Dystopia, 22 PublishDate = new DateTime(1949, 6, 8), 23 Price = 19.84f 24 }, 25 autoSave: true 26 ); 27 28 await _bookRepository.InsertAsync( 29 new Book 30 { 31 Name = "The Hitchhiker's Guide to the Galaxy", 32 Type = BookType.ScienceFiction, 33 PublishDate = new DateTime(1995, 9, 27), 34 Price = 42.0f 35 }, 36 autoSave: true 37 ); 38 } 39 } 40 } 41 }
- 如果數據庫中當前沒有圖書,則此代碼使用
IRepository<Book, Guid>
(默認為repository)將兩本書插入數據庫.
5. 更新數據庫
運行 Acme.BookStore.DbMigrator
應用程序來更新數據庫,將Acme.BookStore.DbMigrator設置為啟動程序,然后運行即可,如下所示:
執行成功后,打開數據庫管理工具,即可看到新生成的數據表,如下所示:
以上則表示數據庫創建成功。
6. 創建應用程序
應用程序層由兩個分離的項目組成:
Acme.BookStore.Application.Contracts
包含你的DTO和應用服務接口.Acme.BookStore.Application
包含你的應用服務實現.
在本部分中,創建一個應用程序服務,使用ABP Framework的 CrudAppService
基類來獲取,創建,更新和刪除書籍.
6. 1 創建BookDto類
在Abp中,需要創建Book實體的Dto類,在Acme.BookStore.Application.Contracts項目中,添加BootDto類,如下所示:
1 namespace Acme.BookStore 2 { 3 public class BookDto : AuditedEntityDto<Guid> 4 { 5 public string Name { get; set; } 6 7 public BookType Type { get; set; } 8 9 public DateTime PublishDate { get; set; } 10 11 public float Price { get; set; } 12 } 13 }
- DTO類被用來在 表示層 和 應用層 傳遞數據.
- 為了在頁面上展示書籍信息,
BookDto
被用來將書籍數據傳遞到表示層. BookDto
繼承自AuditedEntityDto<Guid>
.跟上面定義的Book
實體一樣具有一些審計屬性.
在將書籍返回到表示層時,需要將Book
實體轉換為BookDto
對象. AutoMapper庫可以在定義了正確的映射時自動執行此轉換. 啟動模板配置了AutoMapper,因此你只需在Acme.BookStore.Application
項目的BookStoreApplicationAutoMapperProfile
類中定義映射:
1 namespace Acme.BookStore 2 { 3 public class BookStoreApplicationAutoMapperProfile : Profile 4 { 5 public BookStoreApplicationAutoMapperProfile() 6 { 7 /* You can configure your AutoMapper mapping configuration here. 8 * Alternatively, you can split your mapping configurations 9 * into multiple profile classes for a better organization. */ 10 CreateMap<Book, BookDto>(); 11 } 12 } 13 }
6.2 CreateUpdateBookDto
在Acme.BookStore.Application.Contracts
項目中創建一個名為 CreateUpdateBookDto
的DTO類:
1 namespace Acme.BookStore.Books 2 { 3 public class CreateUpdateBookDto 4 { 5 [Required] 6 [StringLength(128)] 7 public string Name { get; set; } 8 9 [Required] 10 public BookType Type { get; set; } = BookType.Undefined; 11 12 [Required] 13 [DataType(DataType.Date)] 14 public DateTime PublishDate { get; set; } = DateTime.Now; 15 16 [Required] 17 public float Price { get; set; } 18 } 19 }
- 這個DTO類被用於在創建或更新書籍的時候從用戶界面獲取圖書信息.
- 它定義了數據注釋屬性(如
[Required]
)來定義屬性的驗證. DTO由ABP框架自動驗證.
就像上面的BookDto
一樣,創建一個從CreateUpdateBookDto
對象到Book
實體的映射,最終映射配置類如下:
1 namespace Acme.BookStore 2 { 3 public class BookStoreApplicationAutoMapperProfile : Profile 4 { 5 public BookStoreApplicationAutoMapperProfile() 6 { 7 /* You can configure your AutoMapper mapping configuration here. 8 * Alternatively, you can split your mapping configurations 9 * into multiple profile classes for a better organization. */ 10 CreateMap<Book, BookDto>(); 11 CreateMap<CreateUpdateBookDto, Book>(); 12 } 13 } 14 }
7. 創建應用程序服務
7.1 創建IBookAppService
下一步是為應用程序定義接口,在Acme.BookStore.Application.Contracts
項目中定義一個名為IBookAppService
的接口:
- 框架定義應用程序服務的接口不是必需的. 但是,它被建議作為最佳實踐.
ICrudAppService
定義了常見的CRUD方法:GetAsync
,GetListAsync
,CreateAsync
,UpdateAsync
和DeleteAsync
. 你可以從空的IApplicationService
接口繼承並手動定義自己的方法(將在下一部分中完成).ICrudAppService
有一些變體, 你可以在每個方法中使用單獨的DTO,也可以分別單獨指定(例如使用不同的DTO進行創建和更新).
7.2 創建 BookAppService
在Acme.BookStore.Application
項目中創建名為 BookAppService
的 IBookAppService
實現:
1 namespace Acme.BookStore.Books 2 { 3 public class BookAppService : 4 CrudAppService< 5 Book, //The Book entity 6 BookDto, //Used to show books 7 Guid, //Primary key of the book entity 8 PagedAndSortedResultRequestDto, //Used for paging/sorting 9 CreateUpdateBookDto>, //Used to create/update a book 10 IBookAppService //implement the IBookAppService 11 { 12 public BookAppService(IRepository<Book, Guid> repository) 13 : base(repository) 14 { 15 16 } 17 } 18 }
BookAppService
繼承了CrudAppService<...>
.它實現了ICrudAppService
定義的CRUD方法.BookAppService
注入IRepository <Book,Guid>
,這是Book
實體的默認倉儲. ABP自動為每個聚合根(或實體)創建默認倉儲. 請參閱倉儲文檔BookAppService
使用IObjectMapper
將Book
對象轉換為BookDto
對象, 將CreateUpdateBookDto
對象轉換為Book
對象. 啟動模板使用AutoMapper庫作為對象映射提供程序. 我們之前定義了映射, 因此它將按預期工作.
8. 自動生成API Controllers
你通常創建Controller以將應用程序服務公開為HTTP API端點. 因此允許瀏覽器或第三方客戶端通過AJAX調用它們.
ABP可以自動按照慣例將你的應用程序服務配置為MVC API控制器.
9. Swagger UI
啟動模板配置為使用Swashbuckle.AspNetCore運行swagger UI. 運行應用程序並在瀏覽器中輸入https://localhost:XXXX/swagger/
(用你自己的端口替換XXXX)作為URL.
你會看到一些內置的接口和Book
的接口,它們都是REST風格的:
10. 創建頁面
在Acme.BookStore.Web項目的Pages文件夾下,創建Books目錄,然后新增Razer Pages,如下所示:
添加成功后,如下所示:
Index.cshtml頁面代碼如下所示:
1 @page 2 @using Acme.BookStore.Web.Pages.Books 3 @model IndexModel 4 5 <h2>Books</h2>
11. 將Book頁面添加到主菜單
打開 Menus
文件夾中的 BookStoreMenuContributor
類,在 ConfigureMainMenuAsync
方法的底部添加如下代碼:
1 namespace Acme.BookStore.Web.Menus 2 { 3 public class BookStoreMenuContributor : IMenuContributor 4 { 5 public async Task ConfigureMenuAsync(MenuConfigurationContext context) 6 { 7 if (context.Menu.Name == StandardMenus.Main) 8 { 9 await ConfigureMainMenuAsync(context); 10 } 11 } 12 13 private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) 14 { 15 var administration = context.Menu.GetAdministration(); 16 var l = context.GetLocalizer<BookStoreResource>(); 17 18 context.Menu.Items.Insert( 19 0, 20 new ApplicationMenuItem( 21 BookStoreMenus.Home, 22 l["Menu:Home"], 23 "~/", 24 icon: "fas fa-home", 25 order: 0 26 ) 27 ); 28 29 if (MultiTenancyConsts.IsEnabled) 30 { 31 administration.SetSubItemOrder(TenantManagementMenuNames.GroupName, 1); 32 } 33 else 34 { 35 administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName); 36 } 37 38 administration.SetSubItemOrder(IdentityMenuNames.GroupName, 2); 39 administration.SetSubItemOrder(SettingManagementMenuNames.GroupName, 3); 40 //添加book菜單 41 context.Menu.AddItem( 42 new ApplicationMenuItem( 43 "BooksStore", 44 l["Menu:BookStore"], 45 icon: "fa fa-book" 46 ).AddItem( 47 new ApplicationMenuItem( 48 "BooksStore.Books", 49 l["Menu:Books"], 50 url: "/Books" 51 ) 52 ) 53 ); 54 } 55 } 56 }
運行Acme.BookStore.Web項目,等錄以后,便可以查看菜單,如下所示:
點擊菜單后,跳轉到默認的Book首頁,如下所示:
12. 修改Book首頁
將 Pages/Book/Index.cshtml
改成下面的樣子:
1 @page 2 @using Acme.BookStore.Localization 3 @using Acme.BookStore.Web.Pages.Books 4 @using Microsoft.Extensions.Localization 5 @model IndexModel 6 @inject IStringLocalizer<BookStoreResource> L 7 @section scripts 8 { 9 <abp-script src="/Pages/Books/Index.js" /> 10 } 11 <abp-card> 12 <abp-card-header> 13 <h2>@L["Books"]</h2> 14 </abp-card-header> 15 <abp-card-body> 16 <abp-table striped-rows="true" id="BooksTable"></abp-table> 17 </abp-card-body> 18 </abp-card>
其中引用的Index.js位於Pages/Books目錄下,如下所示:
1 $(function () { 2 var l = abp.localization.getResource('BookStore'); 3 4 var dataTable = $('#BooksTable').DataTable( 5 abp.libs.datatables.normalizeConfiguration({ 6 serverSide: true, 7 paging: true, 8 order: [[1, "asc"]], 9 searching: false, 10 scrollX: true, 11 ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), 12 columnDefs: [ 13 { 14 title: l('Name'), 15 data: "name" 16 }, 17 { 18 title: l('Type'), 19 data: "type", 20 render: function (data) { 21 return l('Enum:BookType:' + data); 22 } 23 }, 24 { 25 title: l('PublishDate'), 26 data: "publishDate", 27 render: function (data) { 28 return luxon 29 .DateTime 30 .fromISO(data, { 31 locale: abp.localization.currentCulture.name 32 }).toLocaleString(); 33 } 34 }, 35 { 36 title: l('Price'), 37 data: "price" 38 }, 39 { 40 title: l('CreationTime'), data: "creationTime", 41 render: function (data) { 42 return luxon 43 .DateTime 44 .fromISO(data, { 45 locale: abp.localization.currentCulture.name 46 }).toLocaleString(luxon.DateTime.DATETIME_SHORT); 47 } 48 } 49 ] 50 }) 51 ); 52 });
然后運行項目,結果如下所示:
以上就是Abp的簡單入門介紹,旨在拋轉引玉,一起學習,共同進步。
備注
浣溪沙·一曲新詞酒一杯【作者】晏殊
一曲新詞酒一杯,去年天氣舊亭台。夕陽西下幾時回?
無可奈何花落去,似曾相識燕歸來。小園香徑獨徘徊。