入門系列-ASP.NET Core MVC 介紹 - 第一章

關於本教程

在本系列教程中, 你將構建一個用於管理書籍及其作者列表的應用程序. Entity Framework Core(EF Core)將用作ORM提供者,因爲它是默認數據庫提供者.

這是本教程所有章節中的第一章,下面是所有的章節:

你可以從GitHub存儲庫訪問應用程序的源代碼。你也可以觀看由ABP社區成員爲本教程錄製的視頻課程

創建項目

創建一個名爲Acme.BookStore的新項目, 創建數據庫並按照入門文檔運行應用程序。

解決方案的結構

下面的圖片展示了從啓動模板創建的項目是如何分層的。

bookstore-visual-studio-solution

你可以查看應用程序模板文檔以詳細瞭解解決方案結構.但是,你將通過本教程瞭解基礎知識.

創建Book實體

啓動模板中的域層分爲兩個項目:

  • Acme.BookStore.Domain包含你的實體領域服務和其他核心域對象.
  • Acme.BookStore.Domain.Shared包含可與客戶共享的常量,枚舉或其他域相關對象.

在解決方案的領域層(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爲實體提供了兩個基本的基類: AggregateRootEntityAggregate Root域驅動設計(DDD) 概念之一. 有關詳細信息和最佳做法,請參閱實體文檔.
  • Book實體繼承了AuditedAggregateRoot,AuditedAggregateRoot類在AggregateRoot類的基礎上添加了一些審計屬性(CreationTimeCreatorIdLastModificationTime 等).
  • GuidBook實體的主鍵類型.
  • 使用 數據註解 爲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作爲默認的項目然後執行下面的命令:

bookstore-pmc-add-book-migration

這樣就會在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,UpdateAsyncDeleteAsync. 你可以從空的IApplicationService接口繼承並手動定義自己的方法.
  • ICrudAppService有一些變體, 你可以在每個方法中使用單獨的DTO,也可以分別單獨指定.

BookAppService

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

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使用IObjectMapperBook對象轉換爲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風格的:

bookstore-swagger

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.bookStoreBookAppService的命名空間,轉換成了駝峯命名.
  • bookBookAppService轉換後的名字(去除了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,updatedelete功能.

創建書籍頁面

現在我們來創建一些可見和可用的東西,取代經典的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:BookStoreMenu:Books鍵的本地化文本添加到文件末尾:

{
  "culture": "en",
  "texts": {
    "Menu:BookStore": "Book Store",
    "Menu:Books": "Books"
  }
}
  • ABP的本地化功能建立在ASP.NET Core's standard localization之上並增加了一些擴展.查看本地化文檔.
  • 本地化key是任意的. 你可以設置任何名稱. 我們更喜歡爲菜單項添加Menu:前綴以區別於其他文本. 如果未在本地化文件中定義文本,則它將返回到本地化的key(ASP.NET Core的標準行爲).

運行該應用程序,看到新菜單項已添加到頂部欄:

bookstore-menu-items

點擊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文件

bookstore-index-js-file

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文檔 瞭解更多配置項。

下一章

點擊查看 下一章 的介紹.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章