Tổng quan về ABP FRAMEWORK. Cách build 1 webapplication dựa trên ABP FRAMEWORK

12/21/2020 1:26 PM | Lập trình

ABP Framework sử dụng ngôn ngữ asp.net core và là một open source sử dụng các công nghệ mới nhất để có thể build các web app trên các môi trường khác nhau. ABP cũng có thể tích hợp với nhiều hệ quản trị cơ sở dữ liệu như Sqlserver, MongoDB, Dapper. Frontend có thể tích hợp như Angular, Blazor, Razor Page. Và cơ chế framework tách ra theo từng module dễ quản lý, maintain cho các dự án vừa và lớn.

Tóm tắt về sự liên hệ các tầng project trong ABP Framework

   Mình sẽ tóm tắt sơ lược các tầng để cho các bạn đọc chi tiết bên dưới đỡ bị loạn.

  • Controller: Project HttpApi gọi xuống interface appservice
  • App Service: Interface at project .Application.Contracts gọi hàm trong project Application. Trong hàm gọi interface repository. 
  • Repository: Interface repository ở project .Domain gọi hàm trong project dbcontext (như ở project ví dụ dưới là: EntityFrameworkCore).

1. Giới thiệu

  • ABP là 1 frame work open source có thể xây dựng website hoặc là nền tảng để build các application trên thiết bị di động.
  • ABP có thể tích hợp với database Sql Server hoặc MongoDB
  • Về UI ABP có thể tích hợp với Angular, MVC, Razor Page, Blazor giúp developer có thể linh động tùy chọn ngôn ngữ để phát triển web, application.
  • ABP có thể tích hợp viết React Native trên ứng dụng di động.

Bài viết liên quan: Microservice là gì? Ưu nhược điểm khi xài Microservice.

2. Cách tạo 1 Project ABP FRAMEWORK

Yêu cầu trước

Các công cụ sau sẽ được cài đặt trên máy phát triển của bạn:

1 Bạn có thể sử dụng một trình soạn thảo khác thay vì Visual Studio miễn là nó hỗ trợ .NET Core và ASP.NET Core. 

2 Yarn v2 hoạt động khác nhau và không được hỗ trợ. 

Cài đặt ABP CLI

ABP CLI là một giao diện dòng lệnh được sử dụng để tự động hóa một số tác vụ phổ biến cho các giải pháp dựa trên ABP. Trước tiên, bạn cần cài đặt ABP CLI bằng lệnh sau:

dotnet tool install -g Volo.Abp.Cli
Bash

Nếu bạn đã cài đặt, bạn có thể cập nhật nó bằng lệnh sau:

dotnet tool update -g Volo.Abp.Cli

3. Cách tạo 1 Webapplication

Tạo một dự án mới

Sử dụng newlệnh của ABP CLI để tạo một dự án mới:

abp new Acme.BookStore -u angular
Bash

Bạn có thể sử dụng các cấp độ không gian tên khác nhau; ví dụ: BookStore, Acme.BookStore hoặc Acme.Retail.BookStore.

Tạo cơ sở dữ liệu

Chuỗi kết nối

Kiểm tra chuỗi kết nối trong appsettings.jsontệp trong .HttpApi.Hostdự án

"ConnectionStrings": {
  "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
JSON

Giải pháp được định cấu hình để sử dụng Entity Framework Core với MS SQL Server theo mặc định. EF Core hỗ trợ các nhà cung cấp cơ sở dữ liệu khác nhau , vì vậy bạn có thể sử dụng bất kỳ DBMS nào được hỗ trợ. Xem tài liệu tích hợp Khung thực thể để tìm hiểu cách chuyển sang một DBMS khác .

Áp dụng sự di chuyển

Giải pháp sử dụng Entity Framework Core Code Core Di chuyển đầu tiên . Vì vậy, bạn cần áp dụng chuyển đổi để tạo cơ sở dữ liệu. Có hai cách để áp dụng di chuyển cơ sở dữ liệu.

Áp dụng Di chuyển bằng DbMigrator

Giải pháp đi kèm với một .DbMigratorứng dụng bảng điều khiển áp dụng di chuyển và cũng tạo ra dữ liệu ban đầu . Nó hữu ích cho sự phát triển cũng như môi trường sản xuất .

.DbMigratordự án có riêng của nó appsettings.jsonVì vậy, nếu bạn đã thay đổi chuỗi kết nối ở trên, bạn cũng nên thay đổi chuỗi này.

Nhấp chuột phải vào .DbMigratordự án và chọn Đặt làm Dự án StartUp

 

Nhấn F5 (hoặc Ctrl + F5) để chạy ứng dụng. Nó sẽ có đầu ra như hình dưới đây:

 

 

Dữ liệu hạt giống ban đầu tạo ra adminngười dùng trong cơ sở dữ liệu (với mật khẩu là 1q2w3E*), sau đó được sử dụng để đăng nhập vào ứng dụng. Vì vậy, bạn cần sử dụng .DbMigratorít nhất một lần cho một cơ sở dữ liệu mới.

 

Sử dụng lệnh cập nhật cơ sở dữ liệu lõi EF

Ef Core có Update-Databaselệnh tạo cơ sở dữ liệu nếu cần và áp dụng các di chuyển đang chờ xử lý.

Nhấp chuột phải vào .HttpApi.Hostdự án và chọn Đặt làm Dự án StartUp :

 

Mở Bảng điều khiển Trình quản lý Gói , chọn .EntityFrameworkCore.DbMigrationsdự án làm Dự án Mặc định và chạy Update-Databaselệnh:

 

 

Thao tác này sẽ tạo cơ sở dữ liệu mới dựa trên chuỗi kết nối đã định cấu hình.

 

Sử dụng .DbMigratorcông cụ là cách được đề xuất , vì nó cũng tạo ra dữ liệu ban đầu để có thể chạy đúng ứng dụng web.

Nếu bạn chỉ sử dụng Update-Databaselệnh, bạn sẽ có một cơ sở dữ liệu trống, vì vậy bạn không thể đăng nhập vào ứng dụng vì không có người dùng quản trị ban đầu trong cơ sở dữ liệu. Bạn có thể sử dụng Update-Databaselệnh trong thời gian phát triển khi bạn không cần phải seed cơ sở dữ liệu. Tuy nhiên, việc sử .DbMigratordụng ứng dụng này dễ dàng hơn và bạn luôn có thể sử dụng nó để di chuyển lược đồ và khởi tạo cơ sở dữ liệu.

Chạy ứng dụng

Chạy Máy chủ API HTTP (Phía máy chủ)

Đảm bảo rằng .HttpApi.Hostdự án là dự án khởi động và chạy ứng dụng sẽ mở giao diện người dùng Swagger:

Sử dụng Ctrl + F5 trong Visual Studio (thay vì F5) để chạy ứng dụng mà không cần gỡ lỗi. Nếu bạn không có mục đích gỡ lỗi, việc này sẽ nhanh hơn.

 

Bạn có thể xem các API ứng dụng và kiểm tra chúng tại đây. Nhận thêm thông tin về giao diện người dùng Swagger.

 

Chạy ứng dụng Angular (Phía máy khách)

Đi tới angularthư mục, mở một thiết bị đầu cuối dòng lệnh, nhập yarnlệnh (chúng tôi đề xuất với người quản lý gói sợi trong khi npm installcũng sẽ hoạt động)

yarn
Bash

Khi tất cả các mô-đun nút được tải, hãy thực hiện lệnh yarn start(hoặc npm start):

yarn start
Bash

Có thể mất nhiều thời gian hơn cho lần xây dựng đầu tiên. Sau khi hoàn tất, nó sẽ mở giao diện người dùng Angular trong trình duyệt mặc định của bạn với địa chỉ localhost: 4200 .

 

Nhập admin làm tên người dùng và 1q2w3E * làm mật khẩu để đăng nhập vào ứng dụng. Ứng dụng đang hoạt động. Bạn có thể bắt đầu phát triển ứng dụng của mình dựa trên mẫu khởi động này.

 

Tạo thực thể sách

Lớp miền trong mẫu khởi động được tách thành hai dự án:

  • Acme.BookStore.Domainchứa các thực thể , dịch vụ miền và các đối tượng miền cốt lõi khác của bạn.
  • Acme.BookStore.Domain.Sharedchứa constantsenumshoặc các đối tượng miền khác liên quan đến những người có thể được chia sẻ với khách hàng.

Vì vậy, hãy xác định các thực thể của bạn trong lớp miền ( Acme.BookStore.Domaindự án) của giải pháp.

Thực thể chính của ứng dụng là BookTạo một Booksthư mục (không gian tên) trong Acme.BookStore.Domaindự án và thêm một Booklớp bên trong nó:

using System;
using Volo.Abp.Domain.Entities.Auditing;

namespace Acme.BookStore.Books
{
    public class Book : AuditedAggregateRoot<Guid>
    {
        public string Name { get; set; }

        public BookType Type { get; set; }

        public DateTime PublishDate { get; set; }

        public float Price { get; set; }
    }
}
C #
  • ABP Framework có hai lớp cơ sở cơ bản cho các thực thể: AggregateRootvà EntityAggregate Root là một khái niệm Thiết kế theo hướng miền có thể được coi là một thực thể gốc được truy vấn và làm việc trực tiếp (xem tài liệu về các thực thể để biết thêm).
  • Bookkế thừa thực thể từ AuditedAggregateRootđó cho biết thêm một số cơ sở kiểm toán tài sản (như CreationTimeCreatorIdLastModificationTime...) trên đầu trang của AggregateRootlớp. ABP tự động quản lý các thuộc tính này cho bạn.
  • Guidlà loại khóa chính của Bookthực thể.

Hướng dẫn này để lại các thuộc tính thực thể với public get / set vì mục đích đơn giản. Xem tài liệu thực thể nếu bạn tìm hiểu thêm về các phương pháp hay nhất về DDD.

BookType Enum

Thực Bookthể sử dụng BookTypeenum. Tạo một Booksthư mục (không gian tên) trong Acme.BookStore.Domain.Shareddự án và thêm BookTypevào bên trong dự án :

namespace Acme.BookStore.Books
{
    public enum BookType
    {
        Undefined,
        Adventure,
        Biography,
        Dystopia,
        Fantastic,
        Horror,
        Science,
        ScienceFiction,
        Poetry
    }
}
C #

Cấu trúc thư mục / tệp cuối cùng sẽ như hình dưới đây:

 

Thêm Thực thể Sách vào DbContext

 

EF Core yêu cầu liên kết các thực thể với của bạn DbContextCách dễ nhất để làm điều này là thêm một thuộc DbSettính vào BookStoreDbContextlớp trong Acme.BookStore.EntityFrameworkCoredự án, như được hiển thị bên dưới:

public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
    public DbSet<Book> Books { get; set; }
    //...
}
C #

Ánh xạ Thực thể Sách với Bảng Cơ sở dữ liệu

Mở BookStoreDbContextModelCreatingExtensions.cstệp trong Acme.BookStore.EntityFrameworkCoredự án và thêm mã ánh xạ cho Bookthực thể. Lớp cuối cùng phải như sau:

using Acme.BookStore.Books;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;

namespace Acme.BookStore.EntityFrameworkCore
{
    public static class BookStoreDbContextModelCreatingExtensions
    {
        public static void ConfigureBookStore(this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));

            /* Configure your own tables/entities inside here */

            builder.Entity<Book>(b =>
            {
                b.ToTable(BookStoreConsts.DbTablePrefix + "Books",
                          BookStoreConsts.DbSchema);
                b.ConfigureByConvention(); //auto configure for the base class props
                b.Property(x => x.Name).IsRequired().HasMaxLength(128);
            });
        }
    }
}
C #
  • BookStoreConstscó giá trị không đổi cho lược đồ và tiền tố bảng cho bảng của bạn. Bạn không cần phải sử dụng nó, nhưng được đề xuất để kiểm soát các tiền tố bảng ở một điểm duy nhất.
  • ConfigureByConvention()phương thức cấu hình / ánh xạ các thuộc tính kế thừa một cách duyên dáng. Luôn sử dụng nó cho tất cả các thực thể của bạn.

Thêm di chuyển cơ sở dữ liệu

Mẫu khởi động sử dụng EF Core Code First Migrations để tạo và duy trì lược đồ cơ sở dữ liệu. Mở Bảng điều khiển Trình quản lý Gói (PMC) trong menu Công cụ> Trình quản lý Gói NuGet .

 

Chọn Acme.BookStore.EntityFrameworkCore.DbMigrationslàm dự án mặc định và thực hiện lệnh sau:

 

Add-Migration "Created_Book_Entity"
Bash

 

Điều này sẽ tạo ra một lớp di chuyển mới bên trong Migrationsthư mục của Acme.BookStore.EntityFrameworkCore.DbMigrationsdự án.

 

Trước khi cập nhật cơ sở dữ liệu, hãy đọc phần bên dưới để tìm hiểu cách chuyển một số dữ liệu ban đầu vào cơ sở dữ liệu.

Nếu bạn đang sử dụng một IDE khác với Visual Studio, bạn có thể sử dụng dotnet-efcông cụ như được ghi ở đây .

Thêm dữ liệu hạt giống mẫu

Thật tốt khi có một số dữ liệu ban đầu trong cơ sở dữ liệu trước khi chạy ứng dụng. Phần này giới thiệu hệ thống Gieo dữ liệu của khuôn khổ ABP. Bạn có thể bỏ qua phần này nếu bạn không muốn tạo dữ liệu hạt giống, nhưng bạn nên làm theo để tìm hiểu tính năng ABP Framework hữu ích này.

Tạo một lớp dẫn xuất từ IDataSeedContributortrong *.Domaindự án bằng cách sao chép mã sau:

using System;
using System.Threading.Tasks;
using Acme.BookStore.Books;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;

namespace Acme.BookStore
{
    public class BookStoreDataSeederContributor
        : IDataSeedContributor, ITransientDependency
    {
        private readonly IRepository<Book, Guid> _bookRepository;

        public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository)
        {
            _bookRepository = bookRepository;
        }

        public async Task SeedAsync(DataSeedContext context)
        {
            if (await _bookRepository.GetCountAsync() <= 0)
            {
                await _bookRepository.InsertAsync(
                    new Book
                    {
                        Name = "1984",
                        Type = BookType.Dystopia,
                        PublishDate = new DateTime(1949, 6, 8),
                        Price = 19.84f
                    },
                    autoSave: true
                );

                await _bookRepository.InsertAsync(
                    new Book
                    {
                        Name = "The Hitchhiker's Guide to the Galaxy",
                        Type = BookType.ScienceFiction,
                        PublishDate = new DateTime(1995, 9, 27),
                        Price = 42.0f
                    },
                    autoSave: true
                );
            }
        }
    }
}
C #
  • Mã này chỉ đơn giản sử dụng IRepository<Book, Guid>kho lưu trữ mặc định ) để chèn hai cuốn sách vào cơ sở dữ liệu, nếu hiện tại không có cuốn sách nào trong cơ sở dữ liệu.

Cập nhật cơ sở dữ liệu

Chạy Acme.BookStore.DbMigratorứng dụng để cập nhật cơ sở dữ liệu:

 

.DbMigratorlà một ứng dụng bảng điều khiển có thể được chạy để di chuyển lược đồ cơ sở dữ liệu và bắt nguồn dữ liệu trên môi trường phát triển và sản xuất .

 

Tạo dịch vụ ứng dụng

Lớp ứng dụng được tách thành hai dự án:

  • Acme.BookStore.Application.Contractschứa DTO của bạn và các giao diện dịch vụ ứng dụng .
  • Acme.BookStore.Application chứa các triển khai các dịch vụ ứng dụng của bạn.

Trong phần này, bạn sẽ tạo một dịch vụ ứng dụng để lấy, tạo, cập nhật và xóa sách bằng cách sử dụng CrudAppServicelớp cơ sở của Khung ABP.

BookDto

CrudAppServicelớp cơ sở yêu cầu xác định DTO cơ bản cho thực thể. Tạo một Booksthư mục (không gian tên) trong Acme.BookStore.Application.Contractsdự án và thêm một BookDtolớp bên trong nó:

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

namespace Acme.BookStore.Books
{
    public class BookDto : AuditedEntityDto<Guid>
    {
        public string Name { get; set; }

        public BookType Type { get; set; }

        public DateTime PublishDate { get; set; }

        public float Price { get; set; }
    }
}
C #
  • Các lớp DTO được sử dụng để truyền dữ liệu giữa lớp trình bày và lớp ứng dụng . Xem tài liệu Đối tượng truyền dữ liệu để biết thêm chi tiết.
  • BookDto được sử dụng để chuyển dữ liệu sách sang lớp trình bày nhằm hiển thị thông tin sách trên giao diện người dùng.
  • BookDtođược bắt nguồn từ AuditedEntityDto<Guid>có các thuộc tính kiểm toán giống như Bookđơn vị được xác định ở trên.

Nó sẽ cần thiết để ánh xạ Bookcác thực thể thành BookDtocác đối tượng trong khi trả sách về lớp trình bày. Thư viện AutoMapper có thể tự động chuyển đổi này khi bạn xác định ánh xạ thích hợp. Mẫu khởi động đi kèm với AutoMapper được định cấu hình trước. Vì vậy, bạn chỉ có thể xác định ánh xạ trong BookStoreApplicationAutoMapperProfilelớp trong Acme.BookStore.Applicationdự án:

using Acme.BookStore.Books;
using AutoMapper;

namespace Acme.BookStore
{
    public class BookStoreApplicationAutoMapperProfile : Profile
    {
        public BookStoreApplicationAutoMapperProfile()
        {
            CreateMap<Book, BookDto>();
        }
    }
}
C #

Xem tài liệu ánh xạ đối tượng với đối tượng để biết chi tiết.

CreateUpdateBookDto

Tạo một CreateUpdateBookDtolớp trong Booksthư mục (không gian tên) của Acme.BookStore.Application.Contractsdự án:

using System;
using System.ComponentModel.DataAnnotations;

namespace Acme.BookStore.Books
{
    public class CreateUpdateBookDto
    {
        [Required]
        [StringLength(128)]
        public string Name { get; set; }

        [Required]
        public BookType Type { get; set; } = BookType.Undefined;

        [Required]
        [DataType(DataType.Date)]
        public DateTime PublishDate { get; set; } = DateTime.Now;

        [Required]
        public float Price { get; set; }
    }
}
C #
  • Đây DTOlớp được sử dụng để có được thông tin sách từ người sử dụng giao diện trong khi tạo hoặc cập nhật một cuốn sách.
  • Nó xác định các thuộc tính chú thích dữ liệu (như [Required]) để xác định tính hợp lệ cho các thuộc tính. DTOs được tự động xác nhận bởi khung ABP.

Giống như đã thực hiện BookDtoở trên, chúng ta nên xác định ánh xạ từ CreateUpdateBookDtođối tượng đến Bookthực thể. Lớp cuối cùng sẽ như hình dưới đây:

using Acme.BookStore.Books;
using AutoMapper;

namespace Acme.BookStore
{
    public class BookStoreApplicationAutoMapperProfile : Profile
    {
        public BookStoreApplicationAutoMapperProfile()
        {
            CreateMap<Book, BookDto>();
            CreateMap<CreateUpdateBookDto, Book>();
        }
    }
}
C #

IBookAppService

Bước tiếp theo là xác định giao diện cho dịch vụ ứng dụng. Tạo IBookAppServicegiao diện trong Booksthư mục (không gian tên) của Acme.BookStore.Application.Contractsdự án:

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

namespace Acme.BookStore.Books
{
    public interface IBookAppService :
        ICrudAppService< //Defines CRUD methods
            BookDto, //Used to show books
            Guid, //Primary key of the book entity
            PagedAndSortedResultRequestDto, //Used for paging/sorting
            CreateUpdateBookDto> //Used to create/update a book
    {

    }
}
C #
  • Khuôn khổ không yêu cầu xác định giao diện cho các dịch vụ ứng dụng Tuy nhiên, nó được đề xuất như một phương pháp hay nhất.
  • ICrudAppServiceđịnh nghĩa chung CRUD phương pháp: GetAsyncGetListAsyncCreateAsyncUpdateAsyncvà DeleteAsyncNó không bắt buộc phải mở rộng nó. Thay vào đó, bạn có thể kế thừa từ IApplicationServicegiao diện trống và xác định các phương thức của riêng bạn theo cách thủ công (sẽ được thực hiện cho các tác giả trong các phần tiếp theo).
  • Có một số biến thể trong ICrudAppServiceđó bạn có thể sử dụng các DTO riêng biệt cho từng phương pháp (như sử dụng các DTO khác nhau để tạo và cập nhật).

BookAppService

Đã đến lúc thực hiện IBookAppServicegiao diện. Tạo một lớp mới, được đặt tên BookAppServicetrong Bookskhông gian tên (thư mục) của Acme.BookStore.Applicationdự án:

using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

namespace Acme.BookStore.Books
{
    public class BookAppService :
        CrudAppService<
            Book, //The Book entity
            BookDto, //Used to show books
            Guid, //Primary key of the book entity
            PagedAndSortedResultRequestDto, //Used for paging/sorting
            CreateUpdateBookDto>, //Used to create/update a book
        IBookAppService //implement the IBookAppService
    {
        public BookAppService(IRepository<Book, Guid> repository)
            : base(repository)
        {

        }
    }
}
C #
  • BookAppServicecó nguồn gốc từ CrudAppService<...>đó thực thi tất cả các phương thức CRUD (tạo, đọc, cập nhật, xóa) được định nghĩa bởi ICrudAppService.
  • BookAppServicetiêm IRepository<Book, Guid>là kho lưu trữ mặc định cho Bookthực thể. ABP tự động tạo kho lưu trữ mặc định cho mỗi gốc (hoặc thực thể) tổng hợp. Xem tài liệu kho lưu trữ .
  • BookAppServicesử dụng IObjectMapperdịch vụ ( xem ) để ánh xạ Bookđối tượng với BookDtođối tượng và CreateUpdateBookDtođối tượng với Bookđối tượng. Mẫu Khởi động sử dụng thư viện AutoMapper làm nhà cung cấp ánh xạ đối tượng. Chúng tôi đã xác định các ánh xạ trước đó, vì vậy nó sẽ hoạt động như mong đợi.

Bộ điều khiển API tự động

Trong ứng dụng ASP.NET Core điển hình, bạn tạo Bộ điều khiển API để hiển thị các dịch vụ ứng dụng dưới dạng điểm cuối API HTTP . Điều này cho phép các trình duyệt hoặc máy khách bên thứ 3 gọi chúng qua HTTP.

ABP có thể tự động định cấu hình các dịch vụ ứng dụng của bạn dưới dạng Bộ điều khiển API MVC theo quy ước.

Giao diện người dùng Swagger

Mẫu khởi động được định cấu hình để chạy giao diện người dùng Swagger bằng thư viện Swashbuckle.AspNetCore . Chạy ứng dụng ( Acme.BookStore.HttpApi.Host) bằng cách nhấn CTRL+F5và điều hướng đến https://localhost:<port>/swagger/trên trình duyệt của bạn. Thay thế <port>bằng số cổng của riêng bạn.

Bạn sẽ thấy một số điểm cuối dịch vụ tích hợp sẵn cũng như Bookdịch vụ và các điểm cuối kiểu REST của nó:

 

Swagger có một giao diện đẹp để kiểm tra các API.

 

Nếu bạn cố gắng thực thi [GET] /api/app/bookAPI để lấy danh sách sách, máy chủ sẽ trả về kết quả JSON như vậy:

{
  "totalCount": 2,
  "items": [
    {
      "name": "The Hitchhiker's Guide to the Galaxy",
      "type": 7,
      "publishDate": "1995-09-27T00:00:00",
      "price": 42,
      "lastModificationTime": null,
      "lastModifierId": null,
      "creationTime": "2020-07-03T21:04:18.4607218",
      "creatorId": null,
      "id": "86100bb6-cbc1-25be-6643-39f62806969c"
    },
    {
      "name": "1984",
      "type": 3,
      "publishDate": "1949-06-08T00:00:00",
      "price": 19.84,
      "lastModificationTime": null,
      "lastModifierId": null,
      "creationTime": "2020-07-03T21:04:18.3174016",
      "creatorId": null,
      "id": "41055277-cce8-37d7-bb37-39f62806960b"
    }
  ]
}
JSON

Điều đó khá tuyệt vì chúng tôi chưa viết một dòng mã nào để tạo bộ điều khiển API, nhưng bây giờ chúng tôi có một API REST hoạt động đầy đủ!

Trước khi bắt đầu phát triển giao diện người dùng, trước tiên chúng tôi muốn chuẩn bị các văn bản bản địa hóa (bạn thường làm điều này khi cần trong khi phát triển ứng dụng của mình).

Văn bản bản địa hóa nằm trong Localization/BookStorethư mục của Acme.BookStore.Domain.Shareddự án:

Mở tệp en.jsonbản dịch tiếng Anh ) và thay đổi nội dung như bên dưới:

{
  "Culture": "en",
  "Texts": {
    "Menu:Home": "Home",
    "Welcome": "Welcome",
    "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.",
    "Menu:BookStore": "Book Store",
    "Menu:Books": "Books",
    "Actions": "Actions",
    "Close": "Close",
    "Delete": "Delete",
    "Edit": "Edit",
    "PublishDate": "Publish date",
    "NewBook": "New book",
    "Name": "Name",
    "Type": "Type",
    "Price": "Price",
    "CreationTime": "Creation time",
    "AreYouSure": "Are you sure?",
    "AreYouSureToDelete": "Are you sure you want to delete this item?",
    "Enum:BookType:0": "Undefined",
    "Enum:BookType:1": "Adventure",
    "Enum:BookType:2": "Biography",
    "Enum:BookType:3": "Dystopia",
    "Enum:BookType:4": "Fantastic",
    "Enum:BookType:5": "Horror",
    "Enum:BookType:6": "Science",
    "Enum:BookType:7": "Science fiction",
    "Enum:BookType:8": "Poetry"
  }
}
JSON
  • Tên khóa bản địa hóa là tùy ý. Bạn có thể đặt bất kỳ tên nào. Chúng tôi thích một số quy ước cho các loại văn bản cụ thể;
    • Thêm Menu:tiền tố cho các mục menu.
    • Sử dụng Enum:<enum-type>:<enum-value>quy ước đặt tên để bản địa hóa các thành viên enum. Khi bạn làm như vậy, ABP có thể tự động khoanh vùng các enum trong một số trường hợp thích hợp.

Nếu một văn bản không được xác định trong tệp bản địa hóa, thì nó sẽ dự phòng cho khóa bản địa hóa (như hành vi tiêu chuẩn của ASP.NET Core).

Hệ thống bản địa hóa của ABP được xây dựng dựa trên hệ thống bản địa hóa tiêu chuẩn của ASP.NET Core và mở rộng nó theo nhiều cách. Xem tài liệu bản địa hóa để biết chi tiết.

Cài đặt gói NPM

Lưu ý: Hướng dẫn này dựa trên ABP Framework v3.1.0 + Nếu phiên bản dự án của bạn cũ hơn, hãy nâng cấp giải pháp của bạn. Xem hướng dẫn di chuyển nếu bạn đang nâng cấp một dự án hiện có với v2.x.

Nếu bạn chưa làm điều đó trước đây, hãy mở giao diện dòng lệnh mới (cửa sổ đầu cuối) và chuyển đến angularthư mục của bạn , sau đó chạy yarn lệnh để cài đặt gói NPM:

yarn
Bash

Tạo trang sách

Đã đến lúc tạo ra thứ gì đó có thể nhìn thấy và sử dụng được! Có một số công cụ mà chúng tôi sẽ sử dụng khi phát triển ứng dụng Angular frontend:

  • Ng Bootstrap sẽ được sử dụng làm thư viện thành phần giao diện người dùng.
  • Ngx-Datatable sẽ được sử dụng làm thư viện dữ liệu.

BookModule

Chạy dòng lệnh sau để tạo một mô-đun mới, có tên BookModuletrong thư mục gốc của ứng dụng góc:

yarn ng generate module book --module app --routing --route books
Bash

Lệnh này sẽ tạo ra kết quả sau:

> yarn ng generate module book --module app --routing --route books

yarn run v1.19.1
$ ng generate module book --module app --routing --route books
CREATE src/app/book/book-routing.module.ts (336 bytes)
CREATE src/app/book/book.module.ts (335 bytes)
CREATE src/app/book/book.component.html (19 bytes)
CREATE src/app/book/book.component.spec.ts (614 bytes)
CREATE src/app/book/book.component.ts (268 bytes)
CREATE src/app/book/book.component.scss (0 bytes)
UPDATE src/app/app-routing.module.ts (1289 bytes)
Done in 3.88s.
Bash

BookModule

Mở /src/app/book/book.module.tsvà thay thế nội dung như hình dưới đây:

import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { BookRoutingModule } from './book-routing.module';
import { BookComponent } from './book.component';

@NgModule({
  declarations: [BookComponent],
  imports: [
    BookRoutingModule,
    SharedModule
  ]
})
export class BookModule { }

JavaScript
  • Đã thêm SharedModuleSharedModulexuất một số mô-đun chung cần thiết để tạo giao diện người dùng.
  • SharedModuleđã xuất khẩu CommonModule, vì vậy chúng tôi đã xóa CommonModule.

định tuyến

Mã được tạo đặt định nghĩa tuyến đường mới vào src/app/app-routing.module.tstệp như được hiển thị bên dưới:

const routes: Routes = [
  // other route definitions...
  { path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) },
];
JavaScript

Bây giờ, mở src/app/route.provider.tstệp thay thế configureRouteskhai báo hàm như hình dưới đây:

function configureRoutes(routes: RoutesService) {
  return () => {
    routes.add([
      {
        path: '/',
        name: '::Menu:Home',
        iconClass: 'fas fa-home',
        order: 1,
        layout: eLayoutType.application,
      },
      {
        path: '/book-store',
        name: '::Menu:BookStore',
        iconClass: 'fas fa-book',
        order: 2,
        layout: eLayoutType.application,
      },
      {
        path: '/books',
        name: '::Menu:Books',
        parentName: '::Menu:BookStore',
        layout: eLayoutType.application,
      },
    ]);
  };
}
JavaScript

RoutesService là một dịch vụ do ABP Framework cung cấp để cấu hình menu chính và các tuyến đường.

  • path là URL của tuyến đường.
  • namelà tên mục menu được bản địa hóa (xem tài liệu bản địa hóa để biết thêm chi tiết).
  • iconClasslà biểu tượng của mục menu (bạn có thể sử dụng biểu tượng Font Awesome theo mặc định).
  • order là thứ tự của mục menu.
  • layoutlà cách bố trí các tuyến đường của BooksModule (có ba loại bố trí được xác định trước: eLayoutType.applicationeLayoutType.accounthoặc eLayoutType.empty).

Để biết thêm thông tin, hãy xem tài liệu RoutesService .

Tạo proxy dịch vụ

ABP CLI cung cấp generate-proxylệnh tạo proxy ứng dụng khách cho các API HTTP của bạn để dễ dàng sử dụng các API HTTP của bạn từ phía máy khách. Trước khi chạy generate-proxylệnh, máy chủ của bạn phải được thiết lập và chạy.

Chạy lệnh sau trong angularthư mục:

abp generate-proxy
Bash

Lệnh này sẽ tạo các tệp sau trong /src/app/proxy/booksthư mục:

BookComponent

Mở /src/app/book/book.component.tstệp và thay thế nội dung như bên dưới:

import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookService, BookDto } from '@proxy/books';

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss'],
  providers: [ListService],
})
export class BookComponent implements OnInit {
  book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;

  constructor(public readonly list: ListService, private bookService: BookService) {}

  ngOnInit() {
    const bookStreamCreator = (query) => this.bookService.getList(query);

    this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
      this.book = response;
    });
  }
}
JavaScript
  • Chúng tôi đã nhập và đưa vào các tệp đã tạo BookService.
  • Chúng tôi đang sử dụng ListService , một dịch vụ tiện ích của Khung ABP cung cấp việc phân trang, sắp xếp và tìm kiếm dễ dàng.

Mở /src/app/book/book.component.htmlvà thay thế nội dung như sau:

<div class="card">
  <div class="card-header">
    <div class="row">
      <div class="col col-md-6">
        <h5 class="card-title">
          {{ '::Menu:Books' | abpLocalization }}
        </h5>
      </div>
      <div class="text-right col col-md-6"></div>
    </div>
  </div>
  <div class="card-body">
    <ngx-datatable [rows]="book.items" [count]="book.totalCount" [list]="list" default>
      <ngx-datatable-column [name]="'::Name' | abpLocalization" prop="name"></ngx-datatable-column>
      <ngx-datatable-column [name]="'::Type' | abpLocalization" prop="type">
        <ng-template let-row="row" ngx-datatable-cell-template>
          {{ '::Enum:BookType:' + row.type | abpLocalization }}
        </ng-template>
      </ngx-datatable-column>
      <ngx-datatable-column [name]="'::PublishDate' | abpLocalization" prop="publishDate">
        <ng-template let-row="row" ngx-datatable-cell-template>
          {{ row.publishDate | date }}
        </ng-template>
      </ngx-datatable-column>
      <ngx-datatable-column [name]="'::Price' | abpLocalization" prop="price">
        <ng-template let-row="row" ngx-datatable-cell-template>
          {{ row.price | currency }}
        </ng-template>
      </ngx-datatable-column>
    </ngx-datatable>
  </div>
</div>
HTML

Bây giờ bạn có thể xem kết quả cuối cùng trên trình duyệt của mình:

Tạo một cuốn sách mới

Trong phần này, bạn sẽ học cách tạo một biểu mẫu hộp thoại phương thức mới để tạo một cuốn sách mới.

BookComponent

Mở /src/app/book/book.component.tsvà thay thế nội dung như bên dưới:

import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookService, BookDto } from '@proxy/books';

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss'],
  providers: [ListService],
})
export class BookComponent implements OnInit {
  book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;

  isModalOpen = false; // add this line

  constructor(public readonly list: ListService, private bookService: BookService) {}

  ngOnInit() {
    const bookStreamCreator = (query) => this.bookService.getList(query);

    this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
      this.book = response;
    });
  }

  // add new method
  createBook() {
    this.isModalOpen = true;
  }
}
JavaScript
  • Chúng tôi đã định nghĩa một thuộc tính được gọi isModalOpenvà một phương thức được gọi createBook.

Mở /src/app/book/book.component.htmlvà thực hiện các thay đổi sau:

<div class="card">
  <div class="card-header">
    <div class="row">
      <div class="col col-md-6">
        <h5 class="card-title">{{ '::Menu:Books' | abpLocalization }}</h5>
      </div>        
      <div class="text-right col col-md-6">
          
        <!-- Add the "new book" button here -->
        <div class="text-lg-right pt-2">
          <button id="create" class="btn btn-primary" type="button" (click)="createBook()">
            <i class="fa fa-plus mr-1"></i>
            <span>{{ "::NewBook" | abpLocalization }}</span>
          </button>
        </div>
          
      </div>
    </div>
  </div>
  <div class="card-body">
    <!-- ngx-datatable should be here! -->
  </div>
</div>

<!-- Add the modal here -->
<abp-modal [(visible)]="isModalOpen">
  <ng-template #abpHeader>
    <h3>{{ '::NewBook' | abpLocalization }}</h3>
  </ng-template>

  <ng-template #abpBody> </ng-template>

  <ng-template #abpFooter>
    <button type="button" class="btn btn-secondary" #abpClose>
      {{ '::Close' | abpLocalization }}
    </button>
  </ng-template>
</abp-modal>
HTML
  • Đã thêm New booknút vào tiêu đề thẻ ..
  • Đã thêm abp-modalphương thức hiển thị một phương thức để cho phép người dùng tạo một cuốn sách mới. abp-modallà một thành phần được tạo sẵn để hiển thị các phương thức. Mặc dù bạn có thể sử dụng cách tiếp cận khác để hiển thị một phương thức, abp-modalcung cấp các lợi ích bổ sung.

Bạn có thể mở trình duyệt của mình và nhấp vào nút Sách mới để xem phương thức mới.

 

Tạo biểu mẫu phản ứng

 

Biểu mẫu phản ứng cung cấp cách tiếp cận theo hướng mô hình để xử lý các đầu vào biểu mẫu có giá trị thay đổi theo thời gian.

Mở /src/app/book/book.component.tsvà thay thế nội dung như bên dưới:

import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
// import bookTypeOptions from @proxy/books
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss'],
  providers: [ListService],
})
export class BookComponent implements OnInit {
  book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;

  form: FormGroup; // add this line

  // add bookTypes as a list of BookType enum members
  bookTypes = bookTypeOptions;

  isModalOpen = false;

  constructor(
    public readonly list: ListService,
    private bookService: BookService,
    private fb: FormBuilder // inject FormBuilder
  ) {}

  ngOnInit() {
    const bookStreamCreator = (query) => this.bookService.getList(query);

    this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
      this.book = response;
    });
  }

  createBook() {
    this.buildForm(); // add this line
    this.isModalOpen = true;
  }

  // add buildForm method
  buildForm() {
    this.form = this.fb.group({
      name: ['', Validators.required],
      type: [null, Validators.required],
      publishDate: [null, Validators.required],
      price: [null, Validators.required],
    });
  }

  // add save method
  save() {
    if (this.form.invalid) {
      return;
    }

    this.bookService.create(this.form.value).subscribe(() => {
      this.isModalOpen = false;
      this.form.reset();
      this.list.get();
    });
  }
}
JavaScript
  • Nhập khẩu FormGroupFormBuildervà Validatorstừ @angular/forms.
  • Đã thêm form: FormGrouptài sản.
  • Đã thêm bookTypestài sản dưới dạng danh sách các BookTypethành viên enum. Điều đó sẽ được sử dụng trong các tùy chọn biểu mẫu.
  • Đã tiêm FormBuildervào hàm tạo. FormBuilder cung cấp các phương pháp thuận tiện để tạo điều khiển biểu mẫu. Nó làm giảm số lượng boilerplate cần thiết để xây dựng các biểu mẫu phức tạp.
  • Đã thêm buildFormphương thức vào cuối tệp và thực thi phương thức buildForm()trong createBookphương thức.
  • Đã thêm savephương thức.

Mở /src/app/book/book.component.htmlvà thay thế <ng-template #abpBody> </ng-template> bằng phần mã sau:

<ng-template #abpBody>
  <form [formGroup]="form" (ngSubmit)="save()">
    <div class="form-group">
      <label for="book-name">Name</label><span> * </span>
      <input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
    </div>

    <div class="form-group">
      <label for="book-price">Price</label><span> * </span>
      <input type="number" id="book-price" class="form-control" formControlName="price" />
    </div>

    <div class="form-group">
      <label for="book-type">Type</label><span> * </span>
      <select class="form-control" id="book-type" formControlName="type">
        <option [ngValue]="null">Select a book type</option>
        <option [ngValue]="type.value" *ngFor="let type of bookTypes"> {{ type.key }}</option>
      </select>
    </div>

    <div class="form-group">
      <label>Publish date</label><span> * </span>
      <input
        #datepicker="ngbDatepicker"
        class="form-control"
        name="datepicker"
        formControlName="publishDate"
        ngbDatepicker
        (click)="datepicker.toggle()"
      />
    </div>
  </form>
</ng-template>
HTML

Cũng thay thế <ng-template #abpFooter> </ng-template>bằng phần mã sau:

<ng-template #abpFooter>
  <button type="button" class="btn btn-secondary" #abpClose>
      {{ '::Close' | abpLocalization }}
  </button>

  <!--added save button-->
  <button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
        <i class="fa fa-check mr-1"></i>
        {{ '::Save' | abpLocalization }}
  </button>
</ng-template>
HTML

Bảng chọn ngày

Chúng tôi đã sử dụng trình chọn ngày NgBootstrap trong thành phần này. Vì vậy, cần sắp xếp các phụ thuộc liên quan đến thành phần này.

Mở /src/app/book/book.module.tsvà thay thế nội dung như bên dưới:

import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { BookRoutingModule } from './book-routing.module';
import { BookComponent } from './book.component';
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; // add this line

@NgModule({
  declarations: [BookComponent],
  imports: [
    BookRoutingModule,
    SharedModule,
    NgbDatepickerModule, // add this line
  ]
})
export class BookModule { }
JavaScript
  • Chúng tôi đã nhập NgbDatepickerModule để có thể sử dụng công cụ chọn ngày.

Mở /src/app/book/book.component.tsvà thay thế nội dung như bên dưới:

import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

// added this line
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss'],
  providers: [
    ListService,
    { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter } // add this line
  ],
})
export class BookComponent implements OnInit {
  book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;

  form: FormGroup;

  bookTypes = bookTypeOptions;

  isModalOpen = false;

  constructor(
    public readonly list: ListService,
    private bookService: BookService,
    private fb: FormBuilder
  ) {}

  ngOnInit() {
    const bookStreamCreator = (query) => this.bookService.getList(query);

    this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
      this.book = response;
    });
  }

  createBook() {
    this.buildForm();
    this.isModalOpen = true;
  }

  buildForm() {
    this.form = this.fb.group({
      name: ['', Validators.required],
      type: [null, Validators.required],
      publishDate: [null, Validators.required],
      price: [null, Validators.required],
    });
  }

  save() {
    if (this.form.invalid) {
      return;
    }

    this.bookService.create(this.form.value).subscribe(() => {
      this.isModalOpen = false;
      this.form.reset();
      this.list.get();
    });
  }
}
JavaScript
  • Đã nhập khẩu NgbDateNativeAdaptervà NgbDateAdapter.
  • Chúng tôi đã thêm một nhà cung cấp mới NgbDateAdaptercó thể chuyển đổi giá trị Datepicker thành Dateloại. Xem bộ điều hợp datepicker để biết thêm chi tiết.

Bây giờ, bạn có thể mở trình duyệt của mình để xem các thay đổi:

 

Cập nhật sách

 

Mở /src/app/book/book.component.tsvà thay thế nội dung như hình dưới đây:

import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss'],
  providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookComponent implements OnInit {
  book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;

  selectedBook = {} as BookDto; // declare selectedBook

  form: FormGroup;

  bookTypes = bookTypeOptions;

  isModalOpen = false;

  constructor(
    public readonly list: ListService,
    private bookService: BookService,
    private fb: FormBuilder
  ) {}

  ngOnInit() {
    const bookStreamCreator = (query) => this.bookService.getList(query);

    this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
      this.book = response;
    });
  }

  createBook() {
    this.selectedBook = {} as BookDto; // reset the selected book
    this.buildForm();
    this.isModalOpen = true;
  }

  // Add editBook method
  editBook(id: string) {
    this.bookService.get(id).subscribe((book) => {
      this.selectedBook = book;
      this.buildForm();
      this.isModalOpen = true;
    });
  }

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

  // change the save method
  save() {
    if (this.form.invalid) {
      return;
    }

    const request = this.selectedBook.id
      ? this.bookService.update(this.selectedBook.id, this.form.value)
      : this.bookService.create(this.form.value);

    request.subscribe(() => {
      this.isModalOpen = false;
      this.form.reset();
      this.list.get();
    });
  }
}
JavaScript
  • Chúng tôi đã khai báo một biến có tên selectedBooklà BookDto.
  • Chúng tôi đã thêm editBook phương pháp. Phương thức này tìm nạp sách với giá trị đã cho idvà đặt nó thành selectedBookđối tượng.
  • Chúng tôi đã thay thế buildFormphương thức để nó tạo biểu mẫu với selectedBookdữ liệu.
  • Chúng tôi đã thay thế createBookphương thức để nó đặt selectedBookthành một đối tượng trống.
  • Chúng tôi đã thay đổi savephương pháp để xử lý cả hoạt động tạo và cập nhật.

Thêm menu thả xuống "Tác vụ" vào Bảng

Mở /src/app/book/book.component.html  và thêm ngx-datatable-columnđịnh nghĩa sau làm cột đầu tiên trong ngx-datatable:

<ngx-datatable-column
  [name]="'::Actions' | abpLocalization"
  [maxWidth]="150"
  [sortable]="false"
>
  <ng-template let-row="row" ngx-datatable-cell-template>
    <div ngbDropdown container="body" class="d-inline-block">
      <button
        class="btn btn-primary btn-sm dropdown-toggle"
        data-toggle="dropdown"
        aria-haspopup="true"
        ngbDropdownToggle
      >
        <i class="fa fa-cog mr-1"></i>{{ '::Actions' | abpLocalization }}
      </button>
      <div ngbDropdownMenu>
        <button ngbDropdownItem (click)="editBook(row.id)">
          {{ '::Edit' | abpLocalization }}
        </button>
      </div>
    </div>
  </ng-template>
</ngx-datatable-column>
HTML

Đã thêm trình đơn thả xuống "Tác vụ" làm cột đầu tiên của bảng được hiển thị bên dưới:

 

Ngoài ra, hãy thay đổi ng-template #abpHeaderphần như được hiển thị bên dưới:

 

<ng-template #abpHeader>
    <h3>{{ (selectedBook.id ? '::Edit' : '::NewBook' ) | abpLocalization }}</h3>
</ng-template>
HTML

Mẫu này sẽ hiển thị Chỉnh sửa văn bản cho thao tác chỉnh sửa bản ghi, Sách mới cho thao tác bản ghi mới trong tiêu đề.

Xóa sách

Mở /src/app/book/book.component.tsvà tiêm ConfirmationService.

Thay thế hàm tạo như sau:

// ...

// add new imports
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';

//change the constructor
constructor(
  public readonly list: ListService,
  private bookService: BookService,
  private fb: FormBuilder,
  private confirmation: ConfirmationService // inject the ConfirmationService
) {}

// Add a delete method
delete(id: string) {
  this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => {
    if (status === Confirmation.Status.confirm) {
      this.bookService.delete(id).subscribe(() => this.list.get());
    }
  });
}
JavaScript
  • Chúng tôi đã nhập khẩu ConfirmationService.
  • Chúng tôi đã tiêm ConfirmationServicevào hàm tạo.
  • Đã thêm một deletephương thức.

Xem tài liệu Cửa sổ bật lên xác nhận để biết thêm về dịch vụ này.

Thêm nút Xóa

Mở /src/app/book/book.component.htmlvà sửa đổi ngbDropdownMenuđể thêm nút xóa như hình dưới đây:

<div ngbDropdownMenu>
  <!-- add the Delete button -->
    <button ngbDropdownItem (click)="delete(row.id)">
        {{ '::Delete' | abpLocalization }}
    </button>
</div>
HTML

Giao diện người dùng thả xuống hành động cuối cùng trông giống như bên dưới:

 

Nhấp vào hành động "Xóa" gọi deletephương thức, sau đó sẽ hiển thị cửa sổ bật lên xác nhận như được hiển thị bên dưới:

 

 

 

 

Quyền

ABP Framework cung cấp một hệ thống ủy quyền dựa trên cơ sở hạ tầng ủy quyền của ASP.NET Core Một tính năng chính được thêm vào đầu cơ sở hạ tầng ủy quyền tiêu chuẩn là hệ thống quyền cho phép xác định quyền và bật / tắt cho mỗi vai trò, người dùng hoặc khách hàng.

Tên quyền

Quyền phải có một tên duy nhất (a string). Cách tốt nhất là định nghĩa nó là a const, để chúng ta có thể sử dụng lại tên quyền.

Mở BookStorePermissionslớp bên trong Acme.BookStore.Application.Contractsdự án (trong Permissionsthư mục) và thay đổi nội dung như hình dưới đây:

namespace Acme.BookStore.Permissions
{
    public static class BookStorePermissions
    {
        public const string GroupName = "BookStore";

        public static class Books
        {
            public const string Default = GroupName + ".Books";
            public const string Create = Default + ".Create";
            public const string Edit = Default + ".Edit";
            public const string Delete = Default + ".Delete";
        }
    }
}
C #

Đây là một cách phân cấp để xác định tên quyền. Ví dụ: tên quyền "tạo sách" được định nghĩa là BookStore.Books.CreateABP không ép bạn vào một cấu trúc, nhưng chúng tôi thấy cách này hữu ích.

Định nghĩa quyền

Bạn nên xác định các quyền trước khi sử dụng chúng.

Mở BookStorePermissionDefinitionProviderlớp bên trong Acme.BookStore.Application.Contractsdự án (trong Permissionsthư mục) và thay đổi nội dung như hình dưới đây:

using Acme.BookStore.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;

namespace Acme.BookStore.Permissions
{
    public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
    {
        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);
        }
    }
}
C #

Lớp này định nghĩa một nhóm quyền (để nhóm các quyền trên giao diện người dùng, sẽ được thấy bên dưới) và 4 quyền bên trong nhóm này. Ngoài ra, Tạo , Chỉnh sửa và Xóa là con của BookStorePermissions.Books.Defaultquyền. Quyền con chỉ có thể được chọn nếu cha mẹ đã được chọn .

Cuối cùng, chỉnh sửa tệp bản địa hóa ( en.jsontrong Localization/BookStorethư mục của Acme.BookStore.Domain.Shareddự án) để xác định các khóa bản địa hóa được sử dụng ở trên:

"Permission:BookStore": "Book Store",
"Permission:Books": "Book Management",
"Permission:Books.Create": "Creating new books",
"Permission:Books.Edit": "Editing the books",
"Permission:Books.Delete": "Deleting the books"
JSON

Tên khóa bản địa hóa là tùy ý và không có quy tắc bắt buộc. Nhưng chúng tôi thích quy ước được sử dụng ở trên.

Giao diện người dùng quản lý quyền

Khi bạn xác định các quyền, bạn có thể thấy chúng trên phương thức quản lý quyền .

Vào Administration -> Identity -> Vai trò trang, chọn Permissions hành động cho vai trò quản trị để mở phương thức quản lý cho phép:

 

Cấp quyền bạn muốn và lưu phương thức.

 

Mẹo : Các quyền mới sẽ tự động được cấp cho vai trò quản trị viên nếu bạn chạy Acme.BookStore.DbMigratorứng dụng.

Ủy quyền

Bây giờ, bạn có thể sử dụng quyền để ủy quyền quản lý sách.

Lớp ứng dụng & API HTTP

Mở BookAppServicelớp và thêm đặt tên chính sách làm tên quyền được xác định ở trên:

using System;
using Acme.BookStore.Permissions;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

namespace Acme.BookStore.Books
{
    public class BookAppService :
        CrudAppService<
            Book, //The Book entity
            BookDto, //Used to show books
            Guid, //Primary key of the book entity
            PagedAndSortedResultRequestDto, //Used for paging/sorting
            CreateUpdateBookDto>, //Used to create/update a book
        IBookAppService //implement the IBookAppService
    {
        public BookAppService(IRepository<Book, Guid> repository)
            : base(repository)
        {
            GetPolicyName = BookStorePermissions.Books.Default;
            GetListPolicyName = BookStorePermissions.Books.Default;
            CreatePolicyName = BookStorePermissions.Books.Create;
            UpdatePolicyName = BookStorePermissions.Books.Edit;
            DeletePolicyName = BookStorePermissions.Books.Delete;
        }
    }
}
C #

Đã thêm mã vào hàm tạo. Base CrudAppServicetự động sử dụng các quyền này trên các hoạt động CRUD. Điều này làm cho dịch vụ ứng dụng an toàn, nhưng cũng làm cho API HTTP an toàn vì dịch vụ này được tự động sử dụng như một API HTTP như đã giải thích trước đây (xem bộ điều khiển API tự động ).

Bạn sẽ thấy ủy quyền khai báo, sử dụng [Authorize(...)]thuộc tính, sau đó trong khi phát triển chức năng quản lý tác giả.

Cấu hình Angular Guard

Bước đầu tiên của giao diện người dùng là ngăn người dùng trái phép nhìn thấy mục menu "Sách" và truy cập vào trang quản lý sách.

Mở /src/app/book/book-routing.module.tsvà thay thế bằng nội dung sau:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard, PermissionGuard } from '@abp/ng.core';
import { BookComponent } from './book.component';

const routes: Routes = [
  { path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class BookRoutingModule {}
JavaScript
  • Đã nhập AuthGuardvà PermissionGuardtừ @abp/ng.core.
  • Đã thêm vào canActivate: [AuthGuard, PermissionGuard]định nghĩa tuyến đường.

Mở /src/app/route.provider.tsvà thêm requiredPolicy: 'BookStore.Books'vào /bookstuyến đường. Khối /booksđịnh tuyến phải như sau:

{
  path: '/books',
  name: '::Menu:Books',
  parentName: '::Menu:BookStore',
  layout: eLayoutType.application,
  requiredPolicy: 'BookStore.Books',
}
JavaScript

Ẩn Nút Sách Mới

Trang quản lý sách có nút Sách Mới sẽ ẩn nếu người dùng hiện tại không có quyền Tạo Sách .

 

Mở /src/app/book/book.component.htmltệp và thay thế nút tạo nội dung HTML như hình dưới đây:

 

<!-- Add the abpPermission directive -->
<button *abpPermission="'BookStore.Books.Create'" id="create" class="btn btn-primary" type="button" (click)="createBook()">
  <i class="fa fa-plus mr-1"></i>
  <span>{{ '::NewBook' | abpLocalization }}</span>
</button>
HTML
  • Chỉ cần thêm *abpPermission="'BookStore.Books.Create'"rằng ẩn nút nếu người dùng hiện tại không có quyền.

Ẩn các Hành động Chỉnh sửa và Xóa

Bảng sách trong trang quản lý sách có nút tác vụ cho mỗi hàng. Nút hành động bao gồm các hành động Chỉnh sửa và Xóa :

 

Chúng ta nên ẩn một hành động nếu người dùng hiện tại chưa cấp quyền liên quan.

 

Mở /src/app/book/book.component.htmltệp và thay thế nội dung các nút chỉnh sửa và xóa như hình dưới đây:

<!-- Add the abpPermission directive -->
<button *abpPermission="'BookStore.Books.Edit'" ngbDropdownItem (click)="editBook(row.id)">
  {{ '::Edit' | abpLocalization }}
</button>

<!-- Add the abpPermission directive -->
<button *abpPermission="'BookStore.Books.Delete'" ngbDropdownItem (click)="delete(row.id)">
  {{ '::Delete' | abpLocalization }}
</button>
HTML
  • Đã thêm *abpPermission="'BookStore.Books.Edit'"ẩn hành động chỉnh sửa nếu người dùng hiện tại không có quyền chỉnh sửa.
  • Đã thêm *abpPermission="'BookStore.Books.Delete'"ẩn hành động xóa nếu người dùng hiện tại không có quyền xóa.

Nguồn: https://docs.abp.io/en/abp/latest

Tin tức khác

  • Tạo chatbot với CHAT GPT sử dụng C#

    Tạo chatbot với CHAT GPT sử dụng C#

    Trong hướng dẫn này, chúng ta sẽ đi sâu vào quá trình xây dựng chatbot bằng ChatGPT và C#. Chúng tôi sẽ đề cập đến mọi thứ, từ thiết lập quyền truy cập API ChatGPT đến triển khai chatbot của bạn. Bắt đầu nào!

  • Remote SQL Server. Cách mở port 1433 để kết nối với sqlserver từ xa.

    Remote SQL Server. Cách mở port 1433 để kết nối với sqlserver từ xa.

    Hiện nay nhiều người có xây dựng cơ sở dữ liệu trên server và kết nối tới để làm việc cho tiện. Nên mình chia sẻ bài viết này cho người mới nhé.

  • Sự khác nhau giữa Application, Virtual Direction và Site. Cách tạo 1 Virtual Direction.

    Sự khác nhau giữa Application, Virtual Direction và Site. Cách tạo 1 Virtual Direction.

    Trong IIS, bạn có thể tạo các trang web, ứng dụng và thư mục ảo để chia sẻ thông tin với người dùng qua Internet, mạng nội bộ hoặc mạng phụ. Mặc dù các khái niệm này đã tồn tại trong các phiên bản trước của IIS, một số thay đổi trong IIS 7 trở lên ảnh hưởng đến định nghĩa và chức năng của các khái niệm này. Quan trọng nhất, các trang web, ứng dụng và thư mục ảo giờ đây hoạt động cùng nhau theo mối quan hệ phân cấp như những khối xây dựng cơ bản để lưu trữ nội dung trực tuyến và cung cấp dịch vụ trực tuyến.

  • Design pattern là gì? Tại sao nên sử dụng Design pattern?

    Design pattern là gì? Tại sao nên sử dụng Design pattern?

    Design pattern là các giải pháp tổng thể đã được tối ưu hóa, được tái sử dụng cho các vấn đề phổ biến trong thiết kế phần mềm mà chúng ta thường gặp phải hàng ngày. Đây là tập các giải pháp đã được suy nghĩ, đã giải quyết trong tình huống cụ thể.

  • CDN là gì? Khi nào thì cần xài CDN cho website

    CDN là gì? Khi nào thì cần xài CDN cho website

    Thuật ngữ CDN có thể bạn sẽ bắt gặp khá nhiều bài viết trên thachpham.com, hoặc khi bạn cần một người có kinh nghiệm tư vấn giải pháp tiết kiệm băng thông máy chủ và tăng tốc độ website đều sẽ được nghe tư vấn là sử dụng CDN. Vậy CDN chính xác là cái gì, có bao nhiêu loại CDN, và website của bạn có thích hợp để sử dụng CDN không thì bài này sẽ cung cấp cho bạn các thông tin cần thiết đó.

  • Giao thức HTTP và HTTPS là gì? Tại sao nên sử dụng HTTPS?

    Giao thức HTTP và HTTPS là gì? Tại sao nên sử dụng HTTPS?

    Môi trường internet phát triển, kéo theo tội phạm mạng tăng cao, vì thế cần có những chuẩn bảo mật web cao hơn. Đó là lí do giao thức HTTPS dần thay thế hoàn toàn HTTP. Vậy, giao thức HTTPS là gì? HTTP và HTTPS khác nhau như thế nào? Và tại sao các website nên dùng HTTPS thay vì HTTP? Bài viết này sẽ giúp bạn giải đáp tất cả những thắc mắc đó.

  • Tích hợp zalo vào website.

    Tích hợp zalo vào website.

    Nếu bạn là một người có website bán hàng thì việc liên hệ thuận lợi nhanh cho khách hàng luôn là ưu tiên hàng đầu. Cũng chính vì lí dó này mình đã tìm hiểu sau khi chèn cho website của mình. Thấy hay nên chia sẻ cho mọi người có nhu cầu.