關於本教程
在本系列教程中, 你將構建一個用於管理書籍及其作者列表的應用程序. Entity Framework Core(EF Core)將用作ORM提供者,因爲它是默認數據庫提供者.
這是本教程所有章節中的第一章,下面是所有的章節:
- Part I: 創建項目和書籍列表頁面(本章)
- Part II: 創建,編輯,刪除書籍
- Part III: 集成測試
你可以從GitHub存儲庫訪問應用程序的源代碼。你也可以觀看由ABP社區成員爲本教程錄製的視頻課程。
創建項目
創建一個名爲Acme.BookStore
的新項目, 創建數據庫並按照入門文檔運行應用程序。
解決方案的結構
下面的圖片展示了從啓動模板創建的項目是如何分層的。
你可以查看應用程序模板文檔以詳細瞭解解決方案結構.但是,你將通過本教程瞭解基礎知識.
創建Book實體
啓動模板中的域層分爲兩個項目:
在解決方案的領域層(Acme.BookStore.Domain
項目)中定義實體. 該應用程序的主要實體是Book
. 在Acme.BookStore.Domain
項目中創建一個名爲Book
的類,如下所示:
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace Acme.BookStore
{
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爲實體提供了兩個基本的基類:
AggregateRoot
和Entity
. Aggregate Root是域驅動設計(DDD) 概念之一. 有關詳細信息和最佳做法,請參閱實體文檔. Book
實體繼承了AuditedAggregateRoot
,AuditedAggregateRoot
類在AggregateRoot
類的基礎上添加了一些審計屬性(CreationTime
,CreatorId
,LastModificationTime
等).Guid
是Book
實體的主鍵類型.- 使用 數據註解 爲EF Core添加映射.或者你也可以使用 EF Core 自帶的fluent mapping API.
BookType枚舉
上面所用到的BookType
枚舉定義如下:
namespace Acme.BookStore
{
public enum BookType
{
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry
}
}
將Book實體添加到DbContext中
EF Core需要你將實體和DbContext建立關聯.最簡單的做法是在Acme.BookStore.EntityFrameworkCore
項目的BookStoreDbContext
類中添加DbSet
屬性.如下所示:
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
public DbSet<Book> Books { get; set; }
...
}
配置你的Book實體
在Acme.BookStore.EntityFrameworkCore
項目中打開BookStoreDbContextModelCreatingExtensions.cs
文件,並將以下代碼添加到ConfigureBookStore
方法的末尾以配置Book實體:
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);
});
添加新的Migration並更新數據庫
這個啓動模板使用了EF Core Code First Migrations來創建並維護數據庫結構.打開 程序包管理器控制檯(Package Manager Console) (PMC) (工具/Nuget包管理器菜單),選擇 Acme.BookStore.EntityFrameworkCore.DbMigrations
作爲默認的項目然後執行下面的命令:
這樣就會在Migrations
文件夾中創建一個新的migration類.然後執行Update-Database
命令更新數據庫結構.
PM> Update-Database
添加示例數據
Update-Database
命令在數據庫中創建了AppBooks
表. 打開數據庫並輸入幾個示例行,以便在頁面上顯示它們:
創建應用服務
下一步是創建應用服務來管理(創建,列出,更新,刪除)書籍. 啓動模板中的應用程序層分爲兩個項目:
Acme.BookStore.Application.Contracts
主要包含你的DTO和應用程序服務接口.Acme.BookStore.Application
包含應用程序服務的實現.
BookDto
在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
類一樣具有一些審計屬性.
在將書籍返回到表示層時,需要將Book
實體轉換爲BookDto
對象. AutoMapper庫可以在定義了正確的映射時自動執行此轉換. 啓動模板配置了AutoMapper,因此你只需在Acme.BookStore.Application
項目的BookStoreApplicationAutoMapperProfile
類中定義映射:
using AutoMapper;
namespace Acme.BookStore
{
public class BookStoreApplicationAutoMapperProfile : Profile
{
public BookStoreApplicationAutoMapperProfile()
{
CreateMap<Book, BookDto>();
}
}
}
CreateUpdateBookDto
在Acme.BookStore.Application.Contracts
項目中創建一個名爲CreateUpdateBookDto
的DTO類:
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.AutoMapper;
namespace Acme.BookStore
{
public class CreateUpdateBookDto
{
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
public DateTime PublishDate { get; set; }
[Required]
public float Price { get; set; }
}
}
- 這個DTO類被用於在創建或更新書籍的時候從用戶界面獲取圖書信息.
- 它定義了數據註釋屬性(如
[Required]
)來定義屬性的驗證. DTO由ABP框架自動驗證.
就像上面的BookDto
一樣,創建一個從CreateUpdateBookDto
對象到Book
實體的映射:
CreateMap<CreateUpdateBookDto, Book>();
IBookAppService
在Acme.BookStore.Application.Contracts
項目中定義一個名爲IBookAppService
的接口:
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.BookStore
{
public interface IBookAppService :
ICrudAppService< //定義了CRUD方法
BookDto, //用來展示書籍
Guid, //Book實體的主鍵
PagedAndSortedResultRequestDto, //獲取書籍的時候用於分頁和排序
CreateUpdateBookDto, //用於創建書籍
CreateUpdateBookDto> //用於更新書籍
{
}
}
- 框架定義應用程序服務的接口不是必需的. 但是,它被建議作爲最佳實踐.
ICrudAppService
定義了常見的CRUD方法:GetAsync
,GetListAsync
,CreateAsync
,UpdateAsync
和DeleteAsync
. 你可以從空的IApplicationService
接口繼承並手動定義自己的方法.ICrudAppService
有一些變體, 你可以在每個方法中使用單獨的DTO,也可以分別單獨指定.
BookAppService
在Acme.BookStore.Application
項目中實現名爲BookAppService
的IBookAppService
:
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
}
}
BookAppService
繼承了CrudAppService<...>
.它實現了上面定義的CRUD方法.BookAppService
注入IRepository <Book,Guid>
,這是Book
實體的默認倉儲. ABP自動爲每個聚合根(或實體)創建默認倉儲. 請參閱倉儲文檔BookAppService
使用IObjectMapper
將Book
對象轉換爲BookDto
對象, 將CreateUpdateBookDto
對象轉換爲Book
對象. 啓動模板使用AutoMapper庫作爲對象映射提供程序. 你之前定義了映射, 因此它將按預期工作.
自動生成API Controllers
你通常創建Controller以將應用程序服務公開爲HTTP API端點. 因此允許瀏覽器或第三方客戶端通過AJAX調用它們. ABP可以自動按照慣例將你的應用程序服務配置爲MVC API控制器.
Swagger UI
啓動模板配置爲使用Swashbuckle.AspNetCore運行swagger UI. 運行應用程序並在瀏覽器中輸入https://localhost:XXXX/swagger/
(用您自己的端口替換XXXX)作爲URL.
你會看到一些內置的接口和Book
的接口,它們都是REST風格的:
Swagger有一個很好的UI來測試API. 你可以嘗試執行[GET] /api/app/book
API來獲取書籍列表.
動態JavaScript代理
在Javascript端通過AJAX的方式調用HTTP API接口是很常見的,你可以使用$.ajax
或者其他的工具來調用接口.當然,ABP中提供了更好的方式.
ABP 自動 爲所有的API接口創建了JavaScript 代理.因此,你可以像調用 JavaScript function一樣調用任何接口.
在瀏覽器的開發者控制檯中測試接口
你可以使用你鍾愛的瀏覽器的 開發者控制檯 中輕鬆測試JavaScript代理.運行程序,並打開瀏覽器的 開發者工具(快捷鍵:F12),切換到 Console 標籤,輸入下面的代碼並回車:
acme.bookStore.book.getList({}).done(function (result) { console.log(result); });
acme.bookStore
是BookAppService
的命名空間,轉換成了駝峯命名.book
是BookAppService
轉換後的名字(去除了AppService後綴並轉成了駝峯命名).getList
是定義在AsyncCrudAppService
基類中的GetListAsync
方法轉換後的名字(去除了Async後綴並轉成了駝峯命名).{}
參數用於將空對象發送到GetListAsync
方法,該方法通常需要一個類型爲PagedAndSortedResultRequestDto
的對象,用於向服務器發送分頁和排序選項(所有屬性都是可選的,所以你可以發送一個空對象).getList
方法返回了一個promise
.因此,你可以傳遞一個回調函數到done
(或者then
)方法中來獲取服務返回的結果.
我們使用create
方法 創建一本新書:
acme.bookStore.book.create({
name: 'Foundation',
type: 7,
publishDate: '1951-05-24',
price: 21.5
}).done(function(result){
console.log('successfullycreatedthebookwithid: '+result.id);
});
你會看到控制檯會顯示類似這樣的輸出:
successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7
檢查數據庫中的Books
表以查看新書. 你可以自己嘗試get
,update
和delete
功能.
創建書籍頁面
現在我們來創建一些可見和可用的東西,取代經典的MVC,我們使用微軟推薦的Razor Pages UI.
在 Acme.BookStore.Web
項目的Pages
文件夾下創建一個新的文件夾叫Books
並添加一個名爲Index.cshtml
的Razor Page.
打開Index.cshtml
並把內容修改成下面這樣:
@page
@using Acme.BookStore.Web.Pages.Books
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model IndexModel
<h2>Books</h2>
- 此代碼更改了Razor View Page Model的默認繼承,因此它從
BookStorePage
類(而不是PageModel
)繼承.啓動模板附帶的BookStorePage
類,提供所有頁面使用的一些共享屬性/方法. - 確保
IndexModel
(Index.cshtml.cs)具有Acme.BookStore.Pages.Books
命名空間,或者在Index.cshtml
中更新它.
將Books頁面添加到主菜單
打開Menus
文件夾中的 BookStoreMenuContributor
類,在ConfigureMainMenuAsync
方法的底部添加如下代碼:
context.Menu.AddItem(
new ApplicationMenuItem("BooksStore", l["Menu:BookStore"])
.AddItem(new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books"))
);
本地化菜單
本地化文本位於Acme.BookStore.Domain.Shared
項目的Localization/BookStore
文件夾下:
打開en.json
文件,將Menu:BookStore
和Menu:Books
鍵的本地化文本添加到文件末尾:
{
"culture": "en",
"texts": {
"Menu:BookStore": "Book Store",
"Menu:Books": "Books"
}
}
- ABP的本地化功能建立在ASP.NET Core's standard localization之上並增加了一些擴展.查看本地化文檔.
- 本地化key是任意的. 你可以設置任何名稱. 我們更喜歡爲菜單項添加
Menu:
前綴以區別於其他文本. 如果未在本地化文件中定義文本,則它將返回到本地化的key(ASP.NET Core的標準行爲).
運行該應用程序,看到新菜單項已添加到頂部欄:
點擊Books菜單項就會跳轉到新增的書籍頁面.
書籍列表
我們將使用Datatables.netJQuery插件來顯示頁面上的表格列表. 數據表可以完全通過AJAX工作,速度快,並提供良好的用戶體驗. Datatables插件在啓動模板中配置,因此你可以直接在任何頁面中使用它,而需要在頁面中引用樣式和腳本文件.
Index.cshtml
將Pages/Books/Index.cshtml
改成下面的樣子:
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model Acme.BookStore.Web.Pages.Books.IndexModel
@section scripts
{
<abp-script src="/Pages/Books/index.js" />
}
<abp-card>
<abp-card-header>
<h2>@L["Books"]</h2>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="BooksTable">
<thead>
<tr>
<th>@L["Name"]</th>
<th>@L["Type"]</th>
<th>@L["PublishDate"]</th>
<th>@L["Price"]</th>
<th>@L["CreationTime"]</th>
</tr>
</thead>
</abp-table>
</abp-card-body>
</abp-card>
abp-script
tag helper用於將外部的 腳本 添加到頁面中.它比標準的script
標籤多了很多額外的功能.它可以處理 最小化和 版本.查看捆綁 & 壓縮文檔獲取更多信息.abp-card
和abp-table
是爲Twitter Bootstrap的card component封裝的 tag helpers.ABP中有很多tag helpers,可以很方便的使用大多數bootstrap組件.你也可以使用原生的HTML標籤代替tag helpers.使用tag helper可以通過智能提示和編譯時類型檢查減少HTML代碼並防止錯誤.查看tag helpers 文檔.- 你可以像上面本地化菜單一樣 本地化 列名.
添加腳本文件
在Pages/Books/
文件夾中創建 index.js
文件
index.js
的內容如下:
$(function () {
var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({
ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList),
columnDefs: [
{ data: "name" },
{ data: "type" },
{ data: "publishDate" },
{ data: "price" },
{ data: "creationTime" }
]
}));
});
abp.libs.datatables.createAjax
是幫助ABP的動態JavaScript API代理跟Datatable的格式相適應的輔助方法.abp.libs.datatables.normalizeConfiguration
是另一個輔助方法.不是必須的, 但是它通過爲缺少的選項提供常規值來簡化數據表配置.acme.bookStore.book.getList
是獲取書籍列表的方法(上面已經介紹過了)- 查看 Datatable文檔 瞭解更多配置項。
下一章
點擊查看 下一章 的介紹.