[ABP教程]第一章 创建服务端
Web应用程序开发教程 - 第一章: 创建服务端
关于本教程
在本系列教程中, 你将构建一个名为 Acme.BookStore 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:
- Entity Framework Core 做为ORM提供程序.
- MVC / Razor Pages 做为UI框架.
本教程分为以下部分:
- Part 1: 创建服务端 (本章)
- Part 2: 图书列表页面
- Part 3: 创建,更新和删除图书
- Part 4: 集成测试
- Part 5: 授权
- Part 6: 作者: 领域层
- Part 7: 作者: 数据库集成
- Part 8: 作者: 应用服务层
- Part 9: 作者: 用户页面
- Part 10: 图书到作者的关系
下载源码
本教程根据你的UI 和 Database偏好有多个版,我们准备了两种可供下载的源码组合:
创建解决方案
在开始开发之前,请按照入门教程创建名为 Acme.BookStore 的新解决方案.
创建Book实体
启动模板中的领域层分为两个项目:
在解决方案的领域层(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为实体提供了两个基本的基类:
AggregateRoot和Entity. Aggregate Root是领域驱动设计 概念之一. 可以视为直接查询和处理的根实体(请参阅实体文档). Book实体继承了AuditedAggregateRoot,AuditedAggregateRoot类在AggregateRoot类的基础上添加了一些审计属性(CreationTime,CreatorId,LastModificationTime等). ABP框架自动为你管理这些属性.Guid是Book实体的主键类型.
为了保持简单,本教程将实体属性保留为 public get/set . 如果你想了解关于DDD最佳实践,请参阅实体文档.
BookType枚举
上面所用到了 BookType 枚举,在 Acme.BookStore.Domain.Shared 项目创建 BookType.
namespace Acme.BookStore.Books
{
public enum BookType
{
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry
}
}
最终的文件夹/文件结构应该如下所示:

将Book实体添加到DbContext中
{{if DB == "EF"}}
EF Core需要你将实体和 DbContext 建立关联.最简单的做法是在Acme.BookStore.EntityFrameworkCore项目的BookStoreDbContext类中添加DbSet属性.如下所示:
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
public DbSet<Book> Books { get; set; }
//...
}
{{end}}
{{if DB == "Mongo"}}
添加 IMongoCollection<Book> Book 属性到 Acme.BookStore.MongoDB 项目的 BookStoreMongoDbContext 中.
public class BookStoreMongoDbContext : AbpMongoDbContext
{
public IMongoCollection<Book> Books => Collection<Book>();
//...
}
{{end}}
{{if DB == "EF"}}
将Book实体映射到数据库表
在 Acme.BookStore.EntityFrameworkCore 项目中打开 BookStoreDbContextModelCreatingExtensions.cs 文件,添加 Book 实体的映射代码. 最终类应为:
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).

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

它会在 Acme.BookStore.EntityFrameworkCore.DbMigrations 项目中的 Migrations 文件内创建一个新的迁移类.
在更新数据库之前,请阅读下面的部分了解如何将一些初始数据插入到数据库.
如果你使用其他IDE而不是Visual Studio, 你可以使用
dotnet-ef工具.
{{end}}
添加种子数据
在运行应用程序之前最好将初始数据添加到数据库中. 本节介绍ABP框架的数据种子系统. 如果你不想创建种子数据可以跳过本节,但是建议你遵循它来学习这个有用的ABP Framework功能。
在 *.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>(默认为repository)将两本书插入数据库.
更新数据库
运行 Acme.BookStore.DbMigrator 应用程序来更新数据库:

.DbMigrator 是一个控制台使用程序,可以在开发和生产环境迁移数据库架构和初始化种子数据.
创建应用程序
应用程序层由两个分离的项目组成:
在本部分中,你将创建一个应用程序服务,使用ABP Framework的 CrudAppService 基类来获取,创建,更新和删除书籍.
BookDto
CrudAppService 基类需要定义实体的基本DTO. 在 Acme.BookStore.Application.Contracts 项目中创建一个名为 BookDto 的DTO类:
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类中定义映射:
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类:
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实体的映射,最终映射配置类如下:
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的接口:
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,UpdateAsync和DeleteAsync. 你可以从空的IApplicationService接口继承并手动定义自己的方法(将在下一部分中完成).ICrudAppService有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定(例如使用不同的DTO进行创建和更新).
BookAppService
在Acme.BookStore.Application项目中创建名为 BookAppService 的 IBookAppService 实现:
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使用IObjectMapper将Book对象转换为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风格的:

Swagger有一个很好的UI来测试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!
下一章
参阅教程的下一章.
[ABP教程]第一章 创建服务端的更多相关文章
- [ABP教程]第二章 图书列表页面
Web应用程序开发教程 - 第二章: 图书列表页面 关于本教程 在本系列教程中, 你将构建一个名为 Acme.BookStore 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开 ...
- [Learn Android Studio 汉化教程]第一章 : Android Studio 介绍
注:为了看上去比较清晰这里只转载了中文 原地址: [Learn Android Studio 汉化教程]第一章 : Android Studio 介绍 本章将引导您完成安装和设置开发环境,然后你就可 ...
- javascript进阶教程第一章案例实战
javascript进阶教程第一章案例实战 一.学习任务 通过几个案例练习回顾学过的知识 通过练习积累JS的使用技巧 二.实例 练习1:删除确认提示框 实例描述: 防止用户小心单击了“删除”按钮,在用 ...
- 《进击吧!Blazor!》系列入门教程 第一章 8.部署
<进击吧!Blazor!>是本人与张善友老师合作的Blazor零基础入门教程视频,此教程能让一个从未接触过Blazor的程序员掌握开发Blazor应用的能力. 视频地址:https://s ...
- SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(转载)
SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(Finchley版本) 转载请标明出处:http://blog.csdn.net/forezp/article/details ...
- 村田噪声抑制基础教程-第一章 需要EMI静噪滤波器的原因
1-1. 简介 EMI静噪滤波器 (EMIFIL®) 是为电子设备提供电磁噪声抑制的电子元件,配合屏蔽罩和其他保护装置一起使用.这种滤波器仅从通过连线传导的电流中提取并移除引起电磁噪声的元件.第1章说 ...
- 第一章 创建WEB项目
第一章 创建WEB项目 一.Eclipse创建WEB项目 方法/步骤1 首先,你要先打开Eclipse软件,打开后在工具栏依次点击[File]>>>[New]>>&g ...
- node.js中ws模块创建服务端和客户端,网页WebSocket客户端
首先下载websocket模块,命令行输入 npm install ws 1.node.js中ws模块创建服务端 // 加载node上websocket模块 ws; var ws = require( ...
- (十分钟视频教程)nodejs基础实战教程3:react服务端渲染入门篇
视频截图如下: (具体视频见文末) 前言: 这是小猫的第三篇node教程,本篇内容是由公众号粉丝票选得出的,相信大家对这篇教程是抱有较大希望的,这篇教程由小猫和一位多年的好朋友合作完成(笔名:谷雨,博 ...
随机推荐
- KafKa简介和利用docker配置kafka集群及开发环境
KafKa的基本认识,写的很好的一篇博客:https://www.cnblogs.com/sujing/p/10960832.html 问题:1.kafka是什么?Kafka是一种高吞吐量的分布式发布 ...
- 学习一下 SpringCloud (一)-- 从单体架构到微服务架构、代码拆分(maven 聚合)
一.架构演变 1.系统架构.集群.分布式系统 简单理解 (1)什么是系统架构? [什么是系统架构?] 系统架构 描述了 在应用程序内部,如何根据 业务.技术.灵活性.可扩展性.可维护性 等因素,将系统 ...
- 团队作业part1--团队展示&选题
一.团队展示 1.队名 DiligentVegetableChicken 2.队员信息 纪昂学(组长):3118005053 廖业成:3118005060 蔡越:3118005086 周梓波:3118 ...
- 【题解】「CF1182B」Plus from Picture
这是一道超级水的模拟 + 简单搜索. 说说思路: 先找到中心点,就是自己和上下左右都为 * 的. 上下左右上的所有 * 都删掉,然后再看看有没有多余的 * 如果有输出 NO 否则输出 YES. 比如说 ...
- rocketMq指定broker ip地址,适合解决云主机部署问题
在工作中遇到了一个这个问题,就是我们rocketmq是部署在云主机上的 但是我们的开发同事在自己的电脑连接rocketmq链接不上 报错显示Caused by: org.apache.rocket ...
- git 远端版本回退
情景:本地更改推送远端后,想要回退到自己推送之前的某个版本. 比如想回退的分支为 test 分支. 风险:远端回退到某一版本后,之后的所有推送都没了(对应的日志记录也没了).如果是团队开发,不仅自己推 ...
- 【Django】django.core.exceptions.ImproperlyConfigured: mysqlclient 1.4.0 or newer is required;
报错信息 django.core.exceptions.ImproperlyConfigured: mysqlclient 1.4.0 or newer is required; you have 0 ...
- 从函数到包的Python代码层次
代码层次 Python是一门脚本语言,新建一个.py文件,写点代码,就可以跑起来了,无论放哪都可以.比如where.py文件: print("Where am I?") 那么问题来 ...
- 基于nacos注册中心的ribbon定制规则
前面说到基于nacos的注册发现有可以扩展实现我们自己的负载均衡算法(Nacos数据模型),来实现同集群调用,是基于spring.cloud.nacos.discovery.cluster-name参 ...
- Unity2D 人物移动切换人物图片
勾选Constraints_freeze Rotation_z轴锁定,防止碰撞偏移. public float moveSpeed = 3f;//定义移动速度 priv ...