ABP-Book Store Application中文讲解 - Part 10: Book to Author Relation

ABP-Book Store Application中文讲解 - Part 10: Book to Author Relation

 1. 汇总

ABP-Book Store Application中文讲解-汇总-CSDN博客

2. 前一章 

ABP-Book Store Application中文讲解 - Part 9: Authors: User Interface-CSDN博客

项目之间的引用关系。

目录

1. 添加Author到Book的映射

2. 在OnModelCreating中设置外键约束

3. 新建DB Migration

3.1 利用dotnet ef migrations

3.2 利用VS的Package Manager Console (PMC)

4. 更改Seeder

5. DB Update

6. 更改BookAppService 

6.1 更改Dtos

6.2 IBookAppService新增方法

6.3 BookAppService新增和更改方法

6.3 1. 构造函数中引入IAuthorRepository authorRepository

6.3 2. 更改GetAsync

6.3 3. 更改GetListAsync

7. Unit Tests-BookAppService_Tests.cs

8. UI端的更改

8.1 启动API,生成proxy

8.2 Book List更新

8.3 新增、编辑页面更新

9. 补充下本地化资源

10. 感谢


1. 添加Author到Book的映射

在Acme.BookStore.Domain中打开Books/Book.cs,添加如下代码:

public Guid AuthorId { get; set; }

ABP遵守DDD(Domain Driven Design)的设计模式,因此我们不用在Book.cs中添加 

public Author Author { get; set; }

2. 在OnModelCreating中设置外键约束

因为我们没有添加引用对象Author , 我们需要在OnModelCreating里面指定AuthorId作为外键,代码如下:

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);

    // 添加外键
    b.HasOne<Author>().WithMany().HasForeignKey(x => x.AuthorId).IsRequired();
});

3. 新建DB Migration

按照之前介绍的,此处有两种方式,可以根据自己的需要选择任意一种即可。

1. 利用dotnet ef migrations。 需要全局安装ef

dotnet tool install --global dotnet-ef 

2. 利用VS的Package Manager console

3.1 利用dotnet ef migrations

右击Acme.BookStore.EntityFrameworkCore,选择Open in Terminal,然后输入以下命令后敲回车:

dotnet ef migrations add Added_AuthorId_To_Book

3.2 利用VS的Package Manager Console (PMC)

Tools -> NuGet Package Manager --> Package Manager Console打开PMC, 输入以下命令后敲回车:

Add-Migration Added_AuthorId_To_Book

Tips: 输入add-migr直接敲Tab会自动补全Add-Migration 命令

4. 更改Seeder

展开Acme.BookStore.Domain,找到Data目录下的BookStoreDataSeederContributor.cs,打开后做如下更改:

public async Task SeedAsync(DataSeedContext context)
{
    if (await _bookRepository.GetCountAsync() > 0)
    {
        return;
    }

    // 添加Author初始化数据
    var author = await _authorManager.CreateAsync("刘慈欣", new DateTime(1963, 6, 1), "刘慈欣被誉为中国科幻的领军人物,他的作品“三体三部曲”是中国科幻文学的里程碑之作,将中国科幻推上了世界的高度。");
    await _authorRepository.InsertAsync(author);

    var authorLxs = await _authorManager.CreateAsync("梁晓声", new DateTime(1949, 9, 22), "梁晓声,原名梁绍生,中国当代著名作家,中国作家协会会员,1949年9月22日出生于黑龙江省哈尔滨市,毕业于复旦大学 [3] [50],祖籍山东威海市泊于镇温泉寨。他曾创作出版过大量有影响的小说、散文、随笔及影视作品,为中国现当代以知青文学成名的代表作家之一。现居北京,任教于北京语言大学人文学院汉语言文学专业。");
    await _authorRepository.InsertAsync(authorLxs);

    // 添加Book初始化数据
    await _bookRepository.InsertAsync(
           new Book()
           {
               Name = "三体",
               Type = BookType.Fantastic,
               PublishDate = new DateTime(2006, 5, 20),
               Price = 100,
               AuthorId = author.Id
           },
           autoSave: true
        );
    await _bookRepository.InsertAsync(
       new Book()
       {
           Name = "三体2:黑暗森林",
           Type = BookType.Fantastic,
           PublishDate = new DateTime(2008, 5, 21),
           Price = 100,
           AuthorId = author.Id
       },
       autoSave: true
    );
    await _bookRepository.InsertAsync(
       new Book()
       {
           Name = "三体3:死神永生",
           Type = BookType.Fantastic,
           PublishDate = new DateTime(2010, 11, 11),
           Price = 100,
           AuthorId = author.Id
       },
       autoSave: true
    );
    await _bookRepository.InsertAsync(
      new Book()
      {
          Name = "人世间",
          Type = BookType.Dystopia,
          PublishDate = new DateTime(2017, 12, 11),
          Price = 100,
          AuthorId = authorLxs.Id
      },
      autoSave: true
   );
}

5. DB Update

删除原来的数据或者直接删除原来的数据库,然后重新运行Acme.BookStore.DbMigrator去重新更新数据或者重新创建数据库。

6. 更改BookAppService 

6.1 更改Dtos

Acme.BookStore.Application.Contracts项目的Books目录中:

BookDto,新增如下两个fields

      public Guid AuthorId { get; set; }
      public string AuthorName { get; set; } = string.Empty;

CreateUpdateBookDto, 新增一个字段

public Guid AuthorId { get; set; }

在Books目录中新增一个新类AuthorLookupDto.cs

using System;
using Volo.Abp.Application.Dtos;

namespace Acme.BookStore.Books.Dtos
{
    public class AuthorLookupDto : EntityDto<Guid>
    {
        public string Name { get; set; } = string.Empty;
    }
}

6.2 IBookAppService新增方法

在Acme.BookStore.Application.Contracts项目的Books目录中IBookAppService.cs中新增GetAuthorLookupAsync

        /// <summary>
        /// 获取Authors的Lookup列表,在新增或者编辑Book时使用
        /// </summary>
        /// <returns></returns>
        Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync();

6.3 BookAppService新增和更改方法

首先需要打开Acme.BookStore.Application中的BookStoreApplicationAutoMapperProfile.cs添加Author到AuthorLookupDto的映射。

 CreateMap<Author, AuthorLookupDto>();

然后开始打开Acme.BookStore.Application中的Books目录下的BookAppService.cs,进行如下方法的更改:

因为需要override现有的方法和新增新的方法,因此原来的权限验证会失效,需要主动添加Attribute ([Authorize(BookStorePermissions.Books.Default)])在BookAppService中。

6.3 1. 构造函数中引入IAuthorRepository authorRepository

        private readonly IAuthorRepository _authorRepository;

        public BookAppService(IRepository<Book, Guid> repository, IAuthorRepository authorRepository) : base(repository)
        {
            GetPolicyName = BookStorePermissions.Books.Default;// Default
            GetListPolicyName = BookStorePermissions.Books.Default;
            CreatePolicyName = BookStorePermissions.Books.Create;
            UpdatePolicyName = BookStorePermissions.Books.Edit;
            DeletePolicyName = BookStorePermissions.Books.Delete;
            _authorRepository = authorRepository;
        }

6.3 2. 更改GetAsync

此处有两种方式:因为只有一条记录,并且都是根据主键进行查询,所以两种方式都行。

1. 分开查询,这个方式会访问数据库两次,但是ABP默认启动的UnitOfWork,即一个function或者API用的同一个transaction

        public override async Task<BookDto> GetAsync(Guid id)
        {
            // Get the IQueryable<Book> from the repository
            var book = await base.Repository.GetAsync(id);
            var author = await _authorRepository.GetAsync(book.AuthorId);

            var bookDto = ObjectMapper.Map<Book, BookDto>(book);
            bookDto.AuthorName = author.Name;
            return bookDto;
        }

2. 利用Linq的join查询,只会访问数据库一次,即 query.FirstOrDefault()才会访问数据库

        public override async Task<BookDto> GetAsync(Guid id)
        {
            // Get the IQueryable<Book> from the repository
            var books = await Repository.GetQueryableAsync();
            var authors = await _authorRepository.GetQueryableAsync();

            // Ensure LINQ methods like Join are available by converting to Enumerable
            var query = from book in books.AsEnumerable()
                        join author in authors.AsEnumerable() on book.AuthorId equals author.Id
                        where book.Id == id
                        select new { book, author };

            // Execute the query and get the book with author
            var queryResult = query.FirstOrDefault();
            if (queryResult == null)
            {
                throw new EntityNotFoundException(typeof(Book), id);
            }

            var bookDto = ObjectMapper.Map<Book, BookDto>(queryResult.book);
            bookDto.AuthorName = queryResult.author.Name;
            return bookDto;
        }

6.3 3. 更改GetListAsync

不确定为啥ABP官网的代码无法在我本地编译,因此做了如下更改。

        public override async Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input)
        {
            //Get the IQueryable<Book> from the repository
            var queryable = await Repository.GetQueryableAsync();

            //Prepare a query to join books and authors
            var query = from book in queryable
                        join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
                        select new { book, author };

            //Paging
            var sorting = NormalizeSorting(input.Sorting);
            query = query
                .OrderBy(x => sorting)
                .Skip(input.SkipCount)
                .Take(input.MaxResultCount);

            //Execute the query and get a list
            var queryResult = await AsyncExecuter.ToListAsync(query);

            //Convert the query result to a list of BookDto objects
            var bookDtos = queryResult.Select(x =>
            {
                var bookDto = ObjectMapper.Map<Book, BookDto>(x.book);
                bookDto.AuthorName = x.author.Name;
                return bookDto;
            }).ToList();

            //Get the total count with another query
            var totalCount = await Repository.GetCountAsync();

            return new PagedResultDto<BookDto>(
                totalCount,
                bookDtos
            );
        }

        private string NormalizeSorting(string sorting)
        {
            if (sorting.IsNullOrEmpty())
            {
                return $"book.{nameof(Book.Name)}";
            }

            if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase))
            {
                return sorting.Replace(
                    "authorName",
                    "author.Name",
                    StringComparison.OrdinalIgnoreCase
                );
            }

            return $"book.{sorting}";
        }

6.3 4. 新增方法GetAuthorLookupAsync

  public async Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync()
  {
      var authors = await _authorRepository.GetListAsync();

      return new ListResultDto<AuthorLookupDto>(
          ObjectMapper.Map<List<Author>, List<AuthorLookupDto>>(authors)
      );
  }

完整代码:

我更改后的版本:

using Acme.BookStore.Authors;
using Acme.BookStore.Books.Dtos;
using Acme.BookStore.Permissions;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;

namespace Acme.BookStore.Books
{
    [Authorize(BookStorePermissions.Books.Default)]
    public class BookAppService :
     CrudAppService<
         Book, //The Book entity
         BookDto, //Used to show books in UI
         Guid, //Primary key of the book entity
         PagedAndSortedResultRequestDto, //Used for paging/sorting
         CreateUpdateBookDto>, //Used to create/update a book
     IBookAppService //implement the IBookAppService
    {
        private readonly IAuthorRepository _authorRepository;

        public BookAppService(IRepository<Book, Guid> repository, IAuthorRepository authorRepository) : base(repository)
        {
            GetPolicyName = BookStorePermissions.Books.Default;// Default
            GetListPolicyName = BookStorePermissions.Books.Default;
            CreatePolicyName = BookStorePermissions.Books.Create;
            UpdatePolicyName = BookStorePermissions.Books.Edit;
            DeletePolicyName = BookStorePermissions.Books.Delete;
            _authorRepository = authorRepository;
        }

        public override async Task<BookDto> GetAsync(Guid id)
        {
            // Get the IQueryable<Book> from the repository
            var books = await Repository.GetQueryableAsync();
            var authors = await _authorRepository.GetQueryableAsync();

            // Ensure LINQ methods like Join are available by converting to Enumerable
            var query = from book in books.AsEnumerable()
                        join author in authors.AsEnumerable() on book.AuthorId equals author.Id
                        where book.Id == id
                        select new { book, author };

            // Execute the query and get the book with author
            var queryResult = query.FirstOrDefault();
            if (queryResult == null)
            {
                throw new EntityNotFoundException(typeof(Book), id);
            }

            var bookDto = ObjectMapper.Map<Book, BookDto>(queryResult.book);
            bookDto.AuthorName = queryResult.author.Name;
            return bookDto;
        }

        //public override async Task<BookDto> GetAsync(Guid id)
        //{
        //    // Get the IQueryable<Book> from the repository
        //    var book = await base.Repository.GetAsync(id);
        //    var author = await _authorRepository.GetAsync(book.AuthorId);

        //    var bookDto = ObjectMapper.Map<Book, BookDto>(book);
        //    bookDto.AuthorName = author.Name;
        //    return bookDto;
        //}

        public async Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync()
        {
            var authors = await _authorRepository.GetListAsync();

            return new ListResultDto<AuthorLookupDto>(
                ObjectMapper.Map<List<Author>, List<AuthorLookupDto>>(authors)
            );
        }

        public override async Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input)
        {
            //Get the IQueryable<Book> from the repository
            var queryable = await Repository.GetQueryableAsync();

            //Prepare a query to join books and authors
            var query = from book in queryable
                        join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
                        select new { book, author };

            //Paging
            var sorting = NormalizeSorting(input.Sorting);
            query = query
                .OrderBy(x => sorting)
                .Skip(input.SkipCount)
                .Take(input.MaxResultCount);

            //Execute the query and get a list
            var queryResult = await AsyncExecuter.ToListAsync(query);

            //Convert the query result to a list of BookDto objects
            var bookDtos = queryResult.Select(x =>
            {
                var bookDto = ObjectMapper.Map<Book, BookDto>(x.book);
                bookDto.AuthorName = x.author.Name;
                return bookDto;
            }).ToList();

            //Get the total count with another query
            var totalCount = await Repository.GetCountAsync();

            return new PagedResultDto<BookDto>(
                totalCount,
                bookDtos
            );
        }

        private string NormalizeSorting(string sorting)
        {
            if (sorting.IsNullOrEmpty())
            {
                return $"book.{nameof(Book.Name)}";
            }

            if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase))
            {
                return sorting.Replace(
                    "authorName",
                    "author.Name",
                    StringComparison.OrdinalIgnoreCase
                );
            }

            return $"book.{sorting}";
        }
    }
}

ABP官网的版本:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Acme.BookStore.Permissions;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;

namespace Acme.BookStore.Books;

[Authorize(BookStorePermissions.Books.Default)]
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
{
    private readonly IAuthorRepository _authorRepository;

    public BookAppService(
        IRepository<Book, Guid> repository,
        IAuthorRepository authorRepository)
        : base(repository)
    {
        _authorRepository = authorRepository;
        GetPolicyName = BookStorePermissions.Books.Default;
        GetListPolicyName = BookStorePermissions.Books.Default;
        CreatePolicyName = BookStorePermissions.Books.Create;
        UpdatePolicyName = BookStorePermissions.Books.Edit;
        DeletePolicyName = BookStorePermissions.Books.Delete;
    }

    public override async Task<BookDto> GetAsync(Guid id)
    {
        //Get the IQueryable<Book> from the repository
        var queryable = await Repository.GetQueryableAsync();

        //Prepare a query to join books and authors
        var query = from book in queryable
            join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
            where book.Id == id
            select new { book, author };

        //Execute the query and get the book with author
        var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query);
        if (queryResult == null)
        {
            throw new EntityNotFoundException(typeof(Book), id);
        }

        var bookDto = ObjectMapper.Map<Book, BookDto>(queryResult.book);
        bookDto.AuthorName = queryResult.author.Name;
        return bookDto;
    }

    public override async Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input)
    {
        //Get the IQueryable<Book> from the repository
        var queryable = await Repository.GetQueryableAsync();

        //Prepare a query to join books and authors
        var query = from book in queryable
            join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
            select new {book, author};

        //Paging
        query = query
            .OrderBy(NormalizeSorting(input.Sorting))
            .Skip(input.SkipCount)
            .Take(input.MaxResultCount);

        //Execute the query and get a list
        var queryResult = await AsyncExecuter.ToListAsync(query);

        //Convert the query result to a list of BookDto objects
        var bookDtos = queryResult.Select(x =>
        {
            var bookDto = ObjectMapper.Map<Book, BookDto>(x.book);
            bookDto.AuthorName = x.author.Name;
            return bookDto;
        }).ToList();

        //Get the total count with another query
        var totalCount = await Repository.GetCountAsync();

        return new PagedResultDto<BookDto>(
            totalCount,
            bookDtos
        );
    }

    public async Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync()
    {
        var authors = await _authorRepository.GetListAsync();

        return new ListResultDto<AuthorLookupDto>(
            ObjectMapper.Map<List<Author>, List<AuthorLookupDto>>(authors)
        );
    }

    private static string NormalizeSorting(string sorting)
    {
        if (sorting.IsNullOrEmpty())
        {
            return $"book.{nameof(Book.Name)}";
        }

        if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase))
        {
            return sorting.Replace(
                "authorName",
                "author.Name",
                StringComparison.OrdinalIgnoreCase
            );
        }

        return $"book.{sorting}";
    }
}

7. Unit Tests-BookAppService_Tests.cs

在Acme.BookStore.Application.Tests项目的Books目录中找到BookAppService_Tests.cs,做如下更改:

using Acme.BookStore.Authors.Dtos;
using Acme.BookStore.Books.Dtos;
using Shouldly;
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Modularity;
using Volo.Abp.Validation;
using Xunit;

namespace Acme.BookStore.Books
{
    public abstract class BookAppService_Tests<TStartupModule> : BookStoreApplicationTestBase<TStartupModule>
        where TStartupModule : IAbpModule
    {
        private readonly IBookAppService _bookAppService;


        protected BookAppService_Tests()
        {
            _bookAppService = GetRequiredService<IBookAppService>();
        }

        [Fact]
        public async Task Should_Get_List_Of_Books()
        {
            //Act
            var result = await _bookAppService.GetListAsync(
                new PagedAndSortedResultRequestDto()
            );

            //Assert
            result.TotalCount.ShouldBeGreaterThan(0);
            result.Items.ShouldContain(b => b.Name == "三体" && b.AuthorName == "刘慈欣");
        }

        [Fact]
        public async Task Should_Create_A_Valid_Book()
        {
            var authors = await _bookAppService.GetAuthorLookupAsync();
            var firstAuthor = authors.Items.First();
            var newBookName = "Evan test new book";// 按照需要更改成你自己的书名
            //Act
            var result = await _bookAppService.CreateAsync(
                new CreateUpdateBookDto
                {
                    Name = newBookName,
                    Price = 100,
                    PublishDate = DateTime.Now,
                    Type = BookType.ScienceFiction,
                    AuthorId = firstAuthor.Id // 使用第一个作者的ID
                }
            );

            //Assert
            result.Id.ShouldNotBe(Guid.Empty);
            result.Name.ShouldBe(newBookName);
        }

        [Fact]
        public async Task Should_Not_Create_A_Book_Without_Name()
        {
            var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
            {
                await _bookAppService.CreateAsync(
                    new CreateUpdateBookDto
                    {
                        Name = "",
                        Price = 10,
                        PublishDate = DateTime.Now,
                        Type = BookType.ScienceFiction
                    }
                );
            });

            exception.ValidationErrors
                .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
        }
    }
}

8. UI端的更改

8.1 启动API,生成proxy

在angular根目录,通过ctrl+`打开Terminal console,然后输入以下命令,敲回车去更新proxy。

abp generate-proxy -t ng

8.2 Book List更新

如果你是单独生成的add-edit-book,那么此处只需要在book.component.html页面增加一行代码即可。

  <!-- add the Author column-->
            <ngx-datatable-column name="{{'::Author' | abpLocalization}}" prop="authorName"></ngx-datatable-column>

8.3 新增、编辑页面更新

add-edit-book.component.html增加以下代码

        <div class="formGroup mt-2">
            <label for="book-author">{{'::Author' | abpLocalization}}</label><span> * </span>
            <select class="form-control" id="book-author" formControlName="authorId">
                <option [ngValue]="null">{{'::SelectAuthor' | abpLocalization}}</option>
                <option [ngValue]="author.id" *ngFor="let author of authors"> {{ author.name }}</option>
            </select>
        </div>

add-edit-book.component.ts中需要更改以下几处代码:

导入AuthorLookupDto

注意此处如果重新生成proxy导致BookDto报错的话,需要将路径更改为@proxy/books/dtos即可。

import { BookDto, AuthorLookupDto } from '@proxy/books/dtos'; 

定义authors变量

 authors: AuthorLookupDto[] = [];

ngOnInit增加加载Authors的方法

  ngOnInit(): void {
    this.bookService.getAuthorLookup().subscribe((result) => {
      this.authors = result.items;
      this.buildForm();
    });
  }

buildForm增加authorId

  buildForm() {
    this.form = this.fb.group({
      name: [this.book.name, Validators.required],
      type: [this.book.type, Validators.required],
      publishDate: [this.book.publishDate ? new Date(this.book.publishDate) : null, Validators.required],
      price: [this.book.price, Validators.required],
      authorId: [this.book.authorId, Validators.required],
    });
  }

9. 补充下本地化资源

en.json

{
  "Culture": "en",
  "Texts": {
    "Menu:Home": "Home",
    "Welcome": "Welcome",
    "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP. For more information, visit abp.io.",
    // 以下内容是新增内容
    "Menu:BookStore": "Book Store",
    "Menu:Books": "Books",
    "Actions": "Actions",
    "Close": "Close",
    "Delete": "Delete",
    "Edit": "Edit",
    "Save": "Save",
    "Cancel": "Cancel",
    "Update": "Update",
    "PublishDate": "Publish date",
    "NewBook": "New book",
    "EditBook": "Edit book",
    "Name": "Name",
    "Type": "Type",
    "Price": "Price",
    "CreationTime": "Creation time",
    "Author": "Author",
    "SelectAuthor": "Select an author",
    "SelectType": "Select a book type",
    "AreYouSure": "Are you sure?",
    "AreYouSureToDelete": "Are you sure you want to delete this item?",
    "Enum:BookType.0": "Undefined",
    "Enum:BookType.1": "Adventure",
    "Enum:BookType.2": "Biography",
    "Enum:BookType.3": "Dystopia",
    "Enum:BookType.4": "Fantastic",
    "Enum:BookType.5": "Horror",
    "Enum:BookType.6": "Science",
    "Enum:BookType.7": "Science fiction",
    "Enum:BookType.8": "Poetry",
    "Permission:BookStore": "Book Store",
    "Permission:Books": "Book Management",
    "Permission:Books.Create": "Creating new books",
    "Permission:Books.Edit": "Editing the books",
    "Permission:Books.Delete": "Deleting the books",
    "BookStore:00001": "There is already an author with the same name: {name}",
    "Permission:Authors": "Author Management",
    "Permission:Authors.Create": "Creating new authors",
    "Permission:Authors.Edit": "Editing the authors",
    "Permission:Authors.Delete": "Deleting the authors",
    "Menu:Authors": "Authors",
    "Authors": "Authors",
    "AuthorDeletionConfirmationMessage": "Are you sure you want to delete this item?",
    "BirthDate": "Birth date",
    "ShortBio": "Short Bio",
    "NewAuthor": "New author",
    "EditAuthor": "Edit author"
  }
}

zh-Hans.json

{
  "culture": "zh-Hans",
  "texts": {
    "AppName": "BookStore",
    "Menu:Home": "首页",
    "Welcome": "欢迎",
    "LongWelcomeMessage": "欢迎使用本应用程序。这是一个基于 ABP 框架的启动项目。更多信息,请访问 abp.io。",
    // 以下内容是新增内容
    "Menu:BookStore": "书店",
    "Menu:Books": "书籍管理",
    "Actions": "操作",
    "Close": "关闭",
    "Delete": "删除",
    "Edit": "编辑",
    "Save": "保存",
    "Cancel": "取消",
    "Update": "更新",
    "PublishDate": "发布日期",
    "NewBook": "新增书",
    "EditBook": "编辑书",
    "Name": "名字",
    "Type": "类型",
    "Price": "价格",
    "CreationTime": "新建日期",
    "Author": "作者",
    "SelectAuthor": "选择作者",
    "SelectType": "选择类型",
    "AreYouSure": "你确定吗?",
    "AreYouSureToDelete": "你确定你要删除此条目吗?",
    "Enum:BookType.0": "未定义",
    "Enum:BookType.1": "冒险",
    "Enum:BookType.2": "传记",
    "Enum:BookType.3": "反乌托邦",
    "Enum:BookType.4": "奇幻",
    "Enum:BookType.5": "恐怖",
    "Enum:BookType.6": "科学",
    "Enum:BookType.7": "科幻",
    "Enum:BookType.8": "诗歌",
    "Permission:BookStore": "书店",
    "Permission:Books": "书籍管理",
    "Permission:Books.Create": "新建书籍",
    "Permission:Books.Edit": "编辑书籍",
    "Permission:Books.Delete": "删除书籍",
    "BookStore:00001": "已经存在相同名字的作者: {name}",
    "Permission:Authors": "作者管理",
    "Permission:Authors.Create": "新建作者",
    "Permission:Authors.Edit": "编辑作者",
    "Permission:Authors.Delete": "删除作者",
    "Menu:Authors": "作者管理",
    "Authors": "作者",
    "AuthorDeletionConfirmationMessage": "你确定要删除此条目吗?",
    "BirthDate": "生日",
    "ShortBio": "简介",
    "NewAuthor": "新建作者",
    "EditAuthor": "编辑作者"
  }
}

10. 感谢

历时一个多月,总算完成本系列的最后一篇了,感谢各位的收藏、点赞支持。

后序我会开始完成ABP User Interface-Angular UI中文详解-CSDN博客系列的中文版本以及ABP核心模块的中文版本,感兴趣的朋友可以先收藏下。

谢谢。

Clean版本的代码:acme-book-store-default

如果需要完整版的可以私聊我微信:18901599114

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值