项目

本文档有多个版本。请选择最适合您的选项。

UI
Database

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

创建解决方案

开始开发之前,请按照 入门教程 创建一个名为 Acme.BookStore 的新解决方案并运行它。

创建解决方案之后

安装客户端包

ABP CLI 在创建应用程序时,会在后台运行 abp install-libs 命令,为你的解决方案安装所需的 NPM 包。

但是,有时可能需要手动运行此命令。例如,如果你克隆了应用程序,或者 node_modules 文件夹中的资源没有复制到 wwwroot/libs 文件夹,或者你向解决方案添加了新的客户端包依赖时,就需要运行此命令。

对于这些情况,请在解决方案的根目录运行 abp install-libs 命令以安装所有必需的 NPM 包:

abp install-libs

创建 Book 实体

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

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

因此,你需要在解决方案的领域层(Acme.BookStore.Domain 项目)中定义你的实体。

本应用程序的主要实体是 Book。在 Acme.BookStore.Domain 项目中创建一个 Books 文件夹(命名空间),并在其中添加一个 Book 类:

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聚合根 是一个 领域驱动设计 概念,可以将其视为直接被查询和操作的根实体(详见 实体文档)。
  • Book 实体继承自 AuditedAggregateRoot,它在 AggregateRoot 类的基础上添加了一些基本的 审计 属性(如 CreationTimeCreatorIdLastModificationTime 等)。ABP 会自动为你管理这些属性。
  • GuidBook 实体的 主键类型

为简化起见,本教程将实体属性保留为 公共 get/set。如果你想了解更多关于 DDD 最佳实践的信息,请参阅 实体文档

BookType 枚举

Book 实体使用了 BookType 枚举。在 Acme.BookStore.Domain.Shared 项目中创建一个 Books 文件夹(命名空间),并在其中添加 BookType 枚举:

namespace Acme.BookStore.Books;

public enum BookType
{
    Undefined,
    Adventure,
    Biography,
    Dystopia,
    Fantastic,
    Horror,
    Science,
    ScienceFiction,
    Poetry
}

最终的文件夹/文件结构应如下所示:

bookstore-book-and-booktype

将 Book 实体添加到 DbContext

Acme.BookStore.MongoDB 项目的 BookStoreMongoDbContext 中添加一个 IMongoCollection<Book> Books 属性:

using Acme.BookStore.Books;

public class BookStoreMongoDbContext : AbpMongoDbContext
{
    public IMongoCollection<Book> Books => Collection<Book>();
    //...
}

添加示例种子数据

在运行应用程序之前,数据库中有一些初始数据是很好的。本节介绍 ABP 的 数据种子 系统。如果你不想创建数据种子,可以跳过本节,但建议你跟着操作以学习这个有用的 ABP 功能。

*.Domain 项目中创建一个实现 IDataSeedContributor 接口的类,复制以下代码:

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>(默认的 仓储)向数据库插入两本书(如果其中没有任何书籍的话)。

更新数据库

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

bookstore-dbmigrator-on-solution

.DbMigrator 是一个控制台应用程序,可以在 开发生产 环境中运行,以 迁移数据库架构填充种子数据

创建应用服务

应用层分为两个项目:

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

在本节中,你将使用 ABP 的 CrudAppService 基类创建一个应用服务,用于获取、创建、更新和删除书籍。

BookDto

CrudAppService 基类要求为实体定义基础的 DTO。在 Acme.BookStore.Application.Contracts 项目中创建一个 Books 文件夹(命名空间),并在其中添加 BookDto 类:

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 用于将书籍数据传输到表示层,以便在 UI 上显示书籍信息。
  • BookDto 派生自 AuditedEntityDto<Guid>,它像上面定义的 Book 实体一样,具有审计属性。

在向表示层返回书籍时,需要将 Book 实体映射到 BookDto 对象。当你定义了正确的映射时,Mapperly 库可以自动执行此转换。启动模板预配置了 Mapperly。因此,你只需在 Acme.BookStore.Application 项目的 BookStoreApplicationMappers 类中定义映射即可:

[Mapper]
public partial class BookToBookDtoMapper : MapperBase<Book, BookDto>
{
    public override partial BookDto Map(Book source);

    public override partial void Map(Book source, BookDto destination);
}

详情请参见 对象到对象映射 文档。

CreateUpdateBookDto

Acme.BookStore.Application.Contracts 项目的 Books 文件夹(命名空间)中创建 CreateUpdateBookDto 类:

using System;
using System.ComponentModel.DataAnnotations;

namespace Acme.BookStore.Books;

public class CreateUpdateBookDto
{
    [Required]
    [StringLength(128)]
    public string Name { get; set; } = string.Empty;

    [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])来定义属性的验证规则。ABP 会自动验证 DTO

正如上面为 BookDto 所做的那样,我们应该定义从 CreateUpdateBookDto 对象到 Book 实体的映射。最终的类将如下所示:

[Mapper]
public partial class BookToBookDtoMapper : MapperBase<Book, BookDto>
{
    public override partial BookDto Map(Book source);

    public override partial void Map(Book source, BookDto destination);
}

[Mapper]
public partial class CreateUpdateBookDtoToBookMapper : MapperBase<CreateUpdateBookDto, Book>
{
    public override partial Book Map(CreateUpdateBookDto source);

    public override partial void Map(CreateUpdateBookDto source, Book destination);
}

IBookAppService

下一步是为应用服务定义接口。在 Acme.BookStore.Application.Contracts 项目的 Books 文件夹(命名空间)中创建 IBookAppService 接口:

using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;

namespace Acme.BookStore.Books;

public interface IBookAppService :
    ICrudAppService< // 定义 CRUD 方法
        BookDto, // 用于展示书籍
        Guid, // Book 实体的主键
        PagedAndSortedResultRequestDto, // 用于分页/排序
        CreateUpdateBookDto> // 用于创建/更新书籍
{

}
  • 框架 不要求 为应用服务定义接口。但是,作为最佳实践,建议这样做。
  • ICrudAppService 定义了常见的 CRUD 方法:GetAsyncGetListAsyncCreateAsyncUpdateAsyncDeleteAsync。并非必须扩展它。相反,你可以从空的 IApplicationService 接口继承并手动定义自己的方法(这将在接下来的作者部分中完成)。
  • ICrudAppService 有一些变体,你可以为每个方法使用独立的 DTO(例如,为创建和更新使用不同的 DTO)。

BookAppService

现在该实现 IBookAppService 接口了。在 Acme.BookStore.Application 项目的 Books 命名空间(文件夹)中创建一个名为 BookAppService 的新类:

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, // Book 实体
        BookDto, // 用于展示书籍
        Guid, // Book 实体的主键
        PagedAndSortedResultRequestDto, // 用于分页/排序
        CreateUpdateBookDto>, // 用于创建/更新书籍
    IBookAppService // 实现 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 对象。启动模板使用 Mapperly 库作为对象映射提供程序。我们之前已经定义了映射,因此它将按预期工作。

自动 API 控制器

在典型的 ASP.NET Core 应用程序中,你需要创建 API 控制器 来将应用服务公开为 HTTP API 端点。这允许浏览器或第三方客户端通过 HTTP 调用它们。

ABP 可以按照约定 自动地 将你的应用服务配置为 MVC API 控制器。

Swagger UI

启动模板配置为使用 Swashbuckle.AspNetCore 库运行 Swagger UI。按 CTRL+F5 运行应用程序(Acme.BookStore.Blazor),并在浏览器中导航到 https://localhost:<port>/swagger/。请将 <port> 替换为你自己的端口号。

你将看到一些内置的服务端点,以及 Book 服务及其 REST 风格的端点:

bookstore-swagger

Swagger 提供了一个很好的界面来测试 API。

如果你尝试执行 [GET] /api/app/book API 来获取图书列表,服务器会返回如下的 JSON 结果:

{
  "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"
    }
  ]
}

这非常棒,因为我们还没有编写任何代码来创建 API 控制器,但现在我们有了一个完全可用的 REST API!


在本文档中