ABP 教程文檔 1-1 手把手引進門之 ASP.NET Core & Entity Framework Core(官方教程翻譯版 版本3.2.5)

本文是ABP官方文檔翻譯版,翻譯基於 3.2.5 版本javascript

官方文檔分四部分html

1、 教程文檔前端

2、ABP 框架java

3、zero 模塊jquery

4、其餘(中文翻譯資源)git

 

本篇是第一部分的第一篇。github

第一部分分三篇web

1-1 手把手引進門數據庫

1-2 進階apache

1-3 雜項 (相關理論知識)

 

第一篇含兩個步驟。

1-1-1 ASP.NET Core & Entity Framework Core 後端(內核)含兩篇 (第二篇連接

1-1-2 ASP.NET MVC, Web API, EntityFramework & AngularJs  前端

 

如今進入正文 

使用 ASP.NET Core, Entity Framework Core 和 ASP.NET Boilerplate 建立N層Web應用

土牛語錄:Halil ibrahim Kalkan30 Jul 2017

如下是手把手引進門教程,基於 ASP.NET Core, Entity Framework Core ,ABP 框架 建立Web 應用, PS: 自帶自動的測試模塊哦。

本文目錄以下:

介紹

  前期準備

建立應用

正式開發

  建立任務實體 Entity

  將任務添加到數據庫上下文 DbContext

  建立第一個數據遷移

  建立數據庫

  編寫任務服務

  測試任務服務

  任務列表展現

    添加菜單

    建立任務 Controller 和 視圖模型

    任務列表頁面

    本地化

    任務過濾

    任務列表頁面的自動化測試

其餘相關內容

文章修改歷史

版權全部

 

介紹

這是系列文章的第一部分:使用 ASP.NET Core, Entity Framework Core 和 ASP.NET Boilerplate 建立N層Web應用 

在本文中,我將指導你們建立一個樣例(跨平臺的多層Web應用),該樣例會用到以下工具(請讀者提早準備):

 ABP 框架中會默認使用 Log4Net 和 AutoMapper 。

咱們同時還會使用如下技術:

演示的開發項目是一個簡單的任務管理應用,用於將任務分配出去。我不會一層一層的進行開發,而是隨着應用的拓展直接切換所需的層次。隨着應用的推拓展,我將會介紹所需的ABP和其餘框架的特性。

 

前期準備

開發樣例時須要如下工具,請提早在你的機器上進行安裝:

建立應用

首先使用ABP模版(http://www.aspnetboilerplate.com/Templates)建立一個web應用項目,命名爲"Acme.SimpleTaskApp" 。建立模板時能夠設置本身的公司名稱(好比Acme)。

本樣例使用MPA(Multi Page Web Application)多頁面模式(注:即便用MVC和Razor技術)進行開發,本文不使用SPA(注:土牛的SPA是使用Angular)單頁面模式。同時爲了使用最基礎的開發模板功能,本文不使用Module Zero模塊。

 

ABP 模版會建立一個多層的解決方案,以下圖:

 

模板會根據輸入的名字自動建立6個項目。

  • core  領域層/業務層,包含實體Entity,領域服務 domain service 等等
  • Application 應用層 , 包含DTO,應用服務 application service 等等
  • Entity Framework 基礎設施層 ,EF core 數據庫集成處理 (從其餘層抽象出來的EF core)
  • Web 展現層 , 即Asp.net MVC層
  • Tests 單元測試和集成測試,含應用層,領域層,基礎設施層,不含Web展現層
  • Web.Tests ASP.NET Core集成測試,包含web展現層的所有集成測試

以上是沒有選擇zero的項目結果,若是你選擇了zero,項目結構就會變成下圖:

當你把應用運行起來後,你會看到下圖所示的用戶界面:

 

這個應用包含一個頂級菜單欄,包含空的首頁,關於頁,還有一個語言的下拉選項。

 

正式開發

建立任務實體 Entity

咱們從建立一個簡單的任務實體 Task Entity 開始,因爲它屬於領域層,把它加到 core 項目裏。

 

代碼以下: 

 1 using System;
 2 using System.ComponentModel.DataAnnotations;
 3 using System.ComponentModel.DataAnnotations.Schema;
 4 using Abp.Domain.Entities;
 5 using Abp.Domain.Entities.Auditing;
 6 using Abp.Timing;
 7 
 8 namespace Acme.SimpleTaskApp.Tasks
 9 {
10     [Table("AppTasks")]
11     public class Task : Entity, IHasCreationTime
12     {
13         public const int MaxTitleLength = 256;
14         public const int MaxDescriptionLength = 64 * 1024; //64KB
15 
16         [Required]
17         [MaxLength(MaxTitleLength)]
18         public string Title { get; set; }
19 
20         [MaxLength(MaxDescriptionLength)]
21         public string Description { get; set; }
22 
23         public DateTime CreationTime { get; set; }
24 
25         public TaskState State { get; set; }
26 
27         public Task()
28         {
29             CreationTime = Clock.Now;
30             State = TaskState.Open;
31         }
32 
33         public Task(string title, string description = null)
34             : this()
35         {
36             Title = title;
37             Description = description;
38         }
39     }
40 
41     public enum TaskState : byte
42     {
43         Open = 0,
44         Completed = 1
45     }
46 }
View Code

 

  • Task 實體從 ABP 的 Entity 基類繼承,Entity 基類默認ID屬性是 int 類型。若是主鍵類型爲非 int 類型,也能夠選擇範型版本的 Entity<TPrimaryKey>.
  • IHasCreationTime 是一個簡單的接口,只定義了 CreationTime 屬性 (統一規範 CreationTime 的名字)
  • Task 實體定義了一個必填的 Title 和 非必填的 Description
  • TaskState 是一個簡單枚舉,定義了 Task 任務的狀態
  • Clock.Now 返回默認的 DateTime.Now 。但它提供了一個抽象方法,使得咱們能夠在未來有須要的時候很輕鬆就能夠轉換爲 DateTime.UtcNow 。在 ABP 框架中老是使用Clock.Now 而不使用 DateTime.Now 。
  • 將 Task 實體存儲到數據庫的 AppTasks 表中。

 

將任務添加到數據庫上下文 DbContext

.EntityFrameworkCore 包含一個預約義的 DbContext 。將 Task 實體的 DbSet 加到 DbContext 裏。

代碼以下:

 1 public class SimpleTaskAppDbContext : AbpDbContext
 2 {
 3     public DbSet&lt;Task> Tasks { get; set; }
 4 
 5     public SimpleTaskAppDbContext(DbContextOptions&lt;SimpleTaskAppDbContext> options) 
 6         : base(options)
 7     {
 8 
 9     }
10 }
View Code

 

如今,EF core 知道咱們有了一個 Task 的實體。

 

建立第一個數據遷移

咱們將建立一個初始化數據庫遷移文件,它會自動建立數據庫和數據庫表 AppTasks 。打開源管理器 Package Manager Console from Visual Studio , 執行 Add-Migration 命令(默認的項目必須是 .EntityFrameworkCore 項目),如圖:

這個命令會在 . EntityFrameworkCore 項目下建立一個遷移( Migrations )文件夾,文件夾包含一個遷移類和數據庫模型的快照,如圖:

 

以下代碼所示,自動建立了 「初始化 ( Initial )」遷移類:

 1 public partial class Initial : Migration
 2 {
 3     protected override void Up(MigrationBuilder migrationBuilder)
 4     {
 5         migrationBuilder.CreateTable(
 6             name: "AppTasks",
 7             columns: table => new
 8             {
 9                 Id = table.Column&lt;int>(nullable: false)
10                     .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
11                 CreationTime = table.Column&lt;DateTime>(nullable: false),
12                 Description = table.Column&lt;string>(maxLength: 65536, nullable: true),
13                 State = table.Column&lt;byte>(nullable: false),
14                 Title = table.Column&lt;string>(maxLength: 256, nullable: false)
15             },
16             constraints: table =>
17             {
18                 table.PrimaryKey("PK_AppTasks", x => x.Id);
19             });
20     }
21 
22     protected override void Down(MigrationBuilder migrationBuilder)
23     {
24         migrationBuilder.DropTable(
25             name: "AppTasks");
26     }
27 }
View Code

當咱們執行數據庫遷移命令時,這些代碼會建立 AppTasks 表 (更多遷移相關信息請參照  entity framework documentation )

 

建立數據庫

以上的遷移執行完畢後(注:Add-Migration 命令執行後),在包管理控制檯中執行 Update-Database 命令,以下圖:

 

 

這個命令將在 local SQL Server 中建立一個名爲 「SimpleTaskAppDb」 的數據庫並執行數據庫遷移(此時,咱們只有一個「初始化 ( Initial )」的遷移):

如今,咱們有了 Task 實體,而且在數據庫中有對應的數據庫表, 咱們輸入一些簡單的任務到表裏。

 

友情提示: 數據庫上下文字符串 connection string 在 .web 應用的 appsettings.json 中。 (要換數據庫的本身改一下字符串哦)。

 

編寫任務服務

Application Services 應用層服務用於將領域業務邏輯暴露給展現層。展現層在必要時經過使用 Data Transfer Object 數據傳輸對象(DTO)做用參數調用一個應用服務,應用服務則經過調用領域對象執行一些具體的業務邏輯並在有須要時返回一個DTO給展現層。

咱們在 .Application 項目中建立第一個應用服務 TaskAppService ,該服務將執行與任務相關的應用程序邏輯。首先,咱們先來定義一個app 服務接口:

代碼以下:

1 public interface ITaskAppService : IApplicationService
2 {
3     Task&lt;ListResultDto&lt;TaskListDto>> GetAll(GetAllTasksInput input);
4 }
View Code

 

咱們推薦先定義接口,但不是非這樣作不可。按照慣例,ABP 中全部的應用服務都須要實現 IApplicationService 接口 (它只是一個空的標記接口)。咱們建立了一個 GetAll 方法去查詢任務列表,同時,咱們定義了以下的 DTOs :

 代碼以下: 

 1 public class GetAllTasksInput
 2 {
 3     public TaskState? State { get; set; }
 4 }
 5 
 6 [AutoMapFrom(typeof(Task))]
 7 public class TaskListDto : EntityDto, IHasCreationTime
 8 {
 9     public string Title { get; set; }
10 
11     public string Description { get; set; }
12 
13     public DateTime CreationTime { get; set; }
14 
15     public TaskState State { get; set; }
16 }
View Code
  • GetAllTasksInput DTO 爲 GetAll 應用服務方法定義了一個輸入參數 。 咱們將 狀態 state 定義爲 DTO 對象 而不定義爲方法的參數。 這樣咱們未來須要的時候能夠在這個DTO增長其餘的參數,同時兼容現有的客戶端 (固然咱們也能夠在方法里加一個 state 參數)。
  • TaskListDto 用開返回任務數據。該Dto 從 EntityDto 繼承,EntityDto 只是定義了 Id 屬性(咱們能夠不繼承 EntityDto ,直接本身將 Id 加到咱們的Dto裏)。咱們定義了[AutoMapFrom] 特性來建立 AutoMapper 自動映射任務實體到任務列表Dto TaskListDto 。這個特性在 Abp.AutoMapper nuget 包裏進行了定義。
  • ListResultDto 是一個簡單的類,包含了一個列表(咱們能夠直接返回一個 List<TaskListDto> 列表)。

如今,咱們能夠實現 ITaskAppService 了。

代碼以下:

 1 using System.Collections.Generic;
 2 using System.Linq;
 3 using System.Threading.Tasks;
 4 using Abp.Application.Services.Dto;
 5 using Abp.Domain.Repositories;
 6 using Abp.Linq.Extensions;
 7 using Acme.SimpleTaskApp.Tasks.Dtos;
 8 using Microsoft.EntityFrameworkCore;
 9 
10 namespace Acme.SimpleTaskApp.Tasks
11 {
12     public class TaskAppService : SimpleTaskAppAppServiceBase, ITaskAppService
13     {
14         private readonly IRepository&lt;Task> _taskRepository;
15 
16         public TaskAppService(IRepository&lt;Task> taskRepository)
17         {
18             _taskRepository = taskRepository;
19         }
20 
21         public async Task&lt;ListResultDto&lt;TaskListDto>> GetAll(GetAllTasksInput input)
22         {
23             var tasks = await _taskRepository
24                 .GetAll()
25                 .WhereIf(input.State.HasValue, t => t.State == input.State.Value)
26                 .OrderByDescending(t => t.CreationTime)
27                 .ToListAsync();
28 
29             return new ListResultDto&lt;TaskListDto>(
30                 ObjectMapper.Map&lt;List&lt;TaskListDto>>(tasks)
31             );
32         }
33     }
34 }
View Code

 

  • TaskAppService 該類從 SimpleTaskAppAppServiceBase 繼承,SimpleTaskAppAppServiceBase (從 ABP 的 ApplicationService 類繼承)在模板裏已經自動生成。 TaskAppService 不是必須從 SimpleTaskAppAppServiceBase 繼承,應用服務能夠是普通類。可是 ApplicationService 基類有一些預先注入的服務(就像這裏使用的 ObjectMapper )
  • 咱們使用依賴注入 dependency injection 來獲取數據倉儲 repository 
  • Repositories 數據倉儲用於爲數據實體抽象數據庫操做。ABP 爲每一個實體建立了預約義的數據庫倉儲(就像這裏用到了 IRepository<Task> )用於實現通用的任務。IRepository.GetAll() 方法用於查詢數據實體,它返回了一個 IQueryable 接口。
  • WhereIf 這是 ABP 裏的一個拓展方法,該方法提供了一個 IQueryable.Where 方法的簡便條件語法。
  • ObjectMapper 用於將任務對象列表映射到任務列表Dto對象列表 (基於 Application Service 基類並默認實現 AutoMapper ) 

 

測試任務服務

在建立用戶接口錢,咱們須要測試一下任務應用服務 TaskAppService 。 若是你對自動化測試不感興趣的話,能夠忽略這個部分。

咱們的模板包含 .Tests 項目,這能夠測試咱們的代碼。這個項目不使用 SQL Server數據庫,而是使用EF core 的內存數據庫。因此,咱們能夠不用真實數據庫來進行單元測試。它爲每一個測試建立了單獨的數據庫。因此每一個測試都是隔離的。咱們須要在開始測試前使用 TestDataBuilder 類添加初始測試數據到內存數據庫裏。我修改了 TestDataBuilder 。

代碼以下:

 1 public class TestDataBuilder
 2 {
 3     private readonly SimpleTaskAppDbContext _context;
 4 
 5     public TestDataBuilder(SimpleTaskAppDbContext context)
 6     {
 7         _context = context;
 8     }
 9 
10     public void Build()
11     {
12         _context.Tasks.AddRange(
13             new Task("Follow the white rabbit", "Follow the white rabbit in order to know the reality."),
14             new Task("Clean your room") { State = TaskState.Completed }
15             );
16     }
17 }
View Code

經過樣例項目的源代碼,你能夠看懂 TestDataBuilder 在哪裏用,具體怎麼用。咱們添加2個任務(其中一個已經完成)到數據庫上下文 dbcontext 。咱們能夠假定數據庫中有2個任務,開始編寫測試用例。 第一個繼承測試用來測試 TaskAppService.GetAll 方法。

代碼以下:

 1 public class TaskAppService_Tests : SimpleTaskAppTestBase
 2 {
 3     private readonly ITaskAppService _taskAppService;
 4 
 5     public TaskAppService_Tests()
 6     {
 7         _taskAppService = Resolve&lt;ITaskAppService>();
 8     }
 9 
10     [Fact]
11     public async System.Threading.Tasks.Task Should_Get_All_Tasks()
12     {
13         //Act
14         var output = await _taskAppService.GetAll(new GetAllTasksInput());
15 
16         //Assert
17         output.Items.Count.ShouldBe(2);
18     }
19 
20     [Fact]
21     public async System.Threading.Tasks.Task Should_Get_Filtered_Tasks()
22     {
23         //Act
24         var output = await _taskAppService.GetAll(new GetAllTasksInput { State = TaskState.Open });
25 
26         //Assert
27         output.Items.ShouldAllBe(t => t.State == TaskState.Open);
28     }
29 }
View Code

咱們建立2個不一樣的測試用例來測試 GetAll 方法。如今,咱們打開測試瀏覽器(在VS主菜單的 Test\Windows\Test Explorer 菜單下)開始進行單元測試。

 

全部測試均成功。最後一個咱們如今能夠忽略它,他是一個模板生成的測試。

友情提示: ABP 模板默認安裝使用 xUnit 和 Shouldly 。咱們使用它們編寫咱們的測試。

 

任務列表展現 

如今,咱們肯定 TaskAppService 服務能夠正常工做。 咱們能夠開始建立頁面來展現全部的任務。

 

添加菜單

首先,咱們在頂級菜單上添加一個新的菜單

代碼以下

 1 public class SimpleTaskAppNavigationProvider : NavigationProvider
 2 {
 3     public override void SetNavigation(INavigationProviderContext context)
 4     {
 5         context.Manager.MainMenu
 6             .AddItem(
 7                 new MenuItemDefinition(
 8                     "Home",
 9                     L("HomePage"),
10                     url: "",
11                     icon: "fa fa-home"
12                     )
13             ).AddItem(
14                 new MenuItemDefinition(
15                     "About",
16                     L("About"),
17                     url: "Home/About",
18                     icon: "fa fa-info"
19                     )
20             ).AddItem(
21                 new MenuItemDefinition(
22                     "TaskList",
23                     L("TaskList"),
24                     url: "Tasks",
25                     icon: "fa fa-tasks"
26                     )
27             );
28     }
29 
30     private static ILocalizableString L(string name)
31     {
32         return new LocalizableString(name, SimpleTaskAppConsts.LocalizationSourceName);
33     }
34 }
View Code

模板自帶兩個頁面:首頁和關於頁,如上代碼所示。咱們也能夠修改它們建立新的頁面。但如今咱們不修改首頁和關於頁,咱們建立新的菜單項。

 

建立任務 Controller 和 視圖模型

咱們在 .Web 項目下建立一個新的 controller 類,命名爲 TasksController 。

代碼以下

 1 public class TasksController : SimpleTaskAppControllerBase
 2 {
 3     private readonly ITaskAppService _taskAppService;
 4 
 5     public TasksController(ITaskAppService taskAppService)
 6     {
 7         _taskAppService = taskAppService;
 8     }
 9 
10     public async Task&lt;ActionResult> Index(GetAllTasksInput input)
11     {
12         var output = await _taskAppService.GetAll(input);
13         var model = new IndexViewModel(output.Items);
14         return View(model);
15     }
16 }
View Code
  • TasksController 從 SimpleTaskAppControllerBase ( SimpleTaskAppControllerBase 從 AbpController 繼承)繼承,該類包含應用程序 Controllers 須要的通用基礎代碼。
  • 咱們反射了 ITaskAppService , 以獲取到全部的任務列表。
  • 咱們在 .Web 項目中建立了一個 IndexViewModel 類來將數據展現到視圖上,這樣能夠不直接將 GetAll 方法的結果直接暴露到視圖上。

代碼以下

 1 public class IndexViewModel
 2 {
 3     public IReadOnlyList&lt;TaskListDto> Tasks { get; }
 4 
 5     public IndexViewModel(IReadOnlyList&lt;TaskListDto> tasks)
 6     {
 7         Tasks = tasks;
 8     }
 9 
10     public string GetTaskLabel(TaskListDto task)
11     {
12         switch (task.State)
13         {
14             case TaskState.Open:
15                 return "label-success";
16             default:
17                 return "label-default";
18         }
19     }
20 }
View Code

咱們建立了一個簡單的視圖模型,在它的構造函數中,咱們獲取了一個任務列表(由 ITaskAppService 提供)。同時它還有一個 GetTaskLabel 方法,用於在視圖中經過一個 選擇 Bootstrap 標籤來標示任務。

 

任務列表頁面

最後,完成實際的 Index 視圖。

代碼以下

 1 @model Acme.SimpleTaskApp.Web.Models.Tasks.IndexViewModel
 2 
 3 @{
 4     ViewBag.Title = L("TaskList");
 5     ViewBag.ActiveMenu = "TaskList"; //Matches with the menu name in SimpleTaskAppNavigationProvider to highlight the menu item
 6 }
 7 
 8 &lt;h2>@L("TaskList")&lt;/h2>
 9 
10 &lt;div class="row">
11     &lt;div>
12         &lt;ul class="list-group">
13             @foreach (var task in Model.Tasks)
14             {
15                 &lt;li class="list-group-item">
16                     &lt;span class="pull-right label @Model.GetTaskLabel(task)">@L($"TaskState_{task.State}")&lt;/span>
17                     &lt;h4 class="list-group-item-heading">@task.Title&lt;/h4>
18                     &lt;div class="list-group-item-text">
19                         @task.CreationTime.ToString("yyyy-MM-dd HH:mm:ss")
20                     &lt;/div>
21                 &lt;/li>
22             }
23         &lt;/ul>
24     &lt;/div>
25 &lt;/div>
View Code

咱們使用 Bootstrap 的 list group 組件和定義好的模型來渲染視圖。咱們使用 IndexViewModel.GetTaskLable() 方法來得到任務的標籤類型。渲染後的界面以下圖:

 

本地化

咱們在視圖裏使用 ABP 框架自帶的 L 方法。 它用於本地化語言。咱們在 .Core 項目下的 Localization/Source 文件夾中定義好了本地化字符串,使用 .json 文件。英語版本的本地化語言設置

代碼以下

 1 {
 2   "culture": "en",
 3   "texts": {
 4     "HelloWorld": "Hello World!",
 5     "ChangeLanguage": "Change language",
 6     "HomePage": "HomePage",
 7     "About": "About",
 8     "Home_Description": "Welcome to SimpleTaskApp...",
 9     "About_Description": "This is a simple startup template to use ASP.NET Core with ABP framework.",
10     "TaskList": "Task List",
11     "TaskState_Open": "Open",
12     "TaskState_Completed": "Completed"
13   }
14 }
View Code

模板自帶了大多數的文本,固然,它們能夠刪除掉。在上面的代碼中我只是加了最後的三行。使用 ABP 的本地化是至關的簡單,若是你想了解本地化系統更多的信息,請查閱文檔 localization document

 

任務過濾

正如以前說過的,TaskController 實際上使用的是 GetAllTasksInput ,能夠靈活的過濾任務。咱們能夠添加一個任務列表的下拉菜單來過濾任務。首先,咱們添加一個下拉菜單到視圖上(咱們加到 header 裏):

代碼以下

 1 &lt;h2>
 2     @L("TaskList")
 3     &lt;span class="pull-right">
 4         @Html.DropDownListFor(
 5            model => model.SelectedTaskState,
 6            Model.GetTasksStateSelectListItems(LocalizationManager),
 7            new
 8            {
 9                @class = "form-control",
10                id = "TaskStateCombobox"
11            })
12     &lt;/span>
13 &lt;/h2>
View Code

而後我修改了 IndexViewModel , 增長了 SeletedTaskState 屬性和 GetTaskStateSelectListItems 方法:

代碼以下

 1 public class IndexViewModel
 2 {
 3     //...
 4 
 5     public TaskState? SelectedTaskState { get; set; }
 6 
 7     public List&lt;SelectListItem> GetTasksStateSelectListItems(ILocalizationManager localizationManager)
 8     {
 9         var list = new List&lt;SelectListItem>
10         {
11             new SelectListItem
12             {
13                 Text = localizationManager.GetString(SimpleTaskAppConsts.LocalizationSourceName, "AllTasks"),
14                 Value = "",
15                 Selected = SelectedTaskState == null
16             }
17         };
18 
19         list.AddRange(Enum.GetValues(typeof(TaskState))
20                 .Cast&lt;TaskState>()
21                 .Select(state =>
22                     new SelectListItem
23                     {
24                         Text = localizationManager.GetString(SimpleTaskAppConsts.LocalizationSourceName, $"TaskState_{state}"),
25                         Value = state.ToString(),
26                         Selected = state == SelectedTaskState
27                     })
28         );
29 
30         return list;
31     }
32 }
View Code

咱們也能夠在 controller 裏設置 SelectedTaskState :

代碼以下

1 public async Task&lt;ActionResult> Index(GetAllTasksInput input)
2 {
3     var output = await _taskAppService.GetAll(input);
4     var model = new IndexViewModel(output.Items)
5     {
6         SelectedTaskState = input.State
7     };
8     return View(model);
9 }
View Code

如今,咱們運行程序,能夠看到視圖的右上角有個下拉框,如圖:

咱們添加了下拉框,但它如今還不能用。咱們須要編寫一些簡單的 javascript 代碼,當下拉框內容更改後能夠從新請求/刷新任務列表頁面。咱們在 .Web 項目裏建立了 wwwroot\js\views\tasks\index.js 文件

代碼以下

 1 (function ($) {
 2     $(function () {
 3 
 4         var _$taskStateCombobox = $('#TaskStateCombobox');
 5 
 6         _$taskStateCombobox.change(function() {
 7             location.href = '/Tasks?state=' + _$taskStateCombobox.val();
 8         });
 9 
10     });
11 })(jQuery);
View Code

 咱們首先添加 Bundler & Minifier 擴展程序(這是 ASP.NET Core 項目標配的壓縮文件)來壓縮腳本的大小, 而後開始在視圖裏編寫 javascript :

這將在 .Web 項目中的 bundleconfig.json 中添加如下代碼

代碼以下

1 {
2   "outputFileName": "wwwroot/js/views/tasks/index.min.js",
3   "inputFiles": [
4     "wwwroot/js/views/tasks/index.js"
5   ]
6 }
View Code

同時建立了 script 的壓縮版本

不管我什麼時候修改了index.js , index.min.js 都會自動從新生成。如今,咱們能夠在咱們的頁面裏插入 javascript 文件了。

代碼以下

 1 @section scripts
 2 {
 3     &lt;environment names="Development">
 4         &lt;script src="~/js/views/tasks/index.js">&lt;/script>
 5     &lt;/environment>
 6 
 7     &lt;environment names="Staging,Production">
 8         &lt;script src="~/js/views/tasks/index.min.js">&lt;/script>
 9     &lt;/environment>
10 }
View Code

至此,咱們的視圖將在開發環境下使用 index.js 包,而在生產環境中使用 index.min.js (壓縮版本)包。這是在 ASP.Net Core MVC 項目中通用的作法。

 

任務列表頁面的自動化測試

ASP.NET Core MVC 基礎框架中集成了一個繼承測試模塊。咱們能夠完整的測試咱們的服務端代碼了。若是你對自動化測試不感興趣的話,你能夠忽略這個部分。

ABP 模板中自帶 .Web.Tests 項目。咱們建立一個普通的測試來請求 TaskController.Index , 而後檢查反饋內容:

代碼以下

 1 public class TasksController_Tests : SimpleTaskAppWebTestBase
 2 {
 3     [Fact]
 4     public async System.Threading.Tasks.Task Should_Get_Tasks_By_State()
 5     {
 6         //Act
 7 
 8         var response = await GetResponseAsStringAsync(
 9             GetUrl&lt;TasksController>(nameof(TasksController.Index), new
10                 {
11                     state = TaskState.Open
12                 }
13             )
14         );
15 
16         //Assert
17 
18         response.ShouldNotBeNullOrWhiteSpace();
19     }
20 }
View Code

GetResponseAsStringAsync 和 GetUrl 是 ABP 的 AbpAspNetCoreIntrgratedTestBase 類中頗有用的方法。使用這些快捷方法咱們能夠比較容易的建立請求,若是直接使用客戶端請求(一個 HttpClient 的實例)會相對複雜一些。若是想深刻了解,請參考 ASP.NET Core 的  integration testing documentation

當咱們開始 debug 測試模塊式,咱們能夠看到反饋的 HTML 以下圖

上圖顯示 Index 頁面的反饋很正常。可是,咱們更想知道返回的 HTML 是否正如咱們所預期的那樣。 有不少類庫能夠用來解析 HTML 。ABP 模板的 .Web.Tests 項目預先安裝了其中的一個類庫 AngleSharp 咱們用它來檢查建立的 HTML 代碼。

代碼以下

 1 public class TasksController_Tests : SimpleTaskAppWebTestBase
 2 {
 3     [Fact]
 4     public async System.Threading.Tasks.Task Should_Get_Tasks_By_State()
 5     {
 6         //Act
 7 
 8         var response = await GetResponseAsStringAsync(
 9             GetUrl&lt;TasksController>(nameof(TasksController.Index), new
10                 {
11                     state = TaskState.Open
12                 }
13             )
14         );
15 
16         //Assert
17 
18         response.ShouldNotBeNullOrWhiteSpace();
19 
20         //Get tasks from database
21         var tasksInDatabase = await UsingDbContextAsync(async dbContext =>
22         {
23             return await dbContext.Tasks
24                 .Where(t => t.State == TaskState.Open)
25                 .ToListAsync();
26         });
27 
28         //Parse HTML response to check if tasks in the database are returned
29         var document = new HtmlParser().Parse(response);
30         var listItems = document.QuerySelectorAll("#TaskList li");
31             
32         //Check task count
33         listItems.Length.ShouldBe(tasksInDatabase.Count);
34 
35         //Check if returned list items are same those in the database
36         foreach (var listItem in listItems)
37         {
38             var header = listItem.QuerySelector(".list-group-item-heading");
39             var taskTitle = header.InnerHtml.Trim();
40             tasksInDatabase.Any(t => t.Title == taskTitle).ShouldBeTrue();
41         }
42     }
43 }
View Code

你能夠深刻檢查 HTML 的更多細節。但通常來講,檢查基本的標籤就夠了。

 

其餘相關內容

第二篇 Second article 接着開發這個應用服務。

 (第二篇翻譯連接

文章修改歷史

  • 2017-07-30: 將文章中的 ListResultOutput 替換爲 ListResultDto 
  • 2017-06-02: 將文章和解決方案修改成支持 .net core
  • 2016-08-08: 添加 第二篇 的連接
  • 2016-08-01: 第一次發佈

版權全部

該文章和其中的任何源代碼和文件的版權均歸  The Code Project Open License (CPOL) 全部

相關文章
相關標籤/搜索