Jakeuj's Notes master Help

ABP.IO 新手教學 No.02

本文為 ABP.IO WEB應用程式框架開發教學第 1 部分,說明如何創建服務端。

此開發教學相較上一篇 No.01 快速開始 比較複雜一些

第一次接觸還沒看過快速開始的建議先從上一篇先看

這篇理論上同樣著重在 .Net Core + EF Core 建立 API

前端框架 Angular 的實現不在本次重點會先快速帶過

系列文章索引

參考: [2021] ABP.IO WEB應用程式框架 新手教學 No.0 全篇索引

本篇會以官方文件 開發教學 為依據中文化並附圖加以說明的方式進行

Web應用程序開發教程 - 第一章:創建服務端

關於本教程

在本系列教程中,您將構建一個名稱Acme.BookStore的用於管理書籍及其作者列表的基於 ABP 的程序。是使用以下技術開發的:

  • Entity Framework Core 為ORM 提供程序。

  • MVC / Razor Pages做為 UI 框架。

本教程分為以下部分:

下載源碼

本教程根據你的UI數據庫首選項有多個版本,我們準備了一個模型下載的源碼組件:

創建解決方案

在開始開發之前,請按照入門教程創建命名Acme.BookStore的新解決方案。

// 這邊我從 https://abp.io/get-started 使用以下選項建立

1626833463

// 這邊可以不要勾最下面的選項,這邊只是我想要分開,但分開真正要跑需要有 Redis,可以安裝 docker 後執行, docker pull redis & docker run --name some-redis -d redis -p 6379:6379

// 因為專案預設會啟用 Redis,如果沒有可以先關閉,在 appsettings.json 中的 Redis 裡面加上 "IsEnabled": "true", 請參考 Redis 快取設定 說明文件

1626834411

創建圖書實體

啟動模板中的領域層分為兩個項目:

  • Acme.BookStore.Domain包括你的實體領域服務和其他核心對象(例如:倉儲介面)。

  • Acme.BookStore.Domain.Shared包括可與客戶端共享的所有對象,枚舉或其他域相關。

BookType 枚舉 (Enum)

下面的項目所產生的BookType枚舉,在Acme.BookStore.Domain.Shared創建BookType

// 如果有開發過 API 應該遇過給 client 的時候蠻常會用到實體的 enum ,因為 DDD 中領域不會開放給外部(Client),所以這類東西需要放在領域共用專案,方便到時候 DTO 可以直接使用。

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

Book 實體 (Entity)

在解決方案的領域層Acme.BookStore.Domain項目)中定義你的實體。

該應用程序的主要實體是Book 。在Acme.BookStore.Domain項目中創建一個Books文件夾並在其中添加了一個名稱Book的類,如下所示:

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; } } }
  • ABP為實體提供了兩個基本的基類: AggregateRootEntityAggregate Root領域驅動設計概念一個。可以直接查詢和處理的根實體(請參閱實體文檔)。 // 同聚合內非根的一般實體可以用 BookCover : Entity<Guid> ,因為同聚合內應該只有一個根,DDD不熟暫時不想用也可以直接照你原本開發方式全部用一般實體基類Entity<T>

  • Book實體繼承了AuditedAggregateRootAuditedAggregateRoot類在AggregateRoot類的基礎上添加了一些審計屬性(CreationTime, CreatorId, LastModificationTime )。ABP框架自動為你管理這些屬性。 // 不用聚合根也不想那麼多審計屬性, ABP 也提供其他基類 ,比如:CreationAuditedEntity<TKey> ,再少也可以只使用 ABP 提供的介面 ,例如:IHasCreationTime,優點是可以統一屬性名稱為 CreationTime

  • GuidBook實體的主鍵類型。 // 主鍵類型也可以自己改,例如:BookPage : Entity<long> ,只是 ABP 推薦使用 Guid 就是了

// 這邊引用一下 ABP 審計基類 中關於實體中巡覽屬性的一段敘述給大家參考一下

1626838362

最終的文件夾/文件結構應該如下所示:

1626838911

將Book實體添加到DbContext中

EF Core 需要你將實體和DbContext建立關聯。最簡單的做法是在Acme.BookStore.EntityFrameworkCore項目的BookStoreDbContext類中添加DbSet屬性。如下所示:

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

將書實體映射到數據庫表

Acme.BookStore.EntityFrameworkCore項目中打開BookStoreDbContextModelCreatingExtensions.cs文件,添加Book實體的映射代碼。最終類應為:

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); }); } } }
1626839141
  • ConfigureByConvention() 方法優雅的配置/歸屬的屬性,應始終對你所有的屬性使用它。

  • BookStoreConsts包含用於表的架構和表前綴的常量值。你不一定需要使用它,但建議在單點控製表的前綴。 // 這是定義資料表名稱前綴,用來跟 Abp 開頭的表來做區分,方便辨識哪些是框架用的資料表,哪些是我們自己應用程式用的資料表,或自己定義不同前綴來分類自己的表, // 這邊建議統一定義在領域層的一個統一地方,預設是 BookStoreConsts.cs ,有其他需要共用的常量 (Const) 也可以繼續統一加在這裡,方便使用與管理。

1626839256

添加數據遷移

啟動模板使用EF Core Code First Migrations創建和維護數據庫架構。我們應該創建一個新的遷移並應用到數據庫。

Acme.BookStore.EntityFrameworkCore.DbMigrations目錄中打開命令行輸入以下命令:

dotnet ef migrations add Created_Book_Entity
1626682350

它會添加新的遷移類到項目中:

書店-efcore-遷移
1626682501

添加附加數據 (SeedData)

// 在之後的 整合測試 章節會用到這份資料來做測試,雖然不是必須的流程,但這邊建議可以試著做一遍。

*.Domain項目下創建派生IDataSeedContributor的類,並且拷貝以下代碼:

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 ); } } } }
  • 如果中數據庫沒有當前圖書,則使用IRepository<Book, Guid> (默認為知識庫 )將兩本書插入數據庫。

1626683321

更新數據庫

運行Acme.BookStore.DbMigrator應用程序來更新數據庫:

bookstore-dbmigrator-on-solution

.DbMigrator 是一個開發使用程序,可以在開發生產環境遷移數據庫初始化數據

// 預設連線字串指定的 DB Server 是 LocalDb (裝 VS 預設會有的開發用 DB),沒有了話需要到 appsettings.json 改連線字串到自己的DB,

// 執行完以上可以用SSMS連到 (LocalDb)\MSSQLLocalDB 看看剛剛建立出來的資料庫與表,確認資料是否有如我們預期正確新建出來

創建應用程序

應用程序層由兩個單獨的項目組成:

  • Acme.BookStore.Application.Contracts包含你的DTO應用服務接口。

  • Acme.BookStore.Application 包含你的應用服務實現。

在本部分中,您將創建一個應用程序服務,使用 ABP 框架的CrudAppService基類來獲取、創建、更新和刪除書籍。

// 這邊教學是使用非常規(ABP內建CRUD)的應用服務基底類別,基本的可以參考上一篇 快速開始 的應用服務部分,

// 這邊主要是了解ABP如何簡化重複的CRUD程式碼,基本的可以不實作任何一行程式,只要定義DTO並繼承ABP的介面與基類就可以提供基本CRUD程作業

Book Dto

CrudAppService基類需要定義實體的基本DTO。在Acme.BookStore.Application.Contracts項目中創建一個名稱BookDto的 DTO 類:

using System; using Volo.Abp.Application.Dtos; namespace Acme.BookStore { 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; } } }
  • DTO類被用來表示層應用層 傳遞數據 。查看DTO 文檔查看更多信息。

  • 為了在頁面上展示書籍信息, BookDto被將書籍數據傳遞到顯示層。

  • BookDto繼承自AuditedEntityDto<Guid> 。跟上面定義的Book實體一樣具有一些審計屬性。 // 這邊為了用來做 CRUD,某些DTO可能需要繼承內鍵含有Id定義的基類,才能正常做Update與Delete,一般DTO則可以不用,可以參考 快速開始

1626684853

在將書籍返回到表示層時,需要將Book實體轉換為BookDto對象。 AutoMapper庫可以在定義正確的映射時自動執行此轉換。

啟動模板配置了AutoMapper,因此你很適合在Acme.BookStore.Application項目的BookStoreApplicationAutoMapperProfile類中定義映射:

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

創建更新書Dto

Acme.BookStore.Application.Contracts項目中創建一個名稱CreateUpdateBookDto的 DTO 類:

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; } } }
  • 這個DTO類被用於在創建或更新書籍的時候從用戶界面獲取圖書信息。

  • 它定義了數據註釋屬性(如[Required] )來定義屬性的驗證。DTO由ABP框架自動驗證

就像上面BookDto一樣,創建一個從對像CreateUpdateBookDtoBook實體的映射,最後一個映射類如下:

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

圖書應用服務介面

下一步是應用程序定義接口,在Acme.BookStore.Application.Contracts項目中定義一個未知IBookAppService的接口:

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 { } }
  • 框架定義應用程序服務的接口不是必需的 。但是,它被建議為最佳實踐。

  • ICrudAppService定義了常見的CRUD方法: GetAsyncGetListAsyncCreateAsyncUpdateAsyncDeleteAsync。 你可以從空的IApplicationService接口繼承並手動定義自己的方法(將在下一個領域中完成)。

  • ICrudAppService有一些變體,你可以在每個方法中單獨使用 DTO,也可以分別單獨指定(例如使用不同的 DTO 進行創建和更新)。

1627018683

圖書應用服務

Acme.BookStore.Application項目中命名BookAppServiceIBookAppService實現:

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) { } } }
  • BookAppService繼承了CrudAppService<...> 。它實現了ICrudAppService定義的 CRUD 方法。

  • BookAppService注入IRepository <Book,Guid> ,這是Book實體的默認。ABP自動為每個化根(或實體)創建默認。請參閱文檔

  • BookAppService使用IObjectMapperBook對象轉換為BookDto對象, 將CreateUpdateBookDto對象轉換為Book對象。 啟動模板使用AutoMapper庫作為對象映射提供程序。我們之前定義了映射,從而導致方向預期工作。

1627018955

自動生成API控制器

通常你創建控制器以將應用程序服務公開為HTTP API 。因此允許瀏覽器或客戶端通過 AJAX 調用他們。

ABP可以自動為你的應用程序服務配置MVC API控制器。

Swagger 的用戶界面

啟動模板配置為使用Swashbuckle.AspNetCore運行swagger UI 。運行應用程序並在瀏覽器中輸入https://localhost:XXXX/swagger/ (用你自己的端口替換XXXX)作為URL。

你會看到一些內置的接口和Book接口,它們都是REST風格的:

書店招搖

Swagger 有一個很好的 UI 來測試 API。

你可以嘗試執行[GET] /api/app/bookAPI來獲取書籍列表,服務端會返回以下JSON結果:

{ "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" } ] }

這很酷,因為我們沒有寫任何代碼來創建 API 控制器,但是現在我們有了一個可以正常使用的 REST API!

下一章

請參閱教程的下一章

ABP.IO WEB應用程式框架 新手教學 No.02 開發教學 Part 2 圖書列表頁面

Jakeuj

PS5

  • ABP

  • 回首頁

本文章從點部落遷移至 Writerside

14 October 2025