Web应用程序开发教程 - 第一章: 创建服务端

    • Entity Framework Core 做为ORM提供程序.
    • MVC / Razor Pages 做为UI框架.

    本教程分为以下部分:

    下载源码

    本教程根据你的UIDatabase偏好有多个版,我们准备了两种可供下载的源码组合:

    在开始开发之前,请按照入门教程创建名为 Acme.BookStore 的新解决方案.

    创建Book实体

    启动模板中的领域层分为两个项目:

    • Acme.BookStore.Domain包含你的, 领域服务和其他核心域对象.
    • Acme.BookStore.Domain.Shared包含可与客户共享的常量,枚举或其他域相关对象.

    在解决方案的领域层(Acme.BookStore.Domain项目)中定义你的实体.

    该应用程序的主要实体是Book. 在Acme.BookStore.Domain项目中创建一个 Books 文件夹并在其中添加一个名为 Book 的类,如下所示:

    • ABP为实体提供了两个基本的基类: AggregateRootEntity. Aggregate Root是 概念之一. 可以视为直接查询和处理的根实体(请参阅实体文档).
    • Book实体继承了AuditedAggregateRoot,AuditedAggregateRoot类在AggregateRoot类的基础上添加了一些审计属性(CreationTime, CreatorId, LastModificationTime 等). ABP框架自动为你管理这些属性.
    • GuidBook实体的主键类型.

    上面所用到了 BookType 枚举,在 Acme.BookStore.Domain.Shared 项目创建 BookType.

    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实体添加到DbContext中

    EF Core需要你将实体和 DbContext 建立关联.最简单的做法是在Acme.BookStore.EntityFrameworkCore项目的BookStoreDbContext类中添加DbSet属性.如下所示:

    1. public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
    2. {
    3. public DbSet<Book> Books { get; set; }
    4. //...
    5. }

    将Book实体映射到数据库表

    Acme.BookStore.EntityFrameworkCore 项目中打开 BookStoreDbContextModelCreatingExtensions.cs 文件,添加 Book 实体的映射代码. 最终类应为:

    1. using Acme.BookStore.Books;
    2. using Microsoft.EntityFrameworkCore;
    3. using Volo.Abp;
    4. using Volo.Abp.EntityFrameworkCore.Modeling;
    5. namespace Acme.BookStore.EntityFrameworkCore
    6. {
    7. public static class BookStoreDbContextModelCreatingExtensions
    8. {
    9. public static void ConfigureBookStore(this ModelBuilder builder)
    10. {
    11. Check.NotNull(builder, nameof(builder));
    12. /* Configure your own tables/entities inside here */
    13. builder.Entity<Book>(b =>
    14. {
    15. b.ToTable(BookStoreConsts.DbTablePrefix + "Books",
    16. b.ConfigureByConvention(); //auto configure for the base class props
    17. b.Property(x => x.Name).IsRequired().HasMaxLength(128);
    18. });
    19. }
    20. }
    21. }
    • BookStoreConsts 含有用于表的架构和表前缀的常量值. 你不必使用它,但建议在单点控制表前缀.
    • ConfigureByConvention() 方法优雅的配置/映射继承的属性,应始终对你所有的实体使用它.

    启动模板使用EF Core Code First Migrations创建和维护数据库架构. 打开菜单工具 > NuGet包管理器下的程序包管理控制台 (PMC).

    选择 Acme.BookStore.EntityFrameworkCore.DbMigrations 做为默认项目然后执行以下命令:

    它会在 Acme.BookStore.EntityFrameworkCore.DbMigrations 项目中的 Migrations 文件内创建一个新的迁移类.

    在更新数据库之前,请阅读下面的部分了解如何将一些初始数据插入到数据库.

    添加种子数据

    *.Domain 项目下创建派生 IDataSeedContributor 的类,并且拷贝以下代码:

    1. using System;
    2. using System.Threading.Tasks;
    3. using Acme.BookStore.Books;
    4. using Volo.Abp.Data;
    5. using Volo.Abp.DependencyInjection;
    6. namespace Acme.BookStore
    7. {
    8. public class BookStoreDataSeederContributor
    9. : IDataSeedContributor, ITransientDependency
    10. {
    11. private readonly IRepository<Book, Guid> _bookRepository;
    12. public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository)
    13. {
    14. _bookRepository = bookRepository;
    15. }
    16. public async Task SeedAsync(DataSeedContext context)
    17. {
    18. if (await _bookRepository.GetCountAsync() <= 0)
    19. {
    20. await _bookRepository.InsertAsync(
    21. new Book
    22. {
    23. Name = "1984",
    24. Type = BookType.Dystopia,
    25. PublishDate = new DateTime(1949, 6, 8),
    26. Price = 19.84f
    27. },
    28. autoSave: true
    29. );
    30. await _bookRepository.InsertAsync(
    31. new Book
    32. {
    33. Name = "The Hitchhiker's Guide to the Galaxy",
    34. Type = BookType.ScienceFiction,
    35. PublishDate = new DateTime(1995, 9, 27),
    36. Price = 42.0f
    37. },
    38. autoSave: true
    39. );
    40. }
    41. }
    42. }
    43. }
    • 如果数据库中当前没有图书,则此代码使用 IRepository<Book, Guid>(默认为repository)将两本书插入数据库.

    更新数据库

    运行 Acme.BookStore.DbMigrator 应用程序来更新数据库:

    .DbMigrator 是一个控制台使用程序,可以在开发生产环境迁移数据库架构初始化种子数据.

    应用程序层由两个分离的项目组成:

    • Acme.BookStore.Application.Contracts 包含你的DTO和接口.
    • Acme.BookStore.Application 包含你的应用服务实现.

    在本部分中,你将创建一个应用程序服务,使用ABP Framework的 CrudAppService 基类来获取,创建,更新和删除书籍.

    BookDto

    CrudAppService 基类需要定义实体的基本DTO. 在 Acme.BookStore.Application.Contracts 项目中创建一个名为 BookDto 的DTO类:

    1. using System;
    2. using Volo.Abp.Application.Dtos;
    3. namespace Acme.BookStore
    4. {
    5. public class BookDto : AuditedEntityDto<Guid>
    6. {
    7. public string Name { get; set; }
    8. public BookType Type { get; set; }
    9. public DateTime PublishDate { get; set; }
    10. public float Price { get; set; }
    11. }
    12. }
    • 为了在页面上展示书籍信息,BookDto被用来将书籍数据传递到表示层.
    • BookDto继承自 AuditedEntityDto<Guid>.跟上面定义的 Book 实体一样具有一些审计属性.
    1. using Acme.BookStore.Books;
    2. using AutoMapper;
    3. namespace Acme.BookStore
    4. {
    5. public class BookStoreApplicationAutoMapperProfile : Profile
    6. {
    7. public BookStoreApplicationAutoMapperProfile()
    8. {
    9. CreateMap<Book, BookDto>();
    10. }
    11. }

    Acme.BookStore.Application.Contracts项目中创建一个名为 CreateUpdateBookDto 的DTO类:

    • 这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息.
    • 它定义了数据注释属性(如[Required])来定义属性的验证. DTO由ABP框架自动验证.

    就像上面的BookDto一样,创建一个从CreateUpdateBookDto对象到Book实体的映射,最终映射配置类如下:

    1. using Acme.BookStore.Books;
    2. using AutoMapper;
    3. namespace Acme.BookStore
    4. {
    5. public class BookStoreApplicationAutoMapperProfile : Profile
    6. {
    7. public BookStoreApplicationAutoMapperProfile()
    8. {
    9. CreateMap<Book, BookDto>();
    10. CreateMap<CreateUpdateBookDto, Book>();
    11. }
    12. }
    13. }

    IBookAppService

    下一步是为应用程序定义接口,在Acme.BookStore.Application.Contracts项目中定义一个名为IBookAppService的接口:

    1. using System;
    2. using Volo.Abp.Application.Dtos;
    3. using Volo.Abp.Application.Services;
    4. namespace Acme.BookStore.Books
    5. {
    6. public interface IBookAppService :
    7. ICrudAppService< //Defines CRUD methods
    8. BookDto, //Used to show books
    9. Guid, //Primary key of the book entity
    10. PagedAndSortedResultRequestDto, //Used for paging/sorting
    11. CreateUpdateBookDto> //Used to create/update a book
    12. {
    13. }
    14. }
    • 框架定义应用程序服务的接口不是必需的. 但是,它被建议作为最佳实践.
    • ICrudAppService定义了常见的CRUD方法:GetAsync,GetListAsync,CreateAsync,UpdateAsyncDeleteAsync. 你可以从空的IApplicationService接口继承并手动定义自己的方法(将在下一部分中完成).
    • ICrudAppService有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定(例如使用不同的DTO进行创建和更新).

    BookAppService

    Acme.BookStore.Application项目中创建名为 BookAppServiceIBookAppService 实现:

    1. using System;
    2. using Volo.Abp.Application.Dtos;
    3. using Volo.Abp.Application.Services;
    4. using Volo.Abp.Domain.Repositories;
    5. namespace Acme.BookStore.Books
    6. {
    7. public class BookAppService :
    8. CrudAppService<
    9. Book, //The Book entity
    10. BookDto, //Used to show books
    11. Guid, //Primary key of the book entity
    12. PagedAndSortedResultRequestDto, //Used for paging/sorting
    13. CreateUpdateBookDto>, //Used to create/update a book
    14. IBookAppService //implement the IBookAppService
    15. {
    16. public BookAppService(IRepository<Book, Guid> repository)
    17. : base(repository)
    18. {
    19. }
    20. }
    21. }
    • BookAppService继承了CrudAppService<...>.它实现了 ICrudAppService 定义的CRUD方法.
    • BookAppService注入IRepository <Book,Guid>,这是Book实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储. 请参阅

    你通常创建Controller以将应用程序服务公开为HTTP API端点. 因此允许浏览器或第三方客户端通过AJAX调用它们.

    ABP可以自动按照惯例将你的应用程序服务配置为MVC API控制器.

    Swagger UI

    启动模板配置为使用运行swagger UI. 运行应用程序并在浏览器中输入https://localhost:XXXX/swagger/(用你自己的端口替换XXXX)作为URL.

    你会看到一些内置的接口和Book的接口,它们都是REST风格的:

    Swagger有一个很好的UI来测试API.

    你可以尝试执行[GET] /api/app/book API来获取书籍列表, 服务端会返回以下JSON结果:

    这很酷,因为我们没有编写任何代码来创建API控制器,但是现在我们有了一个可以正常使用的REST API!

    下一章