• Web应用开发教程 Part 8: 作家:应用层


    Web应用开发教程 - Part 8: 作家:应用层

    //[doc-params]
    {
        "UI": ["MVC","Blazor","BlazorServer","NG"],
        "DB": ["EF","Mongo"]
    }
    

    关于本教程

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

    • {{DB_Value}} 作为ORM的提供者。
    • {{UI_Value}} 作为UI框架。

    本教程由以下几个部分组成:

    下载源代码

    本教程根据你对UIDatabase的偏好有多个版本。我们准备了几个组合的源代码供大家下载:

    如果你在Windows上遇到 "文件名太长 "或 "解压错误",它可能与Windows的最大文件路径限制有关。Windows有一个最大的文件路径限制,即250个字符。要解决这个问题,在Windows 10中启用长路径选项

    如果你遇到与Git有关的长路径错误,可以尝试用以下命令在Windows中启用长路径。见 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
    git config --system core.longpaths true

    简介

    This part explains to create an application layer for the Author entity created before.

    这一部分讲解如何为之前创建的Author实体创建应用层。

    IAuthorAppService

    We will first create the application service interface and the related DTOs. Create a new interface, named IAuthorAppService, in the Authors namespace (folder) of the Acme.BookStore.Application.Contracts project:

    我们将首先创建应用服务接口和相关的DTOs。在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<AuthorDto> GetAsync(Guid id);
    
            Task<PagedResultDto<AuthorDto>> GetListAsync(GetAuthorListDto input);
    
            Task<AuthorDto> 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(为你的项目创建它们):

    AuthorDto

    using System;
    using Volo.Abp.Application.Dtos;
    
    namespace Acme.BookStore.Authors
    {
        public class AuthorDto : EntityDto<Guid>
        {
            public string Name { get; set; }
    
            public DateTime BirthDate { get; set; }
    
            public string ShortBio { get; set; }
        }
    }
    
    • EntityDto<T>只是有一个Id'属性,有给定的通用参数。你可以自己创建一个Id属性,而不是继承EntityDto`。

    GetAuthorListDto

    using Volo.Abp.Application.Dtos;
    
    namespace Acme.BookStore.Authors
    {
        public class GetAuthorListDto : PagedAndSortedResultRequestDto
        {
            public string Filter { get; set; }
        }
    }
    
    • Filter用于搜索作者。它可以是null(或空字符串),以获得所有作者。
    • PagedAndSortedResultRequestDto有标准的分页和排序属性。int MaxResultCount, int SkipCountstring Sorting

    ABP框架有这样的基础DTO类来简化和规范你的DTO。参考DTO文档了解全部内容。

    CreateAuthorDto

    using 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。详见验证文档

    UpdateAuthorDto

    using 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;
    using Volo.Abp.Domain.Repositories;
    
    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
    • 注入IAuthorRepositoryAuthorManager以便在服务方法中使用。

    现在,我们将逐一介绍服务方法。将介绍的方法复制到AuthorAppService类中。

    GetAsync

    public async Task<AuthorDto> GetAsync(Guid id)
    {
        var author = await _authorRepository.GetAsync(id);
        return ObjectMapper.Map<Author, AuthorDto>(author);
    }
    

    This method simply gets the Author entity by its Id, converts to the AuthorDto using the object to object mapper. This requires to configure the AutoMapper, which will be explained later.

    这个方法只是通过Id获得Author实体,使用对象间映射转换为AuthorDto。这需要配置AutoMapper,这将在后面解释。

    GetListAsync

    public async Task<PagedResultDto<AuthorDto>> 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 = input.Filter == null
            ? await _authorRepository.CountAsync()
            : await _authorRepository.CountAsync(
                author => author.Name.Contains(input.Filter));
    
        return new PagedResultDto<AuthorDto>(
            totalCount,
            ObjectMapper.Map<List<Author>, List<AuthorDto>>(authors)
        );
    }
    
    • 默认的是按 "作家姓名"排序,这是在方法的开始部分完成的,以防止它不是由客户端发送的。
    • 使用IAuthorRepository.GetListAsync从数据库中获得一个分页、排序和过滤的作家列表。我们已经在本教程的前一部分中实现了这个方法。同样,实际上不需要创建这样的方法,因为我们可以直接查询资源库,但我们想演示如何创建自定义资源库方法。
    • 在获得作家人数时,直接从AuthorRepository查询。如果发送了一个过滤器,那么我们在获得计数的同时用它来过滤实体。
    • 最后,通过将Author列表映射为AuthorDto列表返回一个分页结果。

    CreateAsync

    [Authorize(BookStorePermissions.Authors.Create)]
    public async Task<AuthorDto> CreateAsync(CreateAuthorDto input)
    {
        var author = await _authorManager.CreateAsync(
            input.Name,
            input.BirthDate,
            input.ShortBio
        );
    
        await _authorRepository.InsertAsync(author);
    
        return ObjectMapper.Map<Author, AuthorDto>(author);
    }
    
    • CreateAsync需要BookStorePermissions.Authors.Create权限(除了为AuthorAppService类声明的BookStorePermissions.Authors.Default以外)。
    • 使用AuthorManager(领域服务)创建一个新的作者。
    • 使用IAuthorRepository.InsertAsync向数据库插入新作家。
    • 使用ObjectMapper来返回代表新创建的作家的AuthorDto

    DDD提示:一些开发者可能会发现在_authorManager.CreateAsync中插入新实体是很有用的。我们认为将其留给应用层是更好的设计,因为它更清楚何时将其插入数据库(也许它需要在插入前对实体进行额外的处理,如果我们在领域服务中执行插入,则需要额外的更新)。然而,这完全取决于你。

    UpdateAsync

    [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应用程序中会产生404HTTP状态代码。在更新实体操作中总是先获取这个实体是一个好的做法。
    • 如果在客户端请求改变作家的名字时,使用AuthorManager.ChangeNameAsync(领域服务方法)进行修改。
    • 直接更新BirthDateShortBio,因为改变这些属性时没有任何业务规则,它们接受任何值。
    • 最后,调用IAuthorRepository.UpdateAsync方法来更新数据库中的实体。

    {{if DB == "EF"}}

    EF Core提示:Entity Framework Core有一个变化跟踪系统,并在工作单元结束时自动保存实体的任何变化(你可以简单地认为ABP框架在方法结束时自动调用SaveChanges)。所以,即使你不在方法结束时调用_authorRepository.UpdateAsync(...),它也会按预期工作。如果你不考虑以后替换EF Core,你可以直接删除这一行。

    {{end}}

    DeleteAsync

    [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正在使用ObjectMapperAuthor对象转换成AuthorDto对象。因此,我们需要在AutoMapper配置中定义这个映射。

    打开Acme.BookStore.Application项目中的BookStoreApplicationAutoMapperProfile类,并在构造函数中添加下面一行代码:

    CreateMap<Author, AuthorDto>();
    

    数据初始化

    正如之前为书籍所做的那样,在数据库中拥有一些初始的作者实体将是很好的。这在第一次运行应用程序时是很好的,而且对自动测试也很有用。

    打开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<Book, Guid> _bookRepository;
            private readonly IAuthorRepository _authorRepository;
            private readonly AuthorManager _authorManager;
    
            public BookStoreDataSeederContributor(
                IRepository<Book, Guid> bookRepository,
                IAuthorRepository authorRepository,
                AuthorManager authorManager)
            {
                _bookRepository = bookRepository;
                _authorRepository = authorRepository;
                _authorManager = authorManager;
            }
    
            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
                    );
                }
    
                // ADDED SEED DATA FOR AUTHORS
    
                if (await _authorRepository.GetCountAsync() <= 0)
                {
                    await _authorRepository.InsertAsync(
                        await _authorManager.CreateAsync(
                            "George Orwell",
                            new DateTime(1903, 06, 25),
                            "Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)."
                        )
                    );
    
                    await _authorRepository.InsertAsync(
                        await _authorManager.CreateAsync(
                            "Douglas Adams",
                            new DateTime(1952, 03, 11),
                            "Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed 'radical atheist'."
                        )
                    );
                }
            }
        }
    }
    

    {{if DB=="EF"}}

    现在你可以运行.DbMigrator控制台应用程序来迁移数据库结构植入初始化数据。

    {{else if DB=="Mongo"}}

    You can now run the .DbMigrator console application to seed the initial data.

    现在你可以运行.DbMigrator控制台应用程序来初始化数据

    {{end}}

    作家应用服务测试

    最后,我们可以为IAuthorAppService写一些测试。在Acme.BookStore.Application.Tests项目的Authors命名空间(文件夹)中添加一个新的类,命名为 AuthorAppService_Tests

    using System;
    using System.Threading.Tasks;
    using Shouldly;
    using Xunit;
    
    namespace Acme.BookStore.Authors
    { {{if DB=="Mongo"}}
        [Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
        public class AuthorAppService_Tests : BookStoreApplicationTestBase
        {
            private readonly IAuthorAppService _authorAppService;
    
            public AuthorAppService_Tests()
            {
                _authorAppService = GetRequiredService<IAuthorAppService>();
            }
    
            [Fact]
            public async Task Should_Get_All_Authors_Without_Any_Filter()
            {
                var result = await _authorAppService.GetListAsync(new GetAuthorListDto());
    
                result.TotalCount.ShouldBeGreaterThanOrEqualTo(2);
                result.Items.ShouldContain(author => author.Name == "George Orwell");
                result.Items.ShouldContain(author => 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<AuthorAlreadyExistsException>(async () =>
                {
                    await _authorAppService.CreateAsync(
                        new CreateAuthorDto
                        {
                            Name = "Douglas Adams",
                            BirthDate = DateTime.Now,
                            ShortBio = "..."
                        }
                    );
                });
            }
    
            //TODO: Test other methods...
        }
    }
    

    为应用服务方法创建了一些简单容易理解的测试。

    下一篇

    参见本教程的下一篇

    本文来自博客园,作者:草叶睡蜢,转载请注明原文链接:https://www.cnblogs.com/tjubuntu/p/15722257.html

  • 相关阅读:
    CPU问题导致的大量进程崩溃问题
    coredump的裁剪方法
    GDB中遍历art::Thread打印local reference的脚本
    jemalloc管理块(arena、bin)
    jemalloc存储块(region、run、chunk)
    GDB中打印pthread_internal_t的方法
    FUSE引起的SIGBUS问题分析报告
    GDB中打印ART基础类
    UC浏览器闪退问题分析报告
    『深度实战』天池小目标检测大赛·宫颈癌风险智能诊断推荐
  • 原文地址:https://www.cnblogs.com/tjubuntu/p/15722257.html
Copyright © 2020-2023  润新知