C# Abp框架入门系列文章(一)

  • A+
所属分类:.NET技术
摘要

随着技术的进步,各式各样的框架层出不穷,轮子越来越多,那么有没有哪些优秀的开发框架供我们使用呢?如果我们能够将各方面优秀的框架集合起来,应用到项目开发中,我们的工作是不是能事半功倍呢?而且各个框架的使用方向不同,很多配置也不同,如果能够将繁杂的基础工作集成起来,由统一的框架来完成,那么我们就可以专注于业务逻辑,提高工作效率。现在Abp就是这么一个框架,使用流行技术开发现代web应用程序的最佳实践。本文作为Abp框架的入门文章,仅供学习分享使用,如有不足之处,还请指正。

随着技术的进步,各式各样的框架层出不穷,轮子越来越多,那么有没有哪些优秀的开发框架供我们使用呢?如果我们能够将各方面优秀的框架集合起来,应用到项目开发中,我们的工作是不是能事半功倍呢?而且各个框架的使用方向不同,很多配置也不同,如果能够将繁杂的基础工作集成起来,由统一的框架来完成,那么我们就可以专注于业务逻辑,提高工作效率。现在Abp就是这么一个框架,使用流行技术开发现代web应用程序的最佳实践。本文作为Abp框架的入门文章,仅供学习分享使用,如有不足之处,还请指正。

什么是Abp?

ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。
ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板。ABP是基于最新的ASP.NET CORE,ASP.NET MVC和Web API技术的应用程序框架。并使用流行的框架和库,它提供了便于使用的授权,依赖注入,验证,异常处理,本地化,日志记录,缓存等常用功能。
C# Abp框架入门系列文章(一)

 

 

Abp架构

ABP实现了多层架构(领域层,应用层,基础设施层和表示层),以及领域驱动设计(实体,存储库,领域服务,应用程序服务,DTO等)。还实现和提供了良好的基础设施来实现最佳实践,如依赖注入。
C# Abp框架入门系列文章(一)

 

 了解了Abp框架的基础知识之后,让我们一步一步的搭建Abp框架,并实现一个简单的小例子。

安装CLI

输入cmd打开命令行窗口,然后输入以下命令,安装Abp.Cli,如下所示:

1 dotnet tool install -g Volo.Abp.Cli

安装过程,如下图所示:

C# Abp框架入门系列文章(一)

 

创建第一个Abp项目

在命令行,切换到程序所在目录【最好是空目录】,然后通过命令进行创建,如下所示:

1 abp new Acme.BookStore

安装过程,如下图所示:

C# Abp框架入门系列文章(一)

关于Abp的Cli相关命令,可参考官方文档 。

项目创建成功后,如下所示:

C# Abp框架入门系列文章(一)

 通过Visual Studio打开解决方案,如下所示:

 C# Abp框架入门系列文章(一)

还原数据库

在Abp解决方案中,通过运行【Acme.BookStore.DbMigrator】进行初始化数据库。该项目是控制台程序,采用Entity Framework的Code First方式迁移数据库。

打开项目【Acme.BookStore.DbMigrator】中 appsettings.json文件,修改数据库连接字符串,为本机连接字符串,如下所示:

C# Abp框架入门系列文章(一)

将项目设置为启动项目,然后F5(或Ctrl + F5)运行即可。当出现以下页面时,表示数据库迁移成功。如下所示:

C# Abp框架入门系列文章(一)

 

数据库还原成功后,打开SQL Server数据库,会多出一个数据库【BookStore】,如下所示:

C# Abp框架入门系列文章(一)

注意:最新版本的Abp版本为5.0.0,支持的Entity Framework Core版本为6.0,目前已不再支持SQL Server 2008 R2。所以需要升级数据库版本到2012。

运行Abp程序

打开项目【Acme.BookStore.Web】中的appsettings.json文件,修改数据库连接字符串,如下所示:

C# Abp框架入门系列文章(一)

 

将项目【Acme.BookStore.Web】设置为启动项目,然后按F5(或Ctrl+F5)运行项目。Visual Studio会自动打开首页【https://localhost:44327/】,如下所示:

C# Abp框架入门系列文章(一)

 

 在首页上,点击登录【默认用户名 admin,密码 1q2w3E* 】,如下所示:

C# Abp框架入门系列文章(一)

 

 登录成功后,如下所示:

C# Abp框架入门系列文章(一)

 以上就是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中,默认提供了两个实体基类AggregateRootEntity,而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,如下所示:

C# Abp框架入门系列文章(一)

 

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

具体示例如下所示:

C# Abp框架入门系列文章(一)

 

 上述命令,会添加新的迁移类到项目中,如下所示:

C# Abp框架入门系列文章(一)

 

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设置为启动程序,然后运行即可,如下所示:

C# Abp框架入门系列文章(一)

 

 执行成功后,打开数据库管理工具,即可看到新生成的数据表,如下所示:

C# Abp框架入门系列文章(一)

 

 以上则表示数据库创建成功。

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,UpdateAsyncDeleteAsync. 你可以从空的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使用IObjectMapperBook对象转换为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风格的:

C# Abp框架入门系列文章(一)

10. 创建页面

在Acme.BookStore.Web项目的Pages文件夹下,创建Books目录,然后新增Razer Pages,如下所示:

C# Abp框架入门系列文章(一)

 

 添加成功后,如下所示:

C# Abp框架入门系列文章(一)

 

 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项目,等录以后,便可以查看菜单,如下所示:

C# Abp框架入门系列文章(一)

 

 点击菜单后,跳转到默认的Book首页,如下所示:

C# Abp框架入门系列文章(一)

 

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 });

然后运行项目,结果如下所示:

C# Abp框架入门系列文章(一)

 

 以上就是Abp的简单入门介绍,旨在抛转引玉,一起学习,共同进步。

备注

 浣溪沙·一曲新词酒一杯【作者】晏殊 【朝代】北宋

一曲新词酒一杯,去年天气旧亭台。夕阳西下几时回?

无可奈何花落去,似曾相识燕归来。小园香径独徘徊。

C# Abp框架入门系列文章(一)