Abp vNext 学习(2)

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为实体提供了两个基本的基类: AggregateRootEntity. 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);

/* Include modules to your migration db context */

builder.ConfigurePermissionManagement();
...

/* Configure your own tables/entities inside here */

builder.Entity<Book>(b =>
{
b.ToTable(BookStoreConsts.DbTablePrefix + "Books",
BookStoreConsts.DbSchema);
b.ConfigureByConvention(); //auto configure for the base class props
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 BookStoreDbContextUpdate-Database -Context BookStoreDbContext 命令. 确保 Acme.BookStore.Web 是启动项目并且 Acme.BookStore.EntityFrameworkCore.DbMigrationsPMC 的默认项目.

添加种子数据

在运行应用程序之前最好将初始数据添加到数据库中. 如果你不想创建种子数据可以跳过本节,但是建议你遵循它来学习这个有用的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> //Used to create/update a book
{

}
}
  • 框架定义应用程序服务的接口不是必需的. 但是,它被建议作为最佳实践.
  • ICrudAppService定义了常见的CRUD方法:GetAsync,GetListAsync,CreateAsync,UpdateAsyncDeleteAsync. 从这个接口扩展不是必需的,你可以从空的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>, //Used to create/update a book
IBookAppService //implement the IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{

}
}
}
  • BookAppService继承了CrudAppService<...>.它实现了 ICrudAppService 定义的CRUD方法.
  • BookAppService注入IRepository <Book,Guid>,这是Book实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储.
  • BookAppService使用IObjectMapperBook对象转换为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)