在上一篇如何使用ASP.NET Core、EF Core、ABP(ASP.NET Boilerplate)創建分層的Web應用程式(第一部分)我們介紹了第一部分,這一篇是接著上一篇的內容寫的。 創建Person實體 添加一個Person實體,用於分配任務: 這次我設置主鍵Id的類型為Guid,為了進 ...
在上一篇如何使用ASP.NET Core、EF Core、ABP(ASP.NET Boilerplate)創建分層的Web應用程式(第一部分)我們介紹了第一部分,這一篇是接著上一篇的內容寫的。
創建Person實體
添加一個Person實體,用於分配任務:
[Table("AppPersons")] public class Person:AuditedEntity<Guid> { public const int MaxNameLength = 32; [Required] [MaxLength(MaxNameLength)] public string Name { get; set; } public Person() { } public Person(string name) { Name = name; } }
這次我設置主鍵Id的類型為Guid,為了進行演示,Person類繼承了AuditedEntity(它具有CreationTime、CreaterUserId、LastModificationTime和LastModifierUserId屬性)。
關聯Person與Task
向Task實體添加了AssignedPerson屬性
[Table("AppTasks")] public class Task : Entity, IHasCreationTime { public const int MaxTitleLength = 256; public const int MaxDescriptionLength = 64 * 1024; //64KB [ForeignKey(nameof(AssignedPersonId))] public Person AssignedPerson { get; set; } public Guid? AssignedPersonId { get; set; } [Required] [MaxLength(MaxTitleLength)] public string Title { get; set; } [MaxLength(MaxDescriptionLength)] public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; } public Task() { CreationTime = Clock.Now; State = TaskState.Open; } public Task(string title, string description = null,Guid? assignedPersonId=null) : this() { Title = title; Description = description; AssignedPersonId = assignedPersonId; } }
AssignedPerson是可選的。因此,任務可以分配給一個人,也可以不分配。
添加Person到DbContext
public DbSet<Person> People { get; set; }
為Person實體添加新的遷移
在包管理器控制台執行以下命令
它在項目中創建了一個新的遷移類
using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Acme.SimpleTaskSystem.Migrations { public partial class Added_Person : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn<Guid>( name: "AssignedPersonId", table: "AppTasks", nullable: true); migrationBuilder.CreateTable( name: "AppPersons", columns: table => new { Id = table.Column<Guid>(nullable: false), CreationTime = table.Column<DateTime>(nullable: false), CreatorUserId = table.Column<long>(nullable: true), LastModificationTime = table.Column<DateTime>(nullable: true), LastModifierUserId = table.Column<long>(nullable: true), Name = table.Column<string>(maxLength: 32, nullable: false) }, constraints: table => { table.PrimaryKey("PK_AppPersons", x => x.Id); }); migrationBuilder.CreateIndex( name: "IX_AppTasks_AssignedPersonId", table: "AppTasks", column: "AssignedPersonId"); migrationBuilder.AddForeignKey( name: "FK_AppTasks_AppPersons_AssignedPersonId", table: "AppTasks", column: "AssignedPersonId", principalTable: "AppPersons", principalColumn: "Id", onDelete: ReferentialAction.SetNull); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropForeignKey( name: "FK_AppTasks_AppPersons_AssignedPersonId", table: "AppTasks"); migrationBuilder.DropTable( name: "AppPersons"); migrationBuilder.DropIndex( name: "IX_AppTasks_AssignedPersonId", table: "AppTasks"); migrationBuilder.DropColumn( name: "AssignedPersonId", table: "AppTasks"); } } }
我僅僅將ReferentialAction.Restrict 改變為ReferentialAction.SetNull.這樣的話,當我們刪除一個人,那麼分配給那個人的任務就會被設置成未分配。這個 在本次教程中並不重要,但是可以說明如果有需要的情況下,我們是可以修改遷移類中的代碼的。事實上,我們應該每次都檢查一下遷移代碼之後再將其應用到資料庫。
打開資料庫可以看到新加的表和列,這裡可以加一些測試數據:
我們將第一個任務分配給第一個人:
在任務列表中返回分配的人員
將TaskAppService更改為返回分配的人員信息。首先,向TaskListDto添加兩個屬性:
public Guid? AssignedPersonId { get; set; } public string AssignedPersonName { get; set; }
將Task.AssignedPerson屬性添加到查詢方法中,只添加Include行:
public async Task<ListResultDto<TaskListDto>> GetAll(GetAllTasksInput input) { var tasks = await _taskRepository .GetAll() .Include(t => t.AssignedPerson) .WhereIf(input.State.HasValue, t => t.State == input.State.Value) .OrderByDescending(t => t.CreationTime) .ToListAsync(); return new ListResultDto<TaskListDto>( ObjectMapper.Map<List<TaskListDto>>(tasks) ); }
這樣,GetAll方法將返回分配給任務的人員信息。由於我們使用了AutoMapper,新的屬性也將自動複製到DTO。
在任務列表頁面顯示被分配的人員姓名
我們在Tasks\Index下可以修改index.cshtml來顯示AssignedPersonName:
@foreach (var task in Model.Tasks) { <li class="list-group-item"> <span class="pull-right label @Model.GetTaskLabel(task)">@L($"TaskState_{task.State}")</span> <h4 class="list-group-item-heading">@task.Title</h4> <div class="list-group-item-text"> @task.CreationTime.ToString("yyyy-MM-dd HH:mm:ss")|@(task.AssignedPersonName?? L("Unassigned")) </div> </li> }
運行程式,可以看到被分配的任務會顯示人員姓名;
創建任務
前面的內容都是顯示任務列表,接下來我們要做一個創建任務的頁面。首先在ITaskAppService 介面中增加Create方法;
System.Threading.Tasks.Task Create(CreateTaskInput input);
在TaskAppService 類中實現它:
public async System.Threading.Tasks.Task Create(CreateTaskInput input) { var task = ObjectMapper.Map<Task>(input); await _taskRepository.InsertAsync(task); }
創建CreateTaskInput Dto如下所示:
[AutoMapTo(typeof(Task))] public class CreateTaskInput { [Required] [MaxLength(Task.MaxTitleLength)] public string Title { get; set; } [MaxLength(Task.MaxDescriptionLength)] public string Description { get; set; } public Guid? AssignedPersonId { get; set; } }
配置將其映射到任務實體(使用AutoMapTo屬性)並添加數據註釋以應用驗證,這裡的長度和Task實體中的長度一致。
----省略單元測試的內容-----
創建任務頁面
首先在TaskController 中添加Create action
public class TasksController : SimpleTaskSystemControllerBase { private readonly ITaskAppService _taskAppService; private readonly ILookupAppService _lookupAppService; public TasksController(ITaskAppService taskAppService, ILookupAppService lookupAppService) { _taskAppService = taskAppService; _lookupAppService = lookupAppService; } public async Task<ActionResult> Index(GetAllTasksInput input) { var output = await _taskAppService.GetAll(input); var model = new IndexViewModel(output.Items) { SelectedTaskState = input.State }; return View(model); } public async Task<ActionResult> Create() { var peopleSelectListItems = (await _lookupAppService.GetPeopleComboboxItems()).Items .Select(p => p.ToSelectListItem()) .ToList(); peopleSelectListItems.Insert(0, new SelectListItem { Value = string.Empty, Text = L("Unassigned"), Selected = true }); return View(new CreateTaskViewModel(peopleSelectListItems)); } }
我註入了ILookupAppService 以獲得人員列表,雖然這裡可以直接使用IRepository<Person, Guid>,但是這樣可以更好的分層和重用,ILookupAppService.GetPeopleComboboxItems 定義在應用層:
public interface ILookupAppService:IApplicationService { Task<ListResultDto<ComboboxItemDto>> GetPeopleComboboxItems(); }
public class LookupAppService:SimpleTaskSystemAppServiceBase,ILookupAppService { private readonly IRepository<Person, Guid> _personRepository; public LookupAppService(IRepository<Person, Guid> personRepository) { _personRepository = personRepository; } public async Task<ListResultDto<ComboboxItemDto>> GetPeopleComboboxItems() { var people = await _personRepository.GetAllListAsync(); return new ListResultDto<ComboboxItemDto>( people.Select(p => new ComboboxItemDto(p.Id.ToString("D"), p.Name)).ToList() ); } }
ComboboxItemDto是一個簡單的類(在ABP中定義),用於傳輸combobox項數據。TaskController.Create用這個方法將返回的List轉換成SelectListItem列表(在AspNet .Core中定義),並通過CreateTaskViewModel傳遞到視圖:
public class CreateTaskViewModel { public List<SelectListItem> People { get; set; } public CreateTaskViewModel(List<SelectListItem> people) { People = people; } }
創建視圖代碼如下:
@model Acme.SimpleTaskSystem.Web.CreateTaskViewModel @section scripts { <environment names="Development"> <script src="~/js/views/tasks/create.js"></script> </environment> <environment names="Staging,Production"> <script src="~/js/views/tasks/create.min.js"></script> </environment> } <h2> @L("NewTask") </h2> <form id="TaskCreationForm"> <div class="form-group"> <label for="Title">@L("Title")</label> <input type="text" name="Title" class="form-control" placeholder="@L("Title")" required maxlength="@Acme.SimpleTaskSystem.Task.MaxTitleLength"> </div> <div class="form-group"> <label for="Description">@L("Description")</label> <input type="text" name="Description" class="form-control" placeholder="@L("Description")" maxlength="@Acme.SimpleTaskSystem.Task.MaxDescriptionLength"> </div> <div class="form-group"> @Html.Label(L("AssignedPerson")) @Html.DropDownList( "AssignedPersonId", Model.People, new { @class = "form-control", id = "AssignedPersonCombobox" }) </div> <button type="submit" class="btn btn-default">@L("Save")</button> </form>
創建 create.js如下:
(function ($) { $(function () { var _$form = $('#TaskCreationForm'); _$form.find('input:first').focus(); _$form.validate(); _$form.find('button[type=submit]') .click(function (e) { e.preventDefault(); if (!_$form.valid()) { return; } var input = _$form.serializeFormToObject(); abp.services.app.task.create(input) .done(function () { location.href = '/Tasks'; }); }); }); })(jQuery);
create.js做瞭如下事情:
- 為表單準備驗證(使用jquery驗證插件),併在Save按鈕的單擊時驗證它
- 使用serializeFormToObject jquery插件(在jquery擴展中定義)。將表單數據轉換為JSON對象,(Layout.cshtml中引入了 jquery-extensions.js)。
- 用 abp.services.task.create方法去調用TaskAppService.Create方法。這是ABP中的一個重要的特性,我們可以在JavaScript中調用應用程式服務方法,就像調用JavaScript方法一樣
最後在任務列表中增加“Add Task”按鈕以作為增加任務的入口:
<a class="btn btn-primary btn-sm" asp-action="Create">@L("AddNew")</a>
運行程式到創建任務頁面,可以看到頁面如下所示:
到這我們就可以填寫信息點擊Save按鈕保存即可哦。
註:如果不需要Home或者About的直接去掉就可以,ABP框架很靈活,就根據自己的需求修改就ok了。