文章目录
关于本教程
本教程基于版本3.1
在本教程系列中,您将构建一个名为Acme.BookStore
的基于ABP的Web应用程序。该应用程序用于管理书籍及其作者的列表。它是使用以下技术开发的:
- 实体框架核心作为ORM提供者。
- MVC/Razor页面作为UI框架。
本教程分为以下部分:
第1部分:创建服务器端(此部分)
下载源代码
MVC (Razor Pages) UI with EF Core
创建解决方案
在开始开发之前,请按照入门教程来创建一个名为Acme.BookStore
的新解决方案并运行它。
创建书籍实体
启动模板中的领域层分为两个项目:
Acme.BookStore.Domain
包含您的实体,领域服务和其他核心域对象。Acme.BookStore.Domain.Shared
包含constants
,enums
或其他领域相关的那些能够与客户共享的对象。
因此,在解决方案的领域层(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
。聚合根是域驱动设计概念,可以将其视为直接查询和使用的根实体(有关更多信息,请参见实体文档)。 -
Book
从实体继承了AuditedAggregateRoot
,其在AggregateRoot
类的顶部添加了一些基本的审计属性(比如CreationTime
,CreatorId
,LastModificationTime
…)。ABP会自动为您管理这些属性。 -
Guid
是主键类型的Book
实体。
为了简单起见,本教程将实体属性使用public 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
}
}
最终的文件夹/文件结构应如下所示:
将图书实体添加到DbContext
EF Core要求将实体与您的DbContext
关联。最简单的方法是在Acme.BookStore.EntityFrameworkCore
项目的BookStoreDbContext
类中添加一个DbSet
属性,如下所示:
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
public DbSet<Book> Books { get; set; }
//...
}
将图书实体映射到数据库表
在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
文件夹内创建一个新的迁移类。
在更新数据库之前,请阅读以下部分,以了解如何将一些初始数据插入到数据库中。
如果使用的不是Visual Studio,则可以使用此处文档中的
dotnet-ef
工具。
添加样本种子数据
在运行应用程序之前,最好在数据库中包含一些初始数据。本节介绍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>
(默认存储库)将两本书插入数据库。
更新数据库
运行Acme.BookStore.DbMigrator应用程序以更新数据库:
.DbMigrator
是一个控制台应用程序,可以运行该应用程序以迁移数据库架构并将数据植入到开发和生产环境中。
创建应用程序服务
应用程序层分为两个项目:
在本部分中,您将创建一个应用程序服务,以使用ABP框架的CrudAppService
基类来get, create, update和delete书籍。
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
对象。当您定义正确的映射时,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
项目的Books
文件夹(命名空间)中创建一个CreateUpdateBookDto
类:
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
项目的Books
文件夹(命名空间)中创建一个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
现在是实现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, //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
对象以及将Book
对象映射到CreateUpdateBookDto
对象。启动模板使用AutoMapper库作为对象映射提供程序。我们之前已经定义了映射,因此它将按预期工作。
自动API控制器
在典型的ASP.NET Core
应用程序中,您创建API控制器以将应用程序服务公开为HTTP API端点。这允许浏览器或第三方客户端通过HTTP
调用它们。
ABP可以按照约定自动将您的应用程序服务配置为MVC API控制器。
Swagger UI
启动模板配置为使用Swashbuckle.AspNetCore
库运行Swagger UI
。通过在浏览器上按CTRL+F5
并导航到https://localhost:<port>/swagger/
来运行该应用程序。(替换<port>
为您自己的端口号。)
您将看到一些内置服务端点以及该Book
服务及其REST样式
端点:
Swagger有一个不错的接口来测试API。
如果您尝试执行[GET] /api/app/bookAPI
以获取书籍列表,则服务器会返回这样的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!
下一部分
请参阅本教程的下一部分。