[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教程,本篇内容是由公众号粉丝票选得出的,相信大家对这篇教程是抱有较大希望的,这篇教程由小猫和一位多年的好朋友合作完成(笔名:谷雨,博 ... 
随机推荐
- XSS漏洞防御之HttpOnly
			WWW服务依赖于Http协议实现,Http是无状态的协议,所以为了在各个会话之间传递信息,就需要使用Cookie来标记访问者的状态,以便服务器端识别用户信息. Cookie分为内存Cookie和硬盘C ... 
- 连接数从异常到 300 到 5(RDS MySQL 的一个大坑•后记)
			在 <记 RDS MySQL 的一个大坑> 中,我提到遇到 User juxxxxxxxxxx already has more than 'max_user_connections' a ... 
- 如何实现OSM地图本地发布并自定义配图
			目录 1.缘起 2.准备环境 2.1.安装linux系统 2.2.安装docker 2.3.安装Docker Compose 2.4.安装git 3.发布地图 3.1.拉取代码 3.2.测试网络 3. ... 
- AcWing 332. 股票交易
			大型补档计划 题目链接 \(f[i][j]\) 表示前 \(i\) 天,手里有 \(j\) 个股票挣得最多钱 买股票.枚举 \(u < i - W\) \(f[i][j] = max(f[u][ ... 
- STL——容器(Set & multiset)的默认构造 & 带参构造 & 对象的拷贝构造与赋值
			1. 默认构造 set<int> setInt; //一个存放int的set容器. set<float> setFloat; //一 ... 
- Linux端口被占用解决
			有时候关闭软件后,后台进程死掉,导致端口被占用.下面以JBoss端口8083被占用为例,列出详细解决过程. 解决方法: 1.查找被占用的端口 netstat -tln netstat -tln | g ... 
- ubuntu 16.04安装adobe reader
			终端输入:wget ftp://ftp.adobe.com/pub/adobe/reader/unix/9.x/9.5.5/enu/AdbeRdr9.5.5-1_i386linux_enu.deb s ... 
- 我是如何用go-zero 实现一个中台系统的
			最近发现golang社区里出了一个新星的微服务框架,来自好未来,光看这个名字,就很有奔头,之前,也只是玩过go-micro,其实真正的还没有在项目中运用过,只是觉得 微服务,grpc 这些很高大尚,还 ... 
- git单人本地操作
			git配置个人信息 git config --global user.name "用户名" git config --global user.email "邮箱" ... 
- [日常摸鱼]bzoj1257余数之和
			题意:输入$k,n$,求$\sum_{i=1}^n k \mod i$ $k \mod i=k-i*\lfloor \frac{k}{i} \rfloor $,$n$个$k$直接求和,后面那个东西像比 ... 
