- 关于本教程
- 下载源代码
- 介绍
- IAuthorAppService
- AuthorDto
- GetAuthorListDto
- CreateAuthorDto
- UpdateAuthorDto
- AuthorAppService
- GetAsync
- GetListAsync
- CreateAsync
- UpdateAsync
- DeleteAsync
- 权限定义
- 对象到对象映射
- 数据播种器
- 测试作者应用服务
- 下一部分
本教程基于版本3.1
在本教程系列中,您将构建一个名为Acme.BookStore
的基于ABP的Web应用程序。该应用程序用于管理书籍及其作者的列表。它是使用以下技术开发的:
- 实体框架核心作为ORM提供者。
- MVC/Razor页面作为UI框架。
本教程分为以下部分:
第1部分:创建服务器端
第2部分:书籍列表页面
第3部分:创建、更新和删除书籍
第4部分:集成测试
第5部分:授权
第6部分:作者:领域层
第7部分:作者:数据库集成
第8部分:作者:应用程序层(此部分)
第9部分:作者:用户界面
第10部分:书与作者的关系
下载源代码MVC (Razor Pages) UI with EF Core
介绍本部分说明为之前创建的Author
实体创建应用程序层。
我们将首先创建应用程序服务接口和相关的DTO。在Acme.BookStore.Application.Contracts
项目的Authors
命名空间(文件夹)中创建一个名为IAuthorAppService
的新接口:
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.BookStore.Authors
{
public interface IAuthorAppService : IApplicationService
{
Task GetAsync(Guid id);
Task GetListAsync(GetAuthorListDto input);
Task CreateAsync(CreateAuthorDto input);
Task UpdateAsync(Guid id, UpdateAuthorDto input);
Task DeleteAsync(Guid id);
}
}
-
IApplicationService
是由所有应用程序服务继承的常规接口,因此ABP框架可以标识该服务。 -
定义的标准方法以对
Author
实体执行CRUD操作。 -
PagedResultDto
是ABP框架中的预定义DTO类。它具有一个Items
集合和一个TotalCount
属性以返回分页结果。 -
首选从
CreateAsync
方法中返回AuthorDto
(对于新创建的作者),而此应用程序不使用它——只是为了显示不同的用法。
该接口使用下面定义的DTO(为您的项目创建它们)。
AuthorDtousing System;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Authors
{
public class AuthorDto : EntityDto
{
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
}
}
EntityDto
具有一个Id
属性和给定泛型的参数。您可以自己创建一个Id
属性,而不是从EntityDto
继承。
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Authors
{
public class GetAuthorListDto : PagedAndSortedResultRequestDto
{
public string Filter { get; set; }
}
}
-
Filter
用于搜索作者。可以是null
(或为空字符串)以获取所有作者。 -
PagedAndSortedResultRequestDto
具有标准分页和排序属性:int MaxResultCount
,int SkipCount
和string Sorting
。
ABP框架具有这些基本的DTO类,以简化和标准化您的DTO。有关所有信息,请参见[DTO文档](EntityDto simply has an Id property with the given generic argument.)。
CreateAuthorDtousing System;
using System.ComponentModel.DataAnnotations;
namespace Acme.BookStore.Authors
{
public class CreateAuthorDto
{
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
}
}
数据注释属性可用于验证DTO。有关详细信息,请参见验证文档。
UpdateAuthorDtousing System;
using System.ComponentModel.DataAnnotations;
namespace Acme.BookStore.Authors
{
public class UpdateAuthorDto
{
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
}
}
我们可以在创建和更新操作之间共享(重用)相同的DTO。尽管可以做到,但我们更愿意为这些操作创建不同的DTO,因为我们看到它们通常在那时会有所不同。因此,与紧密耦合的设计相比,这里的代码复制是合理的。
AuthorAppService现在是实现IAuthorAppService
接口的时候了。在Acme.BookStore.Application
项目的Authors
命名空间(文件夹)中创建一个命名为AuthorAppService
新的类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Acme.BookStore.Permissions;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Authors
{
[Authorize(BookStorePermissions.Authors.Default)]
public class AuthorAppService : BookStoreAppService, IAuthorAppService
{
private readonly IAuthorRepository _authorRepository;
private readonly AuthorManager _authorManager;
public AuthorAppService(
IAuthorRepository authorRepository,
AuthorManager authorManager)
{
_authorRepository = authorRepository;
_authorManager = authorManager;
}
//...SERVICE METHODS WILL COME HERE...
}
}
-
[Authorize(BookStorePermissions.Authors.Default)]
是一种检查权限(策略)以授权当前用户的声明方式。有关更多信息,请参见授权文档。BookStorePermissions
类将在下面更新,现在不用担心编译错误。 -
从
BookStoreAppService
派生,这是启动模板附带的一个简单基类。它是从标准ApplicationService
类派生的。 -
实现了上面定义的
IAuthorAppService
。 -
注入
IAuthorRepository
和AuthorManager
以用于服务方法。
现在,我们将逐一介绍服务方法。将解释的方法复制到AuthorAppService
类中。
public async Task GetAsync(Guid id)
{
var author = await _authorRepository.GetAsync(id);
return ObjectMapper.Map(author);
}
这个方法简单地通过它的Id
获取Author
实体,然后使用对象到对象映射转换为AuthorDto
。这需要配置AutoMapper
,这将在后面说明。
public async Task GetListAsync(GetAuthorListDto input)
{
if (input.Sorting.IsNullOrWhiteSpace())
{
input.Sorting = nameof(Author.Name);
}
var authors = await _authorRepository.GetListAsync(
input.SkipCount,
input.MaxResultCount,
input.Sorting,
input.Filter
);
var totalCount = await AsyncExecuter.CountAsync(
_authorRepository.WhereIf(
!input.Filter.IsNullOrWhiteSpace(),
author => author.Name.Contains(input.Filter)
)
);
return new PagedResultDto(
totalCount,
ObjectMapper.Map(authors)
);
}
-
默认排序是“按作者名称”,如果客户端未发送,则在方法的开头进行排序。
-
使用
IAuthorRepository.GetListAsync
从数据库中获取作者的分页,排序和筛选列表。我们已经在本教程的前一部分中实现了它。同样,实际上并不需要创建这样的方法,因为我们可以直接查询存储库,但想演示如何创建自定义存储库方法。 -
直接从
AuthorRepositor
y查询,同时获得作者的数量。我们更喜欢使用允许我们执行异步查询而无需依赖EF Core的AsyncExecuter
服务。但是,您可以依赖EF Core软件包并直接使用_authorRepository.WhereIf(...).ToListAsync()
方法。请参阅存储库文档以阅读替代方法和讨论。 -
最后,通过将
Authors
列表映射到AuthorDto
列表来返回分页结果。
[Authorize(BookStorePermissions.Authors.Create)]
public async Task CreateAsync(CreateAuthorDto input)
{
var author = await _authorManager.CreateAsync(
input.Name,
input.BirthDate,
input.ShortBio
);
await _authorRepository.InsertAsync(author);
return ObjectMapper.Map(author);
}
-
CreateAsync
需要BookStorePermissions.Authors.Create
权限(除了为AuthorAppService
类声明的BookStorePermissions.Authors.Default
以外)。 -
使用
AuthorManeger
(领域服务)创建新作者。 -
使用
IAuthorRepository.InsertAsync
将新作者插入数据库。 -
使用
ObjectMapper
以返回代表新创建的作者的AuthorDto
。
DDD提示:一些开发人员可能会发现在_authorManager.CreateAsync
中插入新的实体很有用。我们认为将其留给应用程序层是一个更好的设计,因为它更好地知道何时将其插入数据库(也许在插入之前它需要对实体进行额外的工作,如果我们在领域服务中执行插入操作,则需要进行其他更新。)。但是,这完全取决于您。
[Authorize(BookStorePermissions.Authors.Edit)]
public async Task UpdateAsync(Guid id, UpdateAuthorDto input)
{
var author = await _authorRepository.GetAsync(id);
if (author.Name != input.Name)
{
await _authorManager.ChangeNameAsync(author, input.Name);
}
author.BirthDate = input.BirthDate;
author.ShortBio = input.ShortBio;
await _authorRepository.UpdateAsync(author);
}
-
UpdateAsync
需要额外的BookStorePermissions.Authors.Edit
权限。 -
使用
IAuthorRepository.GetAsync
从数据库获取作者实体。如果没有具有给定id
的作者,则GetAsync
抛出EntityNotFoundException
异常,从而在Web应用程序中导致404
HTTP状态代码。始终使实体处于更新操作是一个好习惯。 -
如果客户端请求更改作者姓名,则使用
AuthorManager.ChangeNameAsync
(域服务方法)更改作者姓名。 -
直接更新
BirthDate
和ShortBio
,由于没有任何业务规则可更改这些属性,因此它们接受任何值。 -
最后,调用了更新数据库上实体的
IAuthorRepository.UpdateAsync
方法。
EF Core技巧:Entity Framework Core具有变更跟踪系统,并在工作单元结束时自动将所有更改保存到实体(您可以简单地认为ABP Framework 在方法结束时会自动调用SaveChanges
)。因此,即使您未在方法末尾调用_authorRepository.UpdateAsync(...)
,它也将按预期工作。如果您不考虑以后再更改EF Core,则只需删除此行。
[Authorize(BookStorePermissions.Authors.Delete)]
public async Task DeleteAsync(Guid id)
{
await _authorRepository.DeleteAsync(id);
}
-
DeleteAsync
需要额外的BookStorePermissions.Authors.Delete
权限。 -
它仅使用存储库的
DeleteAsync
方法。
您不能编译代码,因为它需要在BookStorePermissions
类中声明一些常量。
打开Acme.BookStore.Application.Contracts
项目内部的BookStorePermissions
类(在Permissions
文件夹中),然后更改内容,如下所示:
namespace Acme.BookStore.Permissions
{
public static class BookStorePermissions
{
public const string GroupName = "BookStore";
public static class Books
{
public const string Default = GroupName + ".Books";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
// *** ADDED a NEW NESTED CLASS ***
public static class Authors
{
public const string Default = GroupName + ".Authors";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
}
}
然后在同一项目中打开BookStorePermissionDefinitionProvider
,并在Define
方法末尾添加以下行:
var authorsPermission = bookStoreGroup.AddPermission(
BookStorePermissions.Authors.Default, L("Permission:Authors"));
authorsPermission.AddChild(
BookStorePermissions.Authors.Create, L("Permission:Authors.Create"));
authorsPermission.AddChild(
BookStorePermissions.Authors.Edit, L("Permission:Authors.Edit"));
authorsPermission.AddChild(
BookStorePermissions.Authors.Delete, L("Permission:Authors.Delete"));
最后,将以下条目添加到Acme.BookStore.Domain.Shared
项目内部的Localization/BookStore/en.json
中,以本地化权限名称:
"Permission:Authors": "Author Management",
"Permission:Authors.Create": "Creating new authors",
"Permission:Authors.Edit": "Editing the authors",
"Permission:Authors.Delete": "Deleting the authors"
对象到对象映射
AuthorAppService
正在使用ObjectMapper
将Author
对象转换为AuthorDto
对象。因此,我们需要在AutoMapper
配置中定义此映射。
打开Acme.BookStore.Application
项目内部的BookStoreApplicationAutoMapperProfile
类,并将以下行添加到构造函数中:
CreateMap();
数据播种器
就像之前的书籍一样,最好在数据库中有一些初始作者实体。第一次运行该应用程序时,这会很好,但对于自动化测试而言,它也非常有用。
在Acme.BookStore.Domain
项目中打开BookStoreDataSeederContributor
,然后使用以下代码更改文件内容:
using System;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
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 _bookRepository;
private readonly IAuthorRepository _authorRepository;
private readonly AuthorManager _authorManager;
public BookStoreDataSeederContributor(
IRepository bookRepository,
IAuthorRepository authorRepository,
AuthorManager authorManager)
{
_bookRepository = bookRepository;
_authorRepository = authorRepository;
_authorManager = authorManager;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() author.Name == "Douglas Adams");
}
[Fact]
public async Task Should_Get_Filtered_Authors()
{
var result = await _authorAppService.GetListAsync(
new GetAuthorListDto {Filter = "George"});
result.TotalCount.ShouldBeGreaterThanOrEqualTo(1);
result.Items.ShouldContain(author => author.Name == "George Orwell");
result.Items.ShouldNotContain(author => author.Name == "Douglas Adams");
}
[Fact]
public async Task Should_Create_A_New_Author()
{
var authorDto = await _authorAppService.CreateAsync(
new CreateAuthorDto
{
Name = "Edward Bellamy",
BirthDate = new DateTime(1850, 05, 22),
ShortBio = "Edward Bellamy was an American author..."
}
);
authorDto.Id.ShouldNotBe(Guid.Empty);
authorDto.Name.ShouldBe("Edward Bellamy");
}
[Fact]
public async Task Should_Not_Allow_To_Create_Duplicate_Author()
{
await Assert.ThrowsAsync(async () =>
{
await _authorAppService.CreateAsync(
new CreateAuthorDto
{
Name = "Douglas Adams",
BirthDate = DateTime.Now,
ShortBio = "..."
}
);
});
}
//TODO: Test other methods...
}
}
为应用程序服务方法创建了一些测试,这些测试应该很容易理解。
下一部分请参阅本教程的下一部分。