[ABP教程]第一章 创建服务端

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

关于本教程#

在本系列教程中, 你将构建一个名为 Acme.BookStore 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:

  • 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 的类,如下所示:

Copy
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框架自动为你管理这些属性.
  • GuidBook实体的主键类型.

为了保持简单,本教程将实体属性保留为 public get/set . 如果你想了解关于DDD最佳实践,请参阅实体文档.

BookType枚举#

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

Copy
namespace Acme.BookStore.Books { public enum BookType { Undefined, Adventure, Biography, Dystopia, Fantastic, Horror, Science, ScienceFiction, Poetry } }

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

bookstore-book-and-booktype

将Book实体添加到DbContext中#

{{if DB == "EF"}}

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

Copy
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext> { public DbSet<Book> Books { get; set; } //... }

{{end}}

{{if DB == "Mongo"}}

添加 IMongoCollection<Book> Book 属性到 Acme.BookStore.MongoDB 项目的 BookStoreMongoDbContext 中.

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

{{end}}

{{if DB == "EF"}}

将Book实体映射到数据库表#

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

Copy
using Acme.BookStore.Books; using Microsoft.EntityFrameworkCore; using Volo.Abp; using Volo.Abp.EntityFrameworkCore.Modeling; namespace Acme.BookStore.EntityFrameworkCore { public static class BookStoreDbContextModelCreatingExtensions { public static void ConfigureBookStore(this ModelBuilder builder) { Check.NotNull(builder, nameof(builder)); /* 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创建和维护数据库架构. 打开菜单工具 > NuGet包管理器下的程序包管理控制台 (PMC).

Open Package Manager Console

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

Copy
Add-Migration "Created_Book_Entity"

bookstore-pmc-add-book-migration

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

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

如果你使用其他IDE而不是Visual Studio, 你可以使用 dotnet-ef 工具.

{{end}}

添加种子数据#

在运行应用程序之前最好将初始数据添加到数据库中. 本节介绍ABP框架的数据种子系统. 如果你不想创建种子数据可以跳过本节,但是建议你遵循它来学习这个有用的ABP Framework功能。

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

Copy
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 应用程序来更新数据库:

bookstore-dbmigrator-on-solution

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

创建应用程序#

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

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

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

BookDto#

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

Copy
using System; using Volo.Abp.Application.Dtos; namespace Acme.BookStore { 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类被用来在 表示层应用层 传递数据.查看DTO文档查看更多信息.
  • 为了在页面上展示书籍信息,BookDto被用来将书籍数据传递到表示层.
  • BookDto继承自 AuditedEntityDto<Guid>.跟上面定义的 Book 实体一样具有一些审计属性.

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

Copy
using Acme.BookStore.Books; using AutoMapper; namespace Acme.BookStore { public class BookStoreApplicationAutoMapperProfile : Profile { public BookStoreApplicationAutoMapperProfile() { CreateMap<Book, BookDto>(); } } }

参阅 对象对对象映射 文档了解详情.

CreateUpdateBookDto#

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

Copy
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实体的映射,最终映射配置类如下:

Copy
using Acme.BookStore.Books; using AutoMapper; namespace Acme.BookStore { public class BookStoreApplicationAutoMapperProfile : Profile { public BookStoreApplicationAutoMapperProfile() { CreateMap<Book, BookDto>(); CreateMap<CreateUpdateBookDto, Book>(); } } }

IBookAppService#

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

Copy
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#

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

Copy
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#

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

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

Swagger UI#

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

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

bookstore-swagger

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

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

Copy
{ "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!

下一章#

参阅教程的下一章.

posted @   LandWind  阅读(423)  评论(2编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示
CONTENTS