Abp vNext 学习第二弹 - 创建服务端 创建解决方案 参考上一篇 :Abp vNext 学习(1) .
创建书籍实体 启动模板中的领域层分为两个项目:
Acme.BookStore.Domain包含你的实体, 领域服务和其他核心域对象.
Acme.BookStore.Domain.Shared包含可与客户共享的常量,枚举或其他域相关对象.
在解决方案的领域层(Acme.BookStore.Domain项目)中定义你的实体.
该应用程序的主要实体是Book. 在Acme.BookStore.Domain项目中创建一个 Books 文件夹(命名空间),并在其中添加名为 Book 的类,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using System;using Volo.Abp.Domain.Entities.Auditing;namespace Acme.BookStore.Books { public class Book : AuditedAggregateRoot <Guid > { public string Name { get ; set ; } public BookType Type { get ; set ; } public DateTime PublishDate { get ; set ; } public float Price { get ; set ; } } }
ABP为实体提供了两个基本的基类: AggregateRoot
和Entity
. Aggregate Root
是领域驱动设计 概念之一. 可以视为直接查询和处理的根实体.
Book实体继承了AuditedAggregateRoot
,AuditedAggregateRoot
类在AggregateRoot
类的基础上添加了一些基础审计属性(例如CreationTime
, CreatorId
, LastModificationTime
等). ABP框架自动为你管理这些属性.
Guid是Book实体的主键类型.
BookType枚举 Book实体使用了BookType
枚举. 在Acme.BookStore.Domain.Shared
项目中创建Books
文件夹(命名空间),并在其中添加BookType
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 namespace Acme.BookStore.Books { public enum BookType { Undefined, Adventure, Biography, Dystopia, Fantastic, Horror, Science, ScienceFiction, Poetry } }
将Book实体添加到DbContext中 EF Core需要你将实体和 DbContext
建立关联.最简单的做法是在Acme.BookStore.EntityFrameworkCore
项目的BookStoreDbContext
类中添加DbSet
属性.如下所示:
1 2 3 4 5 6 7 8 using Acme.BookStore.Books;public class BookStoreDbContext : AbpDbContext <BookStoreDbContext >{ public DbSet<Book> Books { get ; set ; } }
将Book实体映射到数据库表 打开BookStoreDbContext
类的OnModelCreating
方法,为Book
实体添加映射代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 using Acme.BookStore.Books;using Volo.Abp.EntityFrameworkCore.Modeling;... namespace Acme.BookStore.EntityFrameworkCore { public class BookStoreDbContext : AbpDbContext <BookStoreDbContext >, IIdentityDbContext , ITenantManagementDbContext { ... protected override void OnModelCreating (ModelBuilder builder ) { base .OnModelCreating(builder); builder.ConfigurePermissionManagement(); ... builder.Entity<Book>(b => { b.ToTable(BookStoreConsts.DbTablePrefix + "Books" , BookStoreConsts.DbSchema); b.ConfigureByConvention(); b.Property(x => x.Name).IsRequired().HasMaxLength(128 ); }); } } }
BookStoreConsts
含有用于表的架构和表前缀的常量值. 使用它不是强制的,但建议在统一的地方控制表前缀.
ConfigureByConvention()
方法优雅的配置/映射继承的属性,应对所有的实体使用它.
添加数据迁移 本示例使用EF Core Code First Migrations.因为我们修改了数据库映射配置,我们必须创建一个新的迁移并且应用到数据库.
在 Acme.BookStore.EntityFrameworkCore
目录打开命令行终端输入以下命令:
1 dotnet ef migrations add Created_Book_Entity
它会添加新迁移类到项目中
如果你使用Visual Studio, 你也许想要在包管理控制台(PMC )中使用 Add-Migration Created_Book_Entity -c BookStoreDbContext
和 Update-Database -Context BookStoreDbContext
命令. 确保 Acme.BookStore.Web
是启动项目并且 Acme.BookStore.EntityFrameworkCore.DbMigrations
是 PMC 的默认项目.
添加种子数据
在运行应用程序之前最好将初始数据添加到数据库中. 如果你不想创建种子数据可以跳过本节,但是建议你遵循它来学习这个有用的ABP Framework功能。
在 *.Domain 项目下创建 IDataSeedContributor 的派生类,并且拷贝以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 using System;using System.Threading.Tasks;using Acme.BookStore.Books;using Volo.Abp.Data;using Volo.Abp.DependencyInjection;using Volo.Abp.Domain.Repositories;namespace Acme.BookStore { public class BookStoreDataSeederContributor : IDataSeedContributor , ITransientDependency { private readonly IRepository<Book, Guid> _bookRepository; public BookStoreDataSeederContributor (IRepository<Book, Guid> bookRepository ) { _bookRepository = bookRepository; } public async Task SeedAsync (DataSeedContext context ) { if (await _bookRepository.GetCountAsync() <= 0 ) { await _bookRepository.InsertAsync( new Book { Name = "1984" , Type = BookType.Dystopia, PublishDate = new DateTime(1949 , 6 , 8 ), Price = 19.84f }, autoSave: true ); await _bookRepository.InsertAsync( new Book { Name = "The Hitchhiker's Guide to the Galaxy" , Type = BookType.ScienceFiction, PublishDate = new DateTime(1995 , 9 , 27 ), Price = 42.0f }, autoSave: true ); } } } }
如果数据库中当前没有图书,则此代码使用 IRepository<Book, Guid>
(默认repository)将两本书插入数据库.
更新数据库 运行 Acme.BookStore.DbMigrator
应用程序来更新数据库
.DbMigrator
是一个控制台使用程序,可以在开发和生产环境迁移数据库架构和初始化种子数据.
创建应用程序 应用程序层由两个分离的项目组成:
.Application.Contracts
包含你的DTO和应用服务接口.
.Application
包含你的应用服务实现. 在本部分中,你将创建一个应用程序服务,使用ABP Framework的 CrudAppService
基类来获取,创建,更新和删除书籍.
BookDto CrudAppService
基类需要定义实体的基本DTO. 在 .Application.Contracts
项目中创建 Books
文件夹(命名空间), 并在其中添加名为 BookDto
的DTO类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using System;using Volo.Abp.Application.Dtos;namespace Acme.BookStore.Books { public class BookDto : AuditedEntityDto <Guid > { public string Name { get ; set ; } public BookType Type { get ; set ; } public DateTime PublishDate { get ; set ; } public float Price { get ; set ; } } }
DTO类 被用来在 表示层 和 应用层 传递数据.
为了在用户界面上展示书籍信息,BookDto
被用来将书籍数据传递到表示层.
BookDto
继承自 AuditedEntityDto<Guid>
.与上面定义的 Book
实体一样具有一些审计属性.
在将书籍返回到表示层时,需要将Book
实体转换为BookDto
对象. AutoMapper 库可以在定义了正确的映射时自动执行此转换. 启动模板配置了AutoMapper
,因此你只需在.Application
项目的BookStoreApplicationAutoMapperProfile
类中定义映射:
1 2 3 4 5 6 7 8 9 10 11 12 13 using Acme.BookStore.Books;using AutoMapper;namespace Acme.BookStore { public class BookStoreApplicationAutoMapperProfile : Profile { public BookStoreApplicationAutoMapperProfile ( ) { CreateMap<Book, BookDto>(); } } }
CreateUpdateBookDto 在.Application.Contracts
项目中创建 Books
文件夹(命名空间),并在其中添加名为 CreateUpdateBookDto
的DTO类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 using System;using System.ComponentModel.DataAnnotations;namespace Acme.BookStore.Books { public class CreateUpdateBookDto { [Required ] [StringLength(128) ] public string Name { get ; set ; } [Required ] public BookType Type { get ; set ; } = BookType.Undefined; [Required ] [DataType(DataType.Date) ] public DateTime PublishDate { get ; set ; } = DateTime.Now; [Required ] public float Price { get ; set ; } } }
这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息.
它定义了数据注释特性(如[Required
])来定义属性的验证规则. DTO由ABP框架自动验证.
就像上面的BookDto
一样,创建一个从CreateUpdateBookDto
对象到Book
实体的映射,最终映射配置类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 using Acme.BookStore.Books;using AutoMapper;namespace Acme.BookStore { public class BookStoreApplicationAutoMapperProfile : Profile { public BookStoreApplicationAutoMapperProfile ( ) { CreateMap<Book, BookDto>(); CreateMap<CreateUpdateBookDto, Book>(); } } }
IBookAppService 下一步是为应用程序定义接口,在.Application.Contracts
项目创建 Books 文件夹(命名空间),并在其中添加名为IBookAppService
的接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using System;using Volo.Abp.Application.Dtos;using Volo.Abp.Application.Services;namespace Acme.BookStore.Books { public interface IBookAppService : ICrudAppService < //Defines CRUD methods BookDto , //Used to show books Guid , //Primary key of the book entity PagedAndSortedResultRequestDto , //Used for paging /sorting CreateUpdateBookDto > { } }
框架定义应用程序服务的接口不是必需的 . 但是,它被建议作为最佳实践.
ICrudAppService
定义了常见的CRUD 方法:GetAsync
,GetListAsync
,CreateAsync
,UpdateAsync
和DeleteAsync
. 从这个接口扩展不是必需的,你可以从空的IApplicationService
接口继承并手动定义自己的方法(将在下一部分中完成).
ICrudAppService
有一些变体, 你可以在每个方法中使用单独的DTO(例如使用不同的DTO进行创建和更新).
BookAppService 是时候实现IBookAppService
接口了.在.Application
项目中创建 Books
文件夹(命名空间),并在其中添加名为 BookAppService
的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using System;using Volo.Abp.Application.Dtos;using Volo.Abp.Application.Services;using Volo.Abp.Domain.Repositories;namespace Acme.BookStore.Books { public class BookAppService : CrudAppService < Book , //The Book entity BookDto , //Used to show books Guid , //Primary key of the book entity PagedAndSortedResultRequestDto , //Used for paging /sorting CreateUpdateBookDto >, IBookAppService { public BookAppService (IRepository<Book, Guid> repository ) : base (repository ) { } } }
BookAppService
继承了CrudAppService<...>
.它实现了 ICrudAppService
定义的CRUD方法.
BookAppService
注入IRepository <Book,Guid>
,这是Book
实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储.
BookAppService
使用IObjectMapper
将Book
对象转换为BookDto
对象, 将CreateUpdateBookDto
对象转换为Book
对象. 启动模板使用AutoMapper
库作为对象映射提供程序. 我们之前定义了映射, 因此它将按预期工作.
自动生成API Controllers 在典型的ASP.NET Core应用程序中,你创建API Controller 以将应用程序服务公开为HTTP API 端点. 这将允许浏览器或第三方客户端通过HTTP调用它们.
ABP可以自动按照约定将你的应用程序服务配置为MVC API控制器.
Swagger UI 启动模板配置为使用Swashbuckle.AspNetCore 运行swagger UI . 运行应用程序并在浏览器中输入https://localhost:XXXX/swagger/
(用你自己的端口替换XXXX)作为URL. 使用CTRL+F5
运行应用程序 (.Web项目)并使用浏览器访问https://localhost:<port>/swagger/
. 使用你自己的端口号替换 <port>
.
你会看到一些内置的服务端点和Book
服务,它们都是REST风格的端点。
Swagger有一个很好的UI来测试API.
你可以尝试执行[GET] /api/app/book API
来获取书籍列表, 服务端会返回以下JSON结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 { "totalCount" : 2 , "items" : [ { "name" : "The Hitchhiker's Guide to the Galaxy" , "type" : 7 , "publishDate" : "1995-09-27T00:00:00" , "price" : 42 , "lastModificationTime" : null , "lastModifierId" : null , "creationTime" : "2020-07-03T21:04:18.4607218" , "creatorId" : null , "id" : "86100bb6-cbc1-25be-6643-39f62806969c" }, { "name" : "1984" , "type" : 3 , "publishDate" : "1949-06-08T00:00:00" , "price" : 19.84 , "lastModificationTime" : null , "lastModifierId" : null , "creationTime" : "2020-07-03T21:04:18.3174016" , "creatorId" : null , "id" : "41055277-cce8-37d7-bb37-39f62806960b" } ] }
Demo Codes
github commit
下一章 参阅 Abp vNext 学习(3) 。