Browse Source

Merge pull request #15396 from abpframework/auto-merge/rel-7-0/1651

Merge branch dev with rel-7.0
pull/15405/head
maliming 4 years ago
committed by GitHub
parent
commit
2e57be7700
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 258
      docs/en/Tutorials/Part-1.md
  2. 814
      docs/en/Tutorials/Part-10.md
  3. 17
      docs/en/Tutorials/Part-2.md
  4. 97
      docs/en/Tutorials/Part-3.md
  5. 144
      docs/en/Tutorials/Part-4.md
  6. 87
      docs/en/Tutorials/Part-5.md
  7. 195
      docs/en/Tutorials/Part-6.md
  8. 132
      docs/en/Tutorials/Part-7.md
  9. 379
      docs/en/Tutorials/Part-8.md
  10. 416
      docs/en/Tutorials/Part-9.md
  11. BIN
      docs/en/Tutorials/images/blazor-add-book-button-2.png
  12. BIN
      docs/en/Tutorials/images/blazor-bookstore-book-list-2.png
  13. BIN
      docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors-2.png
  14. BIN
      docs/en/Tutorials/images/blazor-delete-book-action-2.png
  15. BIN
      docs/en/Tutorials/images/blazor-edit-book-action-3.png
  16. BIN
      docs/en/Tutorials/images/blazor-edit-book-modal-2.png
  17. BIN
      docs/en/Tutorials/images/blazor-new-book-modal-2.png
  18. BIN
      docs/en/Tutorials/images/book-create-modal-with-author-2.png
  19. BIN
      docs/en/Tutorials/images/bookstore-actions-buttons-2.png
  20. BIN
      docs/en/Tutorials/images/bookstore-angular-author-selection-2.png
  21. BIN
      docs/en/Tutorials/images/bookstore-angular-authors-page-2.png
  22. BIN
      docs/en/Tutorials/images/bookstore-author-permissions-3.png
  23. BIN
      docs/en/Tutorials/images/bookstore-authors-page-3.png
  24. BIN
      docs/en/Tutorials/images/bookstore-book-list-angular.png
  25. BIN
      docs/en/Tutorials/images/bookstore-books-with-authorname-angular-2.png
  26. BIN
      docs/en/Tutorials/images/bookstore-confirmation-popup-2.png
  27. BIN
      docs/en/Tutorials/images/bookstore-empty-new-book-modal-2.png
  28. BIN
      docs/en/Tutorials/images/bookstore-final-actions-dropdown-2.png
  29. BIN
      docs/en/Tutorials/images/bookstore-new-book-form-v3.png
  30. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs
  31. 3
      npm/ng-packs/package.json

258
docs/en/Tutorials/Part-1.md

@ -63,18 +63,17 @@ The main entity of the application is the `Book`. Create a `Books` folder (names
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace Acme.BookStore.Books
namespace Acme.BookStore.Books;
public class Book : AuditedAggregateRoot<Guid>
{
public class Book : AuditedAggregateRoot<Guid>
{
public string Name { get; set; }
public string Name { get; set; }
public BookType Type { get; set; }
public BookType Type { get; set; }
public DateTime PublishDate { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
}
public float Price { get; set; }
}
````
@ -89,20 +88,19 @@ namespace Acme.BookStore.Books
The `Book` entity uses the `BookType` enum. Create a `Books` folder (namespace) in the `Acme.BookStore.Domain.Shared` project and add a `BookType` inside it:
````csharp
namespace Acme.BookStore.Books
namespace Acme.BookStore.Books;
public enum BookType
{
public enum BookType
{
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry
}
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry
}
````
@ -150,34 +148,33 @@ Navigate to the `OnModelCreating` method in the `BookStoreDbContext` class and a
using Acme.BookStore.Books;
...
namespace Acme.BookStore.EntityFrameworkCore
namespace Acme.BookStore.EntityFrameworkCore;
public class BookStoreDbContext :
AbpDbContext<BookStoreDbContext>,
IIdentityDbContext,
ITenantManagementDbContext
{
public class BookStoreDbContext :
AbpDbContext<BookStoreDbContext>,
IIdentityDbContext,
ITenantManagementDbContext
{
...
...
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Include modules to your migration db context */
/* Include modules to your migration db context */
builder.ConfigurePermissionManagement();
...
builder.ConfigurePermissionManagement();
...
/* Configure your own tables/entities inside here */
/* 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);
});
}
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);
});
}
}
````
@ -217,44 +214,43 @@ using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
namespace Acme.BookStore;
public class BookStoreDataSeederContributor
: IDataSeedContributor, ITransientDependency
{
public class BookStoreDataSeederContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IRepository<Book, Guid> _bookRepository;
public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}
public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}
public async Task SeedAsync(DataSeedContext context)
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() <= 0)
{
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
);
}
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
);
}
}
}
@ -287,18 +283,17 @@ In this section, you will create an application service to get, create, update a
using System;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Books
namespace Acme.BookStore.Books;
public class BookDto : AuditedEntityDto<Guid>
{
public class BookDto : AuditedEntityDto<Guid>
{
public string Name { get; set; }
public string Name { get; set; }
public BookType Type { get; set; }
public BookType Type { get; set; }
public DateTime PublishDate { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
}
public float Price { get; set; }
}
````
@ -312,14 +307,13 @@ It will be needed to map the `Book` entities to the `BookDto` objects while retu
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore
namespace Acme.BookStore;
public class BookStoreApplicationAutoMapperProfile : Profile
{
public class BookStoreApplicationAutoMapperProfile : Profile
public BookStoreApplicationAutoMapperProfile()
{
public BookStoreApplicationAutoMapperProfile()
{
CreateMap<Book, BookDto>();
}
CreateMap<Book, BookDto>();
}
}
````
@ -334,24 +328,23 @@ Create a `CreateUpdateBookDto` class in the `Books` folder (namespace) of the `A
using System;
using System.ComponentModel.DataAnnotations;
namespace Acme.BookStore.Books
namespace Acme.BookStore.Books;
public class CreateUpdateBookDto
{
public class CreateUpdateBookDto
{
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
[DataType(DataType.Date)]
public DateTime PublishDate { get; set; } = DateTime.Now;
[Required]
[DataType(DataType.Date)]
public DateTime PublishDate { get; set; } = DateTime.Now;
[Required]
public float Price { get; set; }
}
[Required]
public float Price { get; set; }
}
````
@ -364,15 +357,14 @@ As done to the `BookDto` above, we should define the mapping from the `CreateUpd
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore
namespace Acme.BookStore;
public class BookStoreApplicationAutoMapperProfile : Profile
{
public class BookStoreApplicationAutoMapperProfile : Profile
public BookStoreApplicationAutoMapperProfile()
{
public BookStoreApplicationAutoMapperProfile()
{
CreateMap<Book, BookDto>();
CreateMap<CreateUpdateBookDto, Book>();
}
CreateMap<Book, BookDto>();
CreateMap<CreateUpdateBookDto, Book>();
}
}
````
@ -386,17 +378,16 @@ using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.BookStore.Books
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
{
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
{
}
}
````
@ -414,22 +405,21 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore.Books
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 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)
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
}
}
````

814
docs/en/Tutorials/Part-10.md

@ -141,72 +141,71 @@ using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
namespace Acme.BookStore;
public class BookStoreDataSeederContributor
: IDataSeedContributor, ITransientDependency
{
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)
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IAuthorRepository _authorRepository;
private readonly AuthorManager _authorManager;
public BookStoreDataSeederContributor(
IRepository<Book, Guid> bookRepository,
IAuthorRepository authorRepository,
AuthorManager authorManager)
if (await _bookRepository.GetCountAsync() > 0)
{
_bookRepository = bookRepository;
_authorRepository = authorRepository;
_authorManager = authorManager;
return;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() > 0)
var orwell = 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)."
)
);
var douglas = 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'."
)
);
await _bookRepository.InsertAsync(
new Book
{
return;
}
var orwell = 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)."
)
);
var douglas = 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'."
)
);
await _bookRepository.InsertAsync(
new Book
{
AuthorId = orwell.Id, // SET THE AUTHOR
Name = "1984",
Type = BookType.Dystopia,
PublishDate = new DateTime(1949, 6, 8),
Price = 19.84f
},
autoSave: true
);
await _bookRepository.InsertAsync(
new Book
{
AuthorId = douglas.Id, // SET THE AUTHOR
Name = "The Hitchhiker's Guide to the Galaxy",
Type = BookType.ScienceFiction,
PublishDate = new DateTime(1995, 9, 27),
Price = 42.0f
},
autoSave: true
);
}
AuthorId = orwell.Id, // SET THE AUTHOR
Name = "1984",
Type = BookType.Dystopia,
PublishDate = new DateTime(1949, 6, 8),
Price = 19.84f
},
autoSave: true
);
await _bookRepository.InsertAsync(
new Book
{
AuthorId = douglas.Id, // SET THE AUTHOR
Name = "The Hitchhiker's Guide to the Galaxy",
Type = BookType.ScienceFiction,
PublishDate = new DateTime(1995, 9, 27),
Price = 42.0f
},
autoSave: true
);
}
}
````
@ -248,22 +247,21 @@ The final `BookDto` class should be following:
using System;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Books
namespace Acme.BookStore.Books;
public class BookDto : AuditedEntityDto<Guid>
{
public class BookDto : AuditedEntityDto<Guid>
{
public Guid AuthorId { get; set; }
public Guid AuthorId { get; set; }
public string AuthorName { get; set; }
public string AuthorName { get; set; }
public string Name { get; set; }
public string Name { get; set; }
public BookType Type { get; set; }
public BookType Type { get; set; }
public DateTime PublishDate { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
}
public float Price { get; set; }
}
```
@ -283,12 +281,11 @@ Create a new class, `AuthorLookupDto`, inside the `Books` folder of the `Acme.Bo
using System;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Books
namespace Acme.BookStore.Books;
public class AuthorLookupDto : EntityDto<Guid>
{
public class AuthorLookupDto : EntityDto<Guid>
{
public string Name { get; set; }
}
public string Name { get; set; }
}
````
@ -304,18 +301,17 @@ using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.BookStore.Books
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
{
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
{
// ADD the NEW METHOD
Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync();
}
// ADD the NEW METHOD
Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync();
}
````
@ -341,119 +337,118 @@ using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore.Books
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
{
[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)
{
private readonly IAuthorRepository _authorRepository;
_authorRepository = authorRepository;
GetPolicyName = BookStorePermissions.Books.Default;
GetListPolicyName = BookStorePermissions.Books.Default;
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Create;
}
public BookAppService(
IRepository<Book, Guid> repository,
IAuthorRepository authorRepository)
: base(repository)
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)
{
_authorRepository = authorRepository;
GetPolicyName = BookStorePermissions.Books.Default;
GetListPolicyName = BookStorePermissions.Books.Default;
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Create;
throw new EntityNotFoundException(typeof(Book), id);
}
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;
}
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();
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};
//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);
//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);
//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();
//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();
//Get the total count with another query
var totalCount = await Repository.GetCountAsync();
return new PagedResultDto<BookDto>(
totalCount,
bookDtos
);
}
return new PagedResultDto<BookDto>(
totalCount,
bookDtos
);
}
public async Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync()
{
var authors = await _authorRepository.GetListAsync();
public async Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync()
{
var authors = await _authorRepository.GetListAsync();
return new ListResultDto<AuthorLookupDto>(
ObjectMapper.Map<List<Author>, List<AuthorLookupDto>>(authors)
);
}
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)}";
}
private static string NormalizeSorting(string sorting)
if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase))
{
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}";
return sorting.Replace(
"authorName",
"author.Name",
StringComparison.OrdinalIgnoreCase
);
}
return $"book.{sorting}";
}
}
```
@ -485,108 +480,107 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore.Books
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
{
[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;
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.Create;
}
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.Create;
}
public async override Task<BookDto> GetAsync(Guid id)
{
var book = await Repository.GetAsync(id);
var bookDto = ObjectMapper.Map<Book, BookDto>(book);
public async override Task<BookDto> GetAsync(Guid id)
{
var book = await Repository.GetAsync(id);
var bookDto = ObjectMapper.Map<Book, BookDto>(book);
var author = await _authorRepository.GetAsync(book.AuthorId);
bookDto.AuthorName = author.Name;
var author = await _authorRepository.GetAsync(book.AuthorId);
bookDto.AuthorName = author.Name;
return bookDto;
}
return bookDto;
}
public async override Task<PagedResultDto<BookDto>>
GetListAsync(PagedAndSortedResultRequestDto input)
public async override Task<PagedResultDto<BookDto>>
GetListAsync(PagedAndSortedResultRequestDto input)
{
//Set a default sorting, if not provided
if (input.Sorting.IsNullOrWhiteSpace())
{
//Set a default sorting, if not provided
if (input.Sorting.IsNullOrWhiteSpace())
{
input.Sorting = nameof(Book.Name);
}
input.Sorting = nameof(Book.Name);
}
//Get the IQueryable<Book> from the repository
var queryable = await Repository.GetQueryableAsync();
//Get the IQueryable<Book> from the repository
var queryable = await Repository.GetQueryableAsync();
//Get the books
var books = await AsyncExecuter.ToListAsync(
queryable
.OrderBy(input.Sorting)
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
);
//Get the books
var books = await AsyncExecuter.ToListAsync(
queryable
.OrderBy(input.Sorting)
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
);
//Convert to DTOs
var bookDtos = ObjectMapper.Map<List<Book>, List<BookDto>>(books);
//Convert to DTOs
var bookDtos = ObjectMapper.Map<List<Book>, List<BookDto>>(books);
//Get a lookup dictionary for the related authors
var authorDictionary = await GetAuthorDictionaryAsync(books);
//Get a lookup dictionary for the related authors
var authorDictionary = await GetAuthorDictionaryAsync(books);
//Set AuthorName for the DTOs
bookDtos.ForEach(bookDto => bookDto.AuthorName =
authorDictionary[bookDto.AuthorId].Name);
//Set AuthorName for the DTOs
bookDtos.ForEach(bookDto => bookDto.AuthorName =
authorDictionary[bookDto.AuthorId].Name);
//Get the total count with another query (required for the paging)
var totalCount = await Repository.GetCountAsync();
//Get the total count with another query (required for the paging)
var totalCount = await Repository.GetCountAsync();
return new PagedResultDto<BookDto>(
totalCount,
bookDtos
);
}
return new PagedResultDto<BookDto>(
totalCount,
bookDtos
);
}
public async Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync()
{
var authors = await _authorRepository.GetListAsync();
public async Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync()
{
var authors = await _authorRepository.GetListAsync();
return new ListResultDto<AuthorLookupDto>(
ObjectMapper.Map<List<Author>, List<AuthorLookupDto>>(authors)
);
}
return new ListResultDto<AuthorLookupDto>(
ObjectMapper.Map<List<Author>, List<AuthorLookupDto>>(authors)
);
}
private async Task<Dictionary<Guid, Author>>
GetAuthorDictionaryAsync(List<Book> books)
{
var authorIds = books
.Select(b => b.AuthorId)
.Distinct()
.ToArray();
private async Task<Dictionary<Guid, Author>>
GetAuthorDictionaryAsync(List<Book> books)
{
var authorIds = books
.Select(b => b.AuthorId)
.Distinct()
.ToArray();
var queryable = await _authorRepository.GetQueryableAsync();
var queryable = await _authorRepository.GetQueryableAsync();
var authors = await AsyncExecuter.ToListAsync(
queryable.Where(a => authorIds.Contains(a.Id))
);
var authors = await AsyncExecuter.ToListAsync(
queryable.Where(a => authorIds.Contains(a.Id))
);
return authors.ToDictionary(x => x.Id, x => x);
}
return authors.ToDictionary(x => x.Id, x => x);
}
}
```
@ -623,76 +617,76 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;
namespace Acme.BookStore.Books
{ {{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
namespace Acme.BookStore.Books;
{{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
private readonly IAuthorAppService _authorAppService;
public BookAppService_Tests()
{
private readonly IBookAppService _bookAppService;
private readonly IAuthorAppService _authorAppService;
_bookAppService = GetRequiredService<IBookAppService>();
_authorAppService = GetRequiredService<IAuthorAppService>();
}
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
_authorAppService = GetRequiredService<IAuthorAppService>();
}
[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 == "1984" &&
b.AuthorName == "George Orwell");
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
[Fact]
public async Task Should_Create_A_Valid_Book()
{
var authors = await _authorAppService.GetListAsync(new GetAuthorListDto());
var firstAuthor = authors.Items.First();
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "1984" &&
b.AuthorName == "George Orwell");
}
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
AuthorId = firstAuthor.Id,
Name = "New test book 42",
Price = 10,
PublishDate = System.DateTime.Now,
Type = BookType.ScienceFiction
}
);
[Fact]
public async Task Should_Create_A_Valid_Book()
{
var authors = await _authorAppService.GetListAsync(new GetAuthorListDto());
var firstAuthor = authors.Items.First();
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
//Act
var result = await _bookAppService.CreateAsync(
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
AuthorId = firstAuthor.Id,
Name = "New test book 42",
Name = "",
Price = 10,
PublishDate = System.DateTime.Now,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
[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(m => m == "Name"));
}
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(m => m == "Name"));
}
}
```
@ -751,61 +745,60 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace Acme.BookStore.Web.Pages.Books
namespace Acme.BookStore.Web.Pages.Books;
public class CreateModalModel : BookStorePageModel
{
public class CreateModalModel : BookStorePageModel
{
[BindProperty]
public CreateBookViewModel Book { get; set; }
[BindProperty]
public CreateBookViewModel Book { get; set; }
public List<SelectListItem> Authors { get; set; }
public List<SelectListItem> Authors { get; set; }
private readonly IBookAppService _bookAppService;
private readonly IBookAppService _bookAppService;
public CreateModalModel(
IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public CreateModalModel(
IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public async Task OnGetAsync()
{
Book = new CreateBookViewModel();
public async Task OnGetAsync()
{
Book = new CreateBookViewModel();
var authorLookup = await _bookAppService.GetAuthorLookupAsync();
Authors = authorLookup.Items
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
}
var authorLookup = await _bookAppService.GetAuthorLookupAsync();
Authors = authorLookup.Items
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.CreateAsync(
ObjectMapper.Map<CreateBookViewModel, CreateUpdateBookDto>(Book)
);
return NoContent();
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.CreateAsync(
ObjectMapper.Map<CreateBookViewModel, CreateUpdateBookDto>(Book)
);
return NoContent();
}
public class CreateBookViewModel
{
[SelectItems(nameof(Authors))]
[DisplayName("Author")]
public Guid AuthorId { get; set; }
public class CreateBookViewModel
{
[SelectItems(nameof(Authors))]
[DisplayName("Author")]
public Guid AuthorId { get; set; }
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
[DataType(DataType.Date)]
public DateTime PublishDate { get; set; } = DateTime.Now;
[Required]
[DataType(DataType.Date)]
public DateTime PublishDate { get; set; } = DateTime.Now;
[Required]
public float Price { get; set; }
}
[Required]
public float Price { get; set; }
}
}
```
@ -830,66 +823,65 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace Acme.BookStore.Web.Pages.Books
namespace Acme.BookStore.Web.Pages.Books;
public class EditModalModel : BookStorePageModel
{
public class EditModalModel : BookStorePageModel
{
[BindProperty]
public EditBookViewModel Book { get; set; }
[BindProperty]
public EditBookViewModel Book { get; set; }
public List<SelectListItem> Authors { get; set; }
public List<SelectListItem> Authors { get; set; }
private readonly IBookAppService _bookAppService;
private readonly IBookAppService _bookAppService;
public EditModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public EditModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public async Task OnGetAsync(Guid id)
{
var bookDto = await _bookAppService.GetAsync(id);
Book = ObjectMapper.Map<BookDto, EditBookViewModel>(bookDto);
public async Task OnGetAsync(Guid id)
{
var bookDto = await _bookAppService.GetAsync(id);
Book = ObjectMapper.Map<BookDto, EditBookViewModel>(bookDto);
var authorLookup = await _bookAppService.GetAuthorLookupAsync();
Authors = authorLookup.Items
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
}
var authorLookup = await _bookAppService.GetAuthorLookupAsync();
Authors = authorLookup.Items
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.UpdateAsync(
Book.Id,
ObjectMapper.Map<EditBookViewModel, CreateUpdateBookDto>(Book)
);
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.UpdateAsync(
Book.Id,
ObjectMapper.Map<EditBookViewModel, CreateUpdateBookDto>(Book)
);
return NoContent();
}
return NoContent();
}
public class EditBookViewModel
{
[HiddenInput]
public Guid Id { get; set; }
public class EditBookViewModel
{
[HiddenInput]
public Guid Id { get; set; }
[SelectItems(nameof(Authors))]
[DisplayName("Author")]
public Guid AuthorId { get; set; }
[SelectItems(nameof(Authors))]
[DisplayName("Author")]
public Guid AuthorId { get; set; }
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
[DataType(DataType.Date)]
public DateTime PublishDate { get; set; } = DateTime.Now;
[Required]
[DataType(DataType.Date)]
public DateTime PublishDate { get; set; } = DateTime.Now;
[Required]
public float Price { get; set; }
}
[Required]
public float Price { get; set; }
}
}
```
@ -964,13 +956,13 @@ Book list page change is trivial. Open the `/src/app/book/book.component.html` a
When you run the application, you can see the *Author* column on the table:
![bookstore-books-with-authorname-angular](images/bookstore-books-with-authorname-angular.png)
![bookstore-books-with-authorname-angular](images/bookstore-books-with-authorname-angular-2.png)
### Create/Edit Forms
The next step is to add an Author selection (dropdown) to the create/edit forms. The final UI will look like the one shown below:
![bookstore-angular-author-selection](images/bookstore-angular-author-selection.png)
![bookstore-angular-author-selection](images/bookstore-angular-author-selection-2.png)
Added the Author dropdown as the first element in the form.
@ -1112,7 +1104,7 @@ It is very easy to show the *Author Name* in the book list. Open the `/Pages/Boo
When you run the application, you can see the *Author* column on the table:
![blazor-bookstore-book-list-with-authors](images/blazor-bookstore-book-list-with-authors.png)
![blazor-bookstore-book-list-with-authors](images/blazor-bookstore-book-list-with-authors-2.png)
### Create Book Modal
@ -1183,7 +1175,7 @@ This requires to add a new localization key to the `en.json` file:
You can run the application to see the *Author Selection* while creating a new book:
![book-create-modal-with-author](images/book-create-modal-with-author.png)
![book-create-modal-with-author](images/book-create-modal-with-author-2.png)
### Edit Book Modal

17
docs/en/Tutorials/Part-2.md

@ -178,14 +178,13 @@ Open the `Index.cshtml` and change the whole content as shown below:
```csharp
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Acme.BookStore.Web.Pages.Books
namespace Acme.BookStore.Web.Pages.Books;
public class IndexModel : PageModel
{
public class IndexModel : PageModel
public void OnGet()
{
public void OnGet()
{
}
}
}
```
@ -537,7 +536,7 @@ Open the `/src/app/book/book.component.html` and replace the content as shown be
Now you can see the final result on your browser:
![Book list final result](images/bookstore-book-list.png)
![Book list final result](images/bookstore-book-list-angular.png)
{{else if UI == "Blazor" || UI == "BlazorServer"}}
@ -581,7 +580,7 @@ context.Menu.AddItem(
Run the project, login to the application with the username `admin` and the password `1q2w3E*` and see that the new menu item has been added to the main menu:
![blazor-menu-bookstore](images/blazor-menu-bookstore.png)
![blazor-menu-bookstore](images/bookstore-new-menu-item-2.png)
When you click on the Books menu item under the Book Store parent, you will be redirected to the new empty Books Page.
@ -621,7 +620,7 @@ Open the `Books.razor` and replace the content as the following:
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
<DisplayTemplate>
@L[$"Enum:BookType.{Enum.GetName(context.Type)}"]
@L[$"Enum:BookType.{context.Type}"]
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
@ -664,7 +663,7 @@ We will continue benefitting from `AbpCrudPageBase` for the books page. You cou
You can run the application! The final UI of this part is shown below:
![blazor-bookstore-book-list](images/blazor-bookstore-book-list.png)
![blazor-bookstore-book-list](images/blazor-bookstore-book-list-2.png)
This is a fully working, server side paged, sorted and localized table of books.

97
docs/en/Tutorials/Part-3.md

@ -283,35 +283,34 @@ using System.Threading.Tasks;
using Acme.BookStore.Books;
using Microsoft.AspNetCore.Mvc;
namespace Acme.BookStore.Web.Pages.Books
namespace Acme.BookStore.Web.Pages.Books;
public class EditModalModel : BookStorePageModel
{
public class EditModalModel : BookStorePageModel
{
[HiddenInput]
[BindProperty(SupportsGet = true)]
public Guid Id { get; set; }
[HiddenInput]
[BindProperty(SupportsGet = true)]
public Guid Id { get; set; }
[BindProperty]
public CreateUpdateBookDto Book { get; set; }
[BindProperty]
public CreateUpdateBookDto Book { get; set; }
private readonly IBookAppService _bookAppService;
private readonly IBookAppService _bookAppService;
public EditModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public EditModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public async Task OnGetAsync()
{
var bookDto = await _bookAppService.GetAsync(Id);
Book = ObjectMapper.Map<BookDto, CreateUpdateBookDto>(bookDto);
}
public async Task OnGetAsync()
{
var bookDto = await _bookAppService.GetAsync(Id);
Book = ObjectMapper.Map<BookDto, CreateUpdateBookDto>(bookDto);
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.UpdateAsync(Id, Book);
return NoContent();
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.UpdateAsync(Id, Book);
return NoContent();
}
}
````
@ -327,14 +326,13 @@ To be able to map the `BookDto` to `CreateUpdateBookDto`, configure a new mappin
````csharp
using AutoMapper;
namespace Acme.BookStore.Web
namespace Acme.BookStore.Web;
public class BookStoreWebAutoMapperProfile : Profile
{
public class BookStoreWebAutoMapperProfile : Profile
public BookStoreWebAutoMapperProfile()
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
}
CreateMap<BookDto, CreateUpdateBookDto>();
}
}
````
@ -689,7 +687,7 @@ Open `/src/app/book/book.component.html` and make the following changes:
You can open your browser and click the **New book** button to see the new modal.
![Empty modal for new book](images/bookstore-empty-new-book-modal.png)
![Empty modal for new book](images/bookstore-empty-new-book-modal-2.png)
### Create a Reactive Form
@ -926,7 +924,7 @@ export class BookComponent implements OnInit {
Now, you can open your browser to see the changes:
![Save button to the modal](images/bookstore-new-book-form-v2.png)
![Save button to the modal](images/bookstore-new-book-form-v3.png)
## Updating a Book
@ -1054,7 +1052,7 @@ Open `/src/app/book/book.component.html`  and add the following `ngx-datatable-
Added an "Actions" dropdown as the first column of the table that is shown below:
![Action buttons](images/bookstore-actions-buttons.png)
![Action buttons](images/bookstore-actions-buttons-2.png)
Also, change the `ng-template #abpHeader` section as shown below:
@ -1118,11 +1116,11 @@ Open `/src/app/book/book.component.html` and modify the `ngbDropdownMenu` to add
The final actions dropdown UI looks like below:
![bookstore-final-actions-dropdown](images/bookstore-final-actions-dropdown.png)
![bookstore-final-actions-dropdown](images/bookstore-final-actions-dropdown-2.png)
Clicking the "Delete" action calls the `delete` method which then shows a confirmation popup as shown below:
![bookstore-confirmation-popup](images/bookstore-confirmation-popup.png)
![bookstore-confirmation-popup](images/bookstore-confirmation-popup-2.png)
{{end}}
@ -1152,7 +1150,7 @@ Open the `Books.razor` and replace the `<CardHeader>` section with the following
This will change the card header by adding a "New book" button to the right side:
![blazor-add-book-button](images/blazor-add-book-button.png)
![blazor-add-book-button](images/blazor-add-book-button-2.png)
Now, we can add a modal that will be opened when we click the button.
@ -1187,7 +1185,7 @@ Open the `Books.razor` and add the following code to the end of the page:
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType.{Enum.GetName((BookType)bookTypeValue)}"]
@L[$"Enum:BookType.{bookTypeValue}"]
</SelectItem>
}
</Select>
@ -1226,7 +1224,7 @@ This code requires a service; Inject the `AbpBlazorMessageLocalizerHelper<T>` at
That's all. Run the application and try to add a new book:
![blazor-new-book-modal](images/blazor-new-book-modal.png)
![blazor-new-book-modal](images/blazor-new-book-modal-2.png)
## Updating a Book
@ -1252,7 +1250,7 @@ Open the `Books.razor` and add the following `DataGridEntityActionsColumn` secti
The `DataGridEntityActionsColumn` component is used to show an "Actions" dropdown for each row in the `DataGrid`. The `DataGridEntityActionsColumn` shows a **single button** instead of a dropdown if there is only one available action inside it:
![blazor-edit-book-action](images/blazor-edit-book-action-2.png)
![blazor-edit-book-action](images/blazor-edit-book-action-3.png)
### Edit Modal
@ -1285,7 +1283,7 @@ We can now define a modal to edit the book. Add the following code to the end of
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType.{Enum.GetName((BookType)bookTypeValue)}"]
@L[$"Enum:BookType.{bookTypeValue}"]
</SelectItem>
}
</Select>
@ -1323,14 +1321,13 @@ Open the `BookStoreBlazorAutoMapperProfile` inside the `Acme.BookStore.Blazor` p
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore.Blazor
namespace Acme.BookStore.Blazor;
public class BookStoreBlazorAutoMapperProfile : Profile
{
public class BookStoreBlazorAutoMapperProfile : Profile
public BookStoreBlazorAutoMapperProfile()
{
public BookStoreBlazorAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
}
CreateMap<BookDto, CreateUpdateBookDto>();
}
}
````
@ -1341,7 +1338,7 @@ namespace Acme.BookStore.Blazor
You can now run the application and try to edit a book.
![blazor-edit-book-modal](images/blazor-edit-book-modal.png)
![blazor-edit-book-modal](images/blazor-edit-book-modal-2.png)
> Tip: Try to leave the *Name* field empty and submit the form to show the validation error message.
@ -1362,7 +1359,7 @@ Open the `Books.razor` page and add the following `EntityAction` code under the
The "Actions" button becomes a dropdown since it has two actions now:
![blazor-edit-book-action](images/blazor-delete-book-action.png)
![blazor-delete-book-action](images/blazor-delete-book-action-2.png)
Run the application and try to delete a book.
@ -1422,7 +1419,7 @@ Here's the complete code to create the book management CRUD page, that has been
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
<DisplayTemplate>
@L[$"Enum:BookType.{Enum.GetName(context.Type)}"]
@L[$"Enum:BookType.{context.Type}"]
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
@ -1474,7 +1471,7 @@ Here's the complete code to create the book management CRUD page, that has been
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType.{Enum.GetName((BookType)bookTypeValue)}"]
@L[$"Enum:BookType.{bookTypeValue}"]
</SelectItem>
}
</Select>
@ -1527,7 +1524,7 @@ Here's the complete code to create the book management CRUD page, that has been
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType.{Enum.GetName((BookType)bookTypeValue)}"]
@L[$"Enum:BookType.{bookTypeValue}"]
</SelectItem>
}
</Select>

144
docs/en/Tutorials/Part-4.md

@ -85,30 +85,31 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;
namespace Acme.BookStore.Books
{ {{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
namespace Acme.BookStore.Books;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
{{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]
{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "1984");
}
[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 == "1984");
}
}
````
@ -176,69 +177,70 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;
namespace Acme.BookStore.Books
{ {{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
namespace Acme.BookStore.Books;
{{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]
{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
private readonly IBookAppService _bookAppService;
_bookAppService = GetRequiredService<IBookAppService>();
}
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
[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 == "1984");
}
[Fact]
public async Task Should_Create_A_Valid_Book()
{
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "1984");
}
[Fact]
public async Task Should_Create_A_Valid_Book()
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
//Act
var result = await _bookAppService.CreateAsync(
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Name = "",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
[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"));
}
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
}
}
````

87
docs/en/Tutorials/Part-5.md

@ -55,19 +55,18 @@ A permission must have a unique name (a `string`). The best way is to define it
Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below:
````csharp
namespace Acme.BookStore.Permissions
namespace Acme.BookStore.Permissions;
public static class BookStorePermissions
{
public static class BookStorePermissions
public const string GroupName = "BookStore";
public static class Books
{
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";
}
public const string Default = GroupName + ".Books";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
}
````
@ -85,24 +84,23 @@ using Acme.BookStore.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
namespace Acme.BookStore.Permissions
namespace Acme.BookStore.Permissions;
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
{
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
public override void Define(IPermissionDefinitionContext context)
{
public override void Define(IPermissionDefinitionContext context)
{
var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore"));
var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books"));
booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create"));
booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit"));
booksPermission.AddChild(BookStorePermissions.Books.Delete, L("Permission:Books.Delete"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<BookStoreResource>(name);
}
var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore"));
var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books"));
booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create"));
booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit"));
booksPermission.AddChild(BookStorePermissions.Books.Delete, L("Permission:Books.Delete"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<BookStoreResource>(name);
}
}
````
@ -148,26 +146,25 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore.Books
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 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)
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
GetPolicyName = BookStorePermissions.Books.Default;
GetListPolicyName = BookStorePermissions.Books.Default;
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Delete;
}
GetPolicyName = BookStorePermissions.Books.Default;
GetListPolicyName = BookStorePermissions.Books.Default;
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Delete;
}
}
````

195
docs/en/Tutorials/Part-6.md

@ -60,45 +60,44 @@ using JetBrains.Annotations;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
namespace Acme.BookStore.Authors
namespace Acme.BookStore.Authors;
public class Author : FullAuditedAggregateRoot<Guid>
{
public class Author : FullAuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
public string Name { get; private set; }
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
private Author()
{
/* This constructor is for deserialization / ORM purpose */
}
private Author()
{
/* This constructor is for deserialization / ORM purpose */
}
internal Author(
Guid id,
[NotNull] string name,
DateTime birthDate,
[CanBeNull] string shortBio = null)
: base(id)
{
SetName(name);
BirthDate = birthDate;
ShortBio = shortBio;
}
internal Author(
Guid id,
[NotNull] string name,
DateTime birthDate,
[CanBeNull] string shortBio = null)
: base(id)
{
SetName(name);
BirthDate = birthDate;
ShortBio = shortBio;
}
internal Author ChangeName([NotNull] string name)
{
SetName(name);
return this;
}
internal Author ChangeName([NotNull] string name)
{
SetName(name);
return this;
}
private void SetName([NotNull] string name)
{
Name = Check.NotNullOrWhiteSpace(
name,
nameof(name),
maxLength: AuthorConsts.MaxNameLength
);
}
private void SetName([NotNull] string name)
{
Name = Check.NotNullOrWhiteSpace(
name,
nameof(name),
maxLength: AuthorConsts.MaxNameLength
);
}
}
````
@ -113,13 +112,13 @@ namespace Acme.BookStore.Authors
`AuthorConsts` is a simple class that is located under the `Authors` namespace (folder) of the `Acme.BookStore.Domain.Shared` project:
````csharp
namespace Acme.BookStore.Authors
namespace Acme.BookStore.Authors;
public static class AuthorConsts
{
public static class AuthorConsts
{
public const int MaxNameLength = 64;
}
public const int MaxNameLength = 64;
}
````
Created this class inside the `Acme.BookStore.Domain.Shared` project since we will re-use it on the [Data Transfer Objects](../Data-Transfer-Objects.md) (DTOs) later.
@ -135,53 +134,52 @@ using JetBrains.Annotations;
using Volo.Abp;
using Volo.Abp.Domain.Services;
namespace Acme.BookStore.Authors
namespace Acme.BookStore.Authors;
public class AuthorManager : DomainService
{
public class AuthorManager : DomainService
private readonly IAuthorRepository _authorRepository;
public AuthorManager(IAuthorRepository authorRepository)
{
private readonly IAuthorRepository _authorRepository;
_authorRepository = authorRepository;
}
public AuthorManager(IAuthorRepository authorRepository)
{
_authorRepository = authorRepository;
}
public async Task<Author> CreateAsync(
[NotNull] string name,
DateTime birthDate,
[CanBeNull] string shortBio = null)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
public async Task<Author> CreateAsync(
[NotNull] string name,
DateTime birthDate,
[CanBeNull] string shortBio = null)
var existingAuthor = await _authorRepository.FindByNameAsync(name);
if (existingAuthor != null)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
var existingAuthor = await _authorRepository.FindByNameAsync(name);
if (existingAuthor != null)
{
throw new AuthorAlreadyExistsException(name);
}
return new Author(
GuidGenerator.Create(),
name,
birthDate,
shortBio
);
throw new AuthorAlreadyExistsException(name);
}
public async Task ChangeNameAsync(
[NotNull] Author author,
[NotNull] string newName)
{
Check.NotNull(author, nameof(author));
Check.NotNullOrWhiteSpace(newName, nameof(newName));
return new Author(
GuidGenerator.Create(),
name,
birthDate,
shortBio
);
}
var existingAuthor = await _authorRepository.FindByNameAsync(newName);
if (existingAuthor != null && existingAuthor.Id != author.Id)
{
throw new AuthorAlreadyExistsException(newName);
}
public async Task ChangeNameAsync(
[NotNull] Author author,
[NotNull] string newName)
{
Check.NotNull(author, nameof(author));
Check.NotNullOrWhiteSpace(newName, nameof(newName));
author.ChangeName(newName);
var existingAuthor = await _authorRepository.FindByNameAsync(newName);
if (existingAuthor != null && existingAuthor.Id != author.Id)
{
throw new AuthorAlreadyExistsException(newName);
}
author.ChangeName(newName);
}
}
````
@ -195,15 +193,14 @@ Both methods checks if there is already an author with the given name and throws
````csharp
using Volo.Abp;
namespace Acme.BookStore.Authors
namespace Acme.BookStore.Authors;
public class AuthorAlreadyExistsException : BusinessException
{
public class AuthorAlreadyExistsException : BusinessException
public AuthorAlreadyExistsException(string name)
: base(BookStoreDomainErrorCodes.AuthorAlreadyExists)
{
public AuthorAlreadyExistsException(string name)
: base(BookStoreDomainErrorCodes.AuthorAlreadyExists)
{
WithData("name", name);
}
WithData("name", name);
}
}
````
@ -213,12 +210,11 @@ namespace Acme.BookStore.Authors
Open the `BookStoreDomainErrorCodes` in the `Acme.BookStore.Domain.Shared` project and change as shown below:
````csharp
namespace Acme.BookStore
namespace Acme.BookStore;
public static class BookStoreDomainErrorCodes
{
public static class BookStoreDomainErrorCodes
{
public const string AuthorAlreadyExists = "BookStore:00001";
}
public const string AuthorAlreadyExists = "BookStore:00001";
}
````
@ -240,19 +236,18 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore.Authors
{
public interface IAuthorRepository : IRepository<Author, Guid>
{
Task<Author> FindByNameAsync(string name);
namespace Acme.BookStore.Authors;
Task<List<Author>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null
);
}
public interface IAuthorRepository : IRepository<Author, Guid>
{
Task<Author> FindByNameAsync(string name);
Task<List<Author>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null
);
}
````

132
docs/en/Tutorials/Part-7.md

@ -121,41 +121,40 @@ using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace Acme.BookStore.Authors
namespace Acme.BookStore.Authors;
public class EfCoreAuthorRepository
: EfCoreRepository<BookStoreDbContext, Author, Guid>,
IAuthorRepository
{
public class EfCoreAuthorRepository
: EfCoreRepository<BookStoreDbContext, Author, Guid>,
IAuthorRepository
public EfCoreAuthorRepository(
IDbContextProvider<BookStoreDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Author> FindByNameAsync(string name)
{
public EfCoreAuthorRepository(
IDbContextProvider<BookStoreDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Author> FindByNameAsync(string name)
{
var dbSet = await GetDbSetAsync();
return await dbSet.FirstOrDefaultAsync(author => author.Name == name);
}
public async Task<List<Author>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null)
{
var dbSet = await GetDbSetAsync();
return await dbSet
.WhereIf(
!filter.IsNullOrWhiteSpace(),
author => author.Name.Contains(filter)
)
.OrderBy(sorting)
.Skip(skipCount)
.Take(maxResultCount)
.ToListAsync();
}
var dbSet = await GetDbSetAsync();
return await dbSet.FirstOrDefaultAsync(author => author.Name == name);
}
public async Task<List<Author>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null)
{
var dbSet = await GetDbSetAsync();
return await dbSet
.WhereIf(
!filter.IsNullOrWhiteSpace(),
author => author.Name.Contains(filter)
)
.OrderBy(sorting)
.Skip(skipCount)
.Take(maxResultCount)
.ToListAsync();
}
}
````
@ -182,42 +181,41 @@ using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB;
namespace Acme.BookStore.Authors
namespace Acme.BookStore.Authors;
public class MongoDbAuthorRepository
: MongoDbRepository<BookStoreMongoDbContext, Author, Guid>,
IAuthorRepository
{
public class MongoDbAuthorRepository
: MongoDbRepository<BookStoreMongoDbContext, Author, Guid>,
IAuthorRepository
public MongoDbAuthorRepository(
IMongoDbContextProvider<BookStoreMongoDbContext> dbContextProvider
) : base(dbContextProvider)
{
public MongoDbAuthorRepository(
IMongoDbContextProvider<BookStoreMongoDbContext> dbContextProvider
) : base(dbContextProvider)
{
}
public async Task<Author> FindByNameAsync(string name)
{
var queryable = await GetMongoQueryableAsync();
return await queryable.FirstOrDefaultAsync(author => author.Name == name);
}
public async Task<List<Author>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null)
{
var queryable = await GetMongoQueryableAsync();
return await queryable
.WhereIf<Author, IMongoQueryable<Author>>(
!filter.IsNullOrWhiteSpace(),
author => author.Name.Contains(filter)
)
.OrderBy(sorting)
.As<IMongoQueryable<Author>>()
.Skip(skipCount)
.Take(maxResultCount)
.ToListAsync();
}
}
public async Task<Author> FindByNameAsync(string name)
{
var queryable = await GetMongoQueryableAsync();
return await queryable.FirstOrDefaultAsync(author => author.Name == name);
}
public async Task<List<Author>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null)
{
var queryable = await GetMongoQueryableAsync();
return await queryable
.WhereIf<Author, IMongoQueryable<Author>>(
!filter.IsNullOrWhiteSpace(),
author => author.Name.Contains(filter)
)
.OrderBy(sorting)
.As<IMongoQueryable<Author>>()
.Skip(skipCount)
.Take(maxResultCount)
.ToListAsync();
}
}
```

379
docs/en/Tutorials/Part-8.md

@ -50,20 +50,19 @@ using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.BookStore.Authors
namespace Acme.BookStore.Authors;
public interface IAuthorAppService : IApplicationService
{
public interface IAuthorAppService : IApplicationService
{
Task<AuthorDto> GetAsync(Guid id);
Task<AuthorDto> GetAsync(Guid id);
Task<PagedResultDto<AuthorDto>> GetListAsync(GetAuthorListDto input);
Task<PagedResultDto<AuthorDto>> GetListAsync(GetAuthorListDto input);
Task<AuthorDto> CreateAsync(CreateAuthorDto input);
Task<AuthorDto> CreateAsync(CreateAuthorDto input);
Task UpdateAsync(Guid id, UpdateAuthorDto input);
Task UpdateAsync(Guid id, UpdateAuthorDto input);
Task DeleteAsync(Guid id);
}
Task DeleteAsync(Guid id);
}
````
@ -80,16 +79,15 @@ This interface is using the DTOs defined below (create them for your project).
using System;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Authors
namespace Acme.BookStore.Authors;
public class AuthorDto : EntityDto<Guid>
{
public class AuthorDto : EntityDto<Guid>
{
public string Name { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
}
public string ShortBio { get; set; }
}
````
@ -100,12 +98,11 @@ namespace Acme.BookStore.Authors
````csharp
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Authors
namespace Acme.BookStore.Authors;
public class GetAuthorListDto : PagedAndSortedResultRequestDto
{
public class GetAuthorListDto : PagedAndSortedResultRequestDto
{
public string Filter { get; set; }
}
public string Filter { get; set; }
}
````
@ -120,19 +117,18 @@ namespace Acme.BookStore.Authors
using System;
using System.ComponentModel.DataAnnotations;
namespace Acme.BookStore.Authors
{
public class CreateAuthorDto
{
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
namespace Acme.BookStore.Authors;
[Required]
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
}
public class CreateAuthorDto
{
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
}
````
@ -144,19 +140,18 @@ Data annotation attributes can be used to validate the DTO. See the [validation
using System;
using System.ComponentModel.DataAnnotations;
namespace Acme.BookStore.Authors
{
public class UpdateAuthorDto
{
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
namespace Acme.BookStore.Authors;
[Required]
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
}
public class UpdateAuthorDto
{
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
}
````
@ -176,24 +171,23 @@ 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;
namespace Acme.BookStore.Authors;
public AuthorAppService(
IAuthorRepository authorRepository,
AuthorManager authorManager)
{
_authorRepository = authorRepository;
_authorManager = authorManager;
}
[Authorize(BookStorePermissions.Authors.Default)]
public class AuthorAppService : BookStoreAppService, IAuthorAppService
{
private readonly IAuthorRepository _authorRepository;
private readonly AuthorManager _authorManager;
//...SERVICE METHODS WILL COME HERE...
public AuthorAppService(
IAuthorRepository authorRepository,
AuthorManager authorManager)
{
_authorRepository = authorRepository;
_authorManager = authorManager;
}
//...SERVICE METHODS WILL COME HERE...
}
````
@ -327,28 +321,27 @@ You can't compile the code since it is expecting some constants declared in the
Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below:
````csharp
namespace Acme.BookStore.Permissions
namespace Acme.BookStore.Permissions;
public static class BookStorePermissions
{
public static class BookStorePermissions
{
public const string GroupName = "BookStore";
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";
}
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";
}
}
````
@ -400,72 +393,71 @@ using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
namespace Acme.BookStore;
public class BookStoreDataSeederContributor
: IDataSeedContributor, ITransientDependency
{
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)
{
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)
{
_bookRepository = bookRepository;
_authorRepository = authorRepository;
_authorManager = authorManager;
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
);
}
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
// 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)."
)
);
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'."
)
);
}
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'."
)
);
}
}
}
@ -491,73 +483,74 @@ using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Acme.BookStore.Authors
{ {{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
public class AuthorAppService_Tests : BookStoreApplicationTestBase
namespace Acme.BookStore.Authors;
{{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]
{{end}}
public class AuthorAppService_Tests : BookStoreApplicationTestBase
{
private readonly IAuthorAppService _authorAppService;
public AuthorAppService_Tests()
{
private readonly IAuthorAppService _authorAppService;
_authorAppService = GetRequiredService<IAuthorAppService>();
}
public AuthorAppService_Tests()
{
_authorAppService = GetRequiredService<IAuthorAppService>();
}
[Fact]
public async Task Should_Get_All_Authors_Without_Any_Filter()
{
var result = await _authorAppService.GetListAsync(new GetAuthorListDto());
[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");
}
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"});
[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");
}
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_Create_A_New_Author()
[Fact]
public async Task Should_Not_Allow_To_Create_Duplicate_Author()
{
await Assert.ThrowsAsync<AuthorAlreadyExistsException>(async () =>
{
var authorDto = await _authorAppService.CreateAsync(
await _authorAppService.CreateAsync(
new CreateAuthorDto
{
Name = "Edward Bellamy",
BirthDate = new DateTime(1850, 05, 22),
ShortBio = "Edward Bellamy was an American author..."
Name = "Douglas Adams",
BirthDate = DateTime.Now,
ShortBio = "..."
}
);
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...
});
}
//TODO: Test other methods...
}
````

416
docs/en/Tutorials/Part-9.md

@ -95,14 +95,13 @@ This is a simple page similar to the Books page we had created before. It import
````csharp
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Acme.BookStore.Web.Pages.Authors
namespace Acme.BookStore.Web.Pages.Authors;
public class IndexModel : PageModel
{
public class IndexModel : PageModel
public void OnGet()
{
public void OnGet()
{
}
}
}
````
@ -248,11 +247,11 @@ context.Menu.AddItem(
Run and login to the application. **You can not see the menu item since you don't have permission yet.** Go to the `Identity/Roles` page, click to the *Actions* button and select the *Permissions* action for the **admin role**:
![bookstore-author-permissions](images/bookstore-author-permissions-2.png)
![bookstore-author-permissions](images/bookstore-author-permissions-3.png)
As you see, the admin role has no *Author Management* permissions yet. Click to the checkboxes and save the modal to grant the necessary permissions. You will see the *Authors* menu item under the *Book Store* in the main menu, after **refreshing the page**:
![bookstore-authors-page](images/bookstore-authors-page-2.png)
![bookstore-authors-page](images/bookstore-authors-page-3.png)
The page is fully working except *New author* and *Actions/Edit* since we haven't implemented them yet.
@ -302,45 +301,44 @@ using Acme.BookStore.Authors;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace Acme.BookStore.Web.Pages.Authors
namespace Acme.BookStore.Web.Pages.Authors;
public class CreateModalModel : BookStorePageModel
{
public class CreateModalModel : BookStorePageModel
{
[BindProperty]
public CreateAuthorViewModel Author { get; set; }
[BindProperty]
public CreateAuthorViewModel Author { get; set; }
private readonly IAuthorAppService _authorAppService;
private readonly IAuthorAppService _authorAppService;
public CreateModalModel(IAuthorAppService authorAppService)
{
_authorAppService = authorAppService;
}
public CreateModalModel(IAuthorAppService authorAppService)
{
_authorAppService = authorAppService;
}
public void OnGet()
{
Author = new CreateAuthorViewModel();
}
public void OnGet()
{
Author = new CreateAuthorViewModel();
}
public async Task<IActionResult> OnPostAsync()
{
var dto = ObjectMapper.Map<CreateAuthorViewModel, CreateAuthorDto>(Author);
await _authorAppService.CreateAsync(dto);
return NoContent();
}
public async Task<IActionResult> OnPostAsync()
{
var dto = ObjectMapper.Map<CreateAuthorViewModel, CreateAuthorDto>(Author);
await _authorAppService.CreateAsync(dto);
return NoContent();
}
public class CreateAuthorViewModel
{
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
public class CreateAuthorViewModel
{
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime BirthDate { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime BirthDate { get; set; }
[TextArea]
public string ShortBio { get; set; }
}
[TextArea]
public string ShortBio { get; set; }
}
}
```
@ -359,18 +357,17 @@ using Acme.BookStore.Authors; // ADDED NAMESPACE IMPORT
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore.Web
namespace Acme.BookStore.Web;
public class BookStoreWebAutoMapperProfile : Profile
{
public class BookStoreWebAutoMapperProfile : Profile
public BookStoreWebAutoMapperProfile()
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
CreateMap<BookDto, CreateUpdateBookDto>();
// ADD a NEW MAPPING
CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
CreateAuthorDto>();
}
// ADD a NEW MAPPING
CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
CreateAuthorDto>();
}
}
````
@ -420,52 +417,51 @@ using Acme.BookStore.Authors;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace Acme.BookStore.Web.Pages.Authors
namespace Acme.BookStore.Web.Pages.Authors;
public class EditModalModel : BookStorePageModel
{
public class EditModalModel : BookStorePageModel
{
[BindProperty]
public EditAuthorViewModel Author { get; set; }
[BindProperty]
public EditAuthorViewModel Author { get; set; }
private readonly IAuthorAppService _authorAppService;
private readonly IAuthorAppService _authorAppService;
public EditModalModel(IAuthorAppService authorAppService)
{
_authorAppService = authorAppService;
}
public EditModalModel(IAuthorAppService authorAppService)
{
_authorAppService = authorAppService;
}
public async Task OnGetAsync(Guid id)
{
var authorDto = await _authorAppService.GetAsync(id);
Author = ObjectMapper.Map<AuthorDto, EditAuthorViewModel>(authorDto);
}
public async Task OnGetAsync(Guid id)
{
var authorDto = await _authorAppService.GetAsync(id);
Author = ObjectMapper.Map<AuthorDto, EditAuthorViewModel>(authorDto);
}
public async Task<IActionResult> OnPostAsync()
{
await _authorAppService.UpdateAsync(
Author.Id,
ObjectMapper.Map<EditAuthorViewModel, UpdateAuthorDto>(Author)
);
public async Task<IActionResult> OnPostAsync()
{
await _authorAppService.UpdateAsync(
Author.Id,
ObjectMapper.Map<EditAuthorViewModel, UpdateAuthorDto>(Author)
);
return NoContent();
}
return NoContent();
}
public class EditAuthorViewModel
{
[HiddenInput]
public Guid Id { get; set; }
public class EditAuthorViewModel
{
[HiddenInput]
public Guid Id { get; set; }
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime BirthDate { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime BirthDate { get; set; }
[TextArea]
public string ShortBio { get; set; }
}
[TextArea]
public string ShortBio { get; set; }
}
}
```
@ -482,22 +478,21 @@ using Acme.BookStore.Authors;
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore.Web
namespace Acme.BookStore.Web;
public class BookStoreWebAutoMapperProfile : Profile
{
public class BookStoreWebAutoMapperProfile : Profile
public BookStoreWebAutoMapperProfile()
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
CreateMap<BookDto, CreateUpdateBookDto>();
CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
CreateAuthorDto>();
CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
CreateAuthorDto>();
// ADD THESE NEW MAPPINGS
CreateMap<AuthorDto, Pages.Authors.EditModalModel.EditAuthorViewModel>();
CreateMap<Pages.Authors.EditModalModel.EditAuthorViewModel,
UpdateAuthorDto>();
}
// ADD THESE NEW MAPPINGS
CreateMap<AuthorDto, Pages.Authors.EditModalModel.EditAuthorViewModel>();
CreateMap<Pages.Authors.EditModalModel.EditAuthorViewModel,
UpdateAuthorDto>();
}
}
```
@ -833,11 +828,11 @@ This page uses some localization keys we need to declare. Open the `en.json` fil
Run and login to the application. **You can not see the menu item since you don't have permission yet.** Go to the `identity/roles` page, click to the *Actions* button and select the *Permissions* action for the **admin role**:
![bookstore-author-permissions](images/bookstore-author-permissions.png)
![bookstore-author-permissions](images/bookstore-author-permissions-2.png)
As you see, the admin role has no *Author Management* permissions yet. Click to the checkboxes and save the modal to grant the necessary permissions. You will see the *Authors* menu item under the *Book Store* in the main menu, after **refreshing the page**:
![bookstore-authors-page](images/bookstore-angular-authors-page.png)
![bookstore-authors-page](images/bookstore-angular-authors-page-2.png)
That's all! This is a fully working CRUD page, you can create, edit and delete authors.
@ -863,12 +858,11 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the `Acme.BookStor
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
<Card>
<CardHeader>
<Row>
<Column ColumnSize="ColumnSize.Is6">
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Authors"]</h2>
</Column>
<Column ColumnSize="ColumnSize.Is6">
<Paragraph Alignment="TextAlignment.Right">
<Column ColumnSize="ColumnSize.IsAuto">
@if (CanCreateAuthor)
{
<Button Color="Color.Primary"
@ -876,7 +870,6 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the `Acme.BookStor
@L["NewAuthor"]
</Button>
}
</Paragraph>
</Column>
</Row>
</CardHeader>
@ -1054,142 +1047,141 @@ using Blazorise.DataGrid;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Blazor.Pages
namespace Acme.BookStore.Blazor.Pages;
public partial class Authors
{
public partial class Authors
{
private IReadOnlyList<AuthorDto> AuthorList { get; set; }
private IReadOnlyList<AuthorDto> AuthorList { get; set; }
private int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount;
private int CurrentPage { get; set; }
private string CurrentSorting { get; set; }
private int TotalCount { get; set; }
private int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount;
private int CurrentPage { get; set; }
private string CurrentSorting { get; set; }
private int TotalCount { get; set; }
private bool CanCreateAuthor { get; set; }
private bool CanEditAuthor { get; set; }
private bool CanDeleteAuthor { get; set; }
private bool CanCreateAuthor { get; set; }
private bool CanEditAuthor { get; set; }
private bool CanDeleteAuthor { get; set; }
private CreateAuthorDto NewAuthor { get; set; }
private CreateAuthorDto NewAuthor { get; set; }
private Guid EditingAuthorId { get; set; }
private UpdateAuthorDto EditingAuthor { get; set; }
private Guid EditingAuthorId { get; set; }
private UpdateAuthorDto EditingAuthor { get; set; }
private Modal CreateAuthorModal { get; set; }
private Modal EditAuthorModal { get; set; }
private Modal CreateAuthorModal { get; set; }
private Modal EditAuthorModal { get; set; }
private Validations CreateValidationsRef;
private Validations EditValidationsRef;
public Authors()
{
NewAuthor = new CreateAuthorDto();
EditingAuthor = new UpdateAuthorDto();
}
private Validations CreateValidationsRef;
private Validations EditValidationsRef;
public Authors()
{
NewAuthor = new CreateAuthorDto();
EditingAuthor = new UpdateAuthorDto();
}
protected override async Task OnInitializedAsync()
{
await SetPermissionsAsync();
await GetAuthorsAsync();
}
protected override async Task OnInitializedAsync()
{
await SetPermissionsAsync();
await GetAuthorsAsync();
}
private async Task SetPermissionsAsync()
{
CanCreateAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Create);
private async Task SetPermissionsAsync()
{
CanCreateAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Create);
CanEditAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Edit);
CanEditAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Edit);
CanDeleteAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Delete);
}
CanDeleteAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Delete);
}
private async Task GetAuthorsAsync()
{
var result = await AuthorAppService.GetListAsync(
new GetAuthorListDto
{
MaxResultCount = PageSize,
SkipCount = CurrentPage * PageSize,
Sorting = CurrentSorting
}
);
private async Task GetAuthorsAsync()
{
var result = await AuthorAppService.GetListAsync(
new GetAuthorListDto
{
MaxResultCount = PageSize,
SkipCount = CurrentPage * PageSize,
Sorting = CurrentSorting
}
);
AuthorList = result.Items;
TotalCount = (int)result.TotalCount;
}
AuthorList = result.Items;
TotalCount = (int)result.TotalCount;
}
private async Task OnDataGridReadAsync(DataGridReadDataEventArgs<AuthorDto> e)
{
CurrentSorting = e.Columns
.Where(c => c.SortDirection != SortDirection.Default)
.Select(c => c.Field + (c.SortDirection == SortDirection.Descending ? " DESC" : ""))
.JoinAsString(",");
CurrentPage = e.Page - 1;
private async Task OnDataGridReadAsync(DataGridReadDataEventArgs<AuthorDto> e)
{
CurrentSorting = e.Columns
.Where(c => c.SortDirection != SortDirection.Default)
.Select(c => c.Field + (c.SortDirection == SortDirection.Descending ? " DESC" : ""))
.JoinAsString(",");
CurrentPage = e.Page - 1;
await GetAuthorsAsync();
await GetAuthorsAsync();
await InvokeAsync(StateHasChanged);
}
await InvokeAsync(StateHasChanged);
}
private void OpenCreateAuthorModal()
{
CreateValidationsRef.ClearAll();
NewAuthor = new CreateAuthorDto();
CreateAuthorModal.Show();
}
private void OpenCreateAuthorModal()
{
CreateValidationsRef.ClearAll();
NewAuthor = new CreateAuthorDto();
CreateAuthorModal.Show();
}
private void CloseCreateAuthorModal()
{
CreateAuthorModal.Hide();
}
private void CloseCreateAuthorModal()
{
CreateAuthorModal.Hide();
}
private void OpenEditAuthorModal(AuthorDto author)
{
EditValidationsRef.ClearAll();
EditingAuthorId = author.Id;
EditingAuthor = ObjectMapper.Map<AuthorDto, UpdateAuthorDto>(author);
EditAuthorModal.Show();
}
private void OpenEditAuthorModal(AuthorDto author)
{
EditValidationsRef.ClearAll();
EditingAuthorId = author.Id;
EditingAuthor = ObjectMapper.Map<AuthorDto, UpdateAuthorDto>(author);
EditAuthorModal.Show();
}
private async Task DeleteAuthorAsync(AuthorDto author)
private async Task DeleteAuthorAsync(AuthorDto author)
{
var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name];
if (!await Message.Confirm(confirmMessage))
{
var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name];
if (!await Message.Confirm(confirmMessage))
{
return;
}
await AuthorAppService.DeleteAsync(author.Id);
await GetAuthorsAsync();
return;
}
private void CloseEditAuthorModal()
{
EditAuthorModal.Hide();
}
await AuthorAppService.DeleteAsync(author.Id);
await GetAuthorsAsync();
}
private void CloseEditAuthorModal()
{
EditAuthorModal.Hide();
}
private async Task CreateAuthorAsync()
private async Task CreateAuthorAsync()
{
if (await CreateValidationsRef.ValidateAll())
{
if (await CreateValidationsRef.ValidateAll())
{
await AuthorAppService.CreateAsync(NewAuthor);
await GetAuthorsAsync();
CreateAuthorModal.Hide();
}
await AuthorAppService.CreateAsync(NewAuthor);
await GetAuthorsAsync();
CreateAuthorModal.Hide();
}
}
private async Task UpdateAuthorAsync()
private async Task UpdateAuthorAsync()
{
if (await EditValidationsRef.ValidateAll())
{
if (await EditValidationsRef.ValidateAll())
{
await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
await GetAuthorsAsync();
EditAuthorModal.Hide();
}
await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
await GetAuthorsAsync();
EditAuthorModal.Hide();
}
}
}
@ -1240,11 +1232,11 @@ We should complete the localizations we've used above. Open the `en.json` file u
Run and login to the application. **If you don't see the Authors menu item under the Book Store menu, that means you don't have the permission yet.** Go to the `identity/roles` page, click to the *Actions* button and select the *Permissions* action for the **admin role**:
![bookstore-author-permissions](images/bookstore-author-permissions.png)
![bookstore-author-permissions](images/bookstore-author-permissions-2.png)
As you see, the admin role has no *Author Management* permissions yet. Click to the checkboxes and save the modal to grant the necessary permissions. You will see the *Authors* menu item under the *Book Store* in the main menu, after **refreshing the page**:
![bookstore-authors-page](images/bookstore-authors-blazor-ui.png)
![bookstore-authors-page](images/bookstore-authors-page-3.png)
That's all! This is a fully working CRUD page, you can create, edit and delete the authors.

BIN
docs/en/Tutorials/images/blazor-add-book-button-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/Tutorials/images/blazor-bookstore-book-list-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
docs/en/Tutorials/images/blazor-delete-book-action-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
docs/en/Tutorials/images/blazor-edit-book-action-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/en/Tutorials/images/blazor-edit-book-modal-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/en/Tutorials/images/blazor-new-book-modal-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/en/Tutorials/images/book-create-modal-with-author-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
docs/en/Tutorials/images/bookstore-actions-buttons-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
docs/en/Tutorials/images/bookstore-angular-author-selection-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Tutorials/images/bookstore-angular-authors-page-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
docs/en/Tutorials/images/bookstore-author-permissions-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/en/Tutorials/images/bookstore-authors-page-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Tutorials/images/bookstore-book-list-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
docs/en/Tutorials/images/bookstore-books-with-authorname-angular-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
docs/en/Tutorials/images/bookstore-confirmation-popup-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/Tutorials/images/bookstore-empty-new-book-modal-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/en/Tutorials/images/bookstore-final-actions-dropdown-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/Tutorials/images/bookstore-new-book-form-v3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs

@ -507,7 +507,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
private static bool IsAppServiceInterface(string typeName)
{
return typeName.StartsWith("I") && ServicePostfixes.Any(typeName.EndsWith);
return typeName.StartsWith("I") && ServicePostfixes.Any(typeName.Contains);
}
private static string GetTypeNamespace(string typeFullName)
@ -546,7 +546,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
for (var x = 0; x < s2.Length; x++)
{
type.Append(s2[x].Split(".").Last());
if (x < s1.Length - 1)
if (x < s2.Length - 1)
{
type.Append(", ");
}

3
npm/ng-packs/package.json

@ -64,6 +64,7 @@
"@abp/ng.account": "~7.0.0",
"@abp/ng.account.core": "~7.0.0",
"@abp/ng.core": "~7.0.0",
"@abp/ng.oauth":"~7.0.0",
"@abp/ng.feature-management": "~7.0.0",
"@abp/ng.identity": "~7.0.0",
"@abp/ng.permission-management": "~7.0.0",
@ -102,7 +103,7 @@
"jest": "28.1.1",
"jest-canvas-mock": "^2.3.1",
"jest-environment-jsdom": "28.1.1",
"jest-preset-angular": "12.2.2",
"jest-preset-angular": "^12.2.2",
"jsonc-parser": "^2.3.0",
"just-clone": "^6.1.1",
"just-compare": "^1.4.0",

Loading…
Cancel
Save