200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 使用 ASP.NET Core Entity Framework Core 和 ABP 创建N层Web应用 第二篇

使用 ASP.NET Core Entity Framework Core 和 ABP 创建N层Web应用 第二篇

时间:2020-01-26 01:06:28

相关推荐

使用 ASP.NET Core  Entity Framework Core 和 ABP 创建N层Web应用 第二篇

介绍

这是“使用 Core ,Entity Framework Core 和 Boilerplate 创建N层 Web 应用”系列文章的第二篇。以下可以看其他篇目:

使用 Core ,Entity Framework Core 和 Boilerplate 创建N层 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 类型。同时,这次不从 base Entity 继承,而是从 AuditedEntity 继承 (该类定义了多个常用属性 创建时间CreationTime, 创建者用户Id CreaterUserId, 最后修改时间 LastModificationTime 和最后修改人Id LastModifierUserId)

关联 Person 到 Task 实体

我们同时将 责任人 AssignedPerson 属性添加到 任务 Task 实体中(如下代码只粘贴修改的部分)

代码如下

[Table("AppTasks")]

public class Task : Entity, IHasCreationTime

{

//...

[ForeignKey(nameof(AssignedPersonId))]

public Person AssignedPerson { get; set; }

public Guid? AssignedPersonId { get; set; }

public Task(string title, string description = null, Guid? assignedPersonId = null)

: this()

{

Title = title;

Description = description;

AssignedPersonId = assignedPersonId;

}

}

责任人 AssignedPerson 是可选的。所以,任务可以指派给责任人或者不指派

添加 Person 到 数据库上下文 DbContext

最后,我们添加新的责任人 Person 实体到 DbContext 类中:

代码如下

public class SimpleTaskAppDbContext : AbpDbContext

{

public DbSet<Person> People { get; set; }

//...

}

添加 Person 实体的新迁移文件

现在,我们在 源包管理控制台 Package Manager Console 中执行迁移命令,如图所示

该命令将会在项目里创建新的数据迁移类。

代码如下

public partial class Added_Person : Migration

{

protected override void Up(MigrationBuilder migrationBuilder)

{

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.AddColumn<Guid>(

name: "AssignedPersonId",

table: "AppTasks",

nullable: true);

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);

}

//...

}

该类为自动生成的,我们只是将 ReferentialAction.Restrict 修改为 ReferentialAction.SetNull 。它的作用是:当我们删除一个责任人的时候,分配给这个人的任务会变成为分配。在这个 demo 里,这并不重要。我们只是想告诉你,如果有必要的话,迁移类的代码是可以修改的。实际上,我们总是应该在执行到数据库之前,重新阅读生成的代码。

之后,我们可以对我们的数据库执行迁移了。如下图:(更多迁移相关信息请参照 entity framework documentation)

当我们打开数据库的时候,我们可以看到表和字段都已经创建完毕了,我们可以添加一些测试数据。如下图:

我们添加一个责任人并分配第一个任务给他。如下图:

返回任务列表中的责任人 Person

我们将修改 TaskAppService ,使之可以返回责任人信息。首先,我们在 TaskListDto 中添加2个属性。

代码如下 (只显示有变动的代码,如需看完整代码请参考第一篇,下同)

[AutoMapFrom(typeof(Task))]

public class TaskListDto : EntityDto, IHasCreationTime

{

//...

public Guid? AssignedPersonId { get; set; }

public string AssignedPersonName { get; set; }

}

同时将 Task.AssignedPerson 属性添加到查询里,仅添加 Include 行即可

代码如下

public class TaskAppService : SimpleTaskAppAppServiceBase, ITaskAppService

{

//...

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 里。

单元测试责任人 Person

在这里,我们修改单元测试,(对测试不感兴趣者可直接跳过)看看获取任务列表时是否能获取到责任人。首先,我们修改 TestDataBuilder 类里的初始化测试数据,分配任务给责任人。

代码如下

public class TestDataBuilder

{

//...

public void Build()

{

var neo = new Person("Neo");

_context.People.Add(neo);

_context.SaveChanges();

_context.Tasks.AddRange(

new Task("Follow the white rabbit", "Follow the white rabbit in order to know the reality.", neo.Id),

new Task("Clean your room") { State = pleted }

);

}

}

然后我们修改 TaskAppService_Tests.Should_Get_All_Tasks() 方法,检查是否有一个任务已经指派了责任人(请看代码最后一行)

代码如下

[Fact]

public async System.Threading.Tasks.Task Should_Get_All_Tasks()

{

//Act

var output = await _taskAppService.GetAll(new GetAllTasksInput());

//Assert

output.Items.Count.ShouldBe(2);

output.Items.Count(t => t.AssignedPersonName != null).ShouldBe(1);

}

友情提示:扩张方法 Count 需要使用 using System.Linq 语句。

在任务列表页展示责任人的名字

最后,我们修改 Task\Index.cshtml 来展示 责任人的名字 AssignedPersonName 。

代码如下

@foreach (var task in Model.Tasks)

{

<li class="list-group-item">

<span class="pull-right label label-lg @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 方法。

代码如下

public interface ITaskAppService : IApplicationService

{

//...

System.Threading.Tasks.Task Create(CreateTaskInput input);

}

然后在 TaskAppService 类里实现它

代码如下

public class TaskAppService : SimpleTaskAppAppServiceBase, ITaskAppService

{

private readonly IRepository<Task> _taskRepository;

public TaskAppService(IRepository<Task> taskRepository)

{

_taskRepository = taskRepository;

}

//...

public async System.Threading.Tasks.Task Create(CreateTaskInput input)

{

var task = ObjectMapper.Map<Task>(input);

await _taskRepository.InsertAsync(task);

}

}

Create 方法会自动映射输入参数 input 到task 实体,之后我们使用仓储 repository 来将任务实体插入数据库中。让我们来看看输入参数 input 的 CreateTaskInput DTO 。

代码如下

using System;

using ponentModel.DataAnnotations;

using Abp.AutoMapper;

namespace Acme.SimpleTaskApp.Tasks.Dtos

{

[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; }

}

}

我们将DTO配置为映射到任务 Task 实体(使用 AutoMap 特性),同时添加数据验证 validation。我们使用任务 Task 实体的常量来同步设置最大字串长度。

测试任务创建服务

我们添加 TaskAppService_Tests 类的集成测试来测试 Create 方法:(如果对测试不感兴趣者可以跳过这个部分)

代码如下

using Acme.SimpleTaskApp.Tasks;

using Acme.SimpleTaskApp.Tasks.Dtos;

using Shouldly;

using Xunit;

using System.Linq;

using Abp.Runtime.Validation;

namespace Acme.SimpleTaskApp.Tests.Tasks

{

public class TaskAppService_Tests : SimpleTaskAppTestBase

{

private readonly ITaskAppService _taskAppService;

public TaskAppService_Tests()

{

_taskAppService = Resolve<ITaskAppService>();

}

//...

[Fact]

public async System.Threading.Tasks.Task Should_Create_New_Task_With_Title()

{

await _taskAppService.Create(new CreateTaskInput

{

Title = "Newly created task #1"

});

UsingDbContext(context =>

{

var task1 = context.Tasks.FirstOrDefault(t => t.Title == "Newly created task #1");

task1.ShouldNotBeNull();

});

}

[Fact]

public async System.Threading.Tasks.Task Should_Create_New_Task_With_Title_And_Assigned_Person()

{

var neo = UsingDbContext(context => context.People.Single(p => p.Name == "Neo"));

await _taskAppService.Create(new CreateTaskInput

{

Title = "Newly created task #1",

AssignedPersonId = neo.Id

});

UsingDbContext(context =>

{

var task1 = context.Tasks.FirstOrDefault(t => t.Title == "Newly created task #1");

task1.ShouldNotBeNull();

task1.AssignedPersonId.ShouldBe(neo.Id);

});

}

[Fact]

public async System.Threading.Tasks.Task Should_Not_Create_New_Task_Without_Title()

{

await Assert.ThrowsAsync<AbpValidationException>(async () =>

{

await _taskAppService.Create(new CreateTaskInput

{

Title = null

});

});

}

}

}

第一个测试创建了一个带 title 的任务, 第二个测试创建了一个带 title 和 责任人 的测试,最后一个测试创建了一个无效的任务来展示 exception 例子。

任务创建页面

我们现在知道 TaskAppService.Create 方法可以正常工作了。现在,我们可以创建一个页面来添加新任务了。完成后的效果如下图所示:

首先,我们在任务控制器 TaskController 添加一个 Create action 。

代码如下

using System.Threading.Tasks;

using Abp.Application.Services.Dto;

using Acme.SimpleTaskApp.Tasks;

using Acme.SimpleTaskApp.Tasks.Dtos;

using Acme.SimpleTaskApp.Web.Models.Tasks;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Rendering;

using System.Linq;

using mon;

using Acme.SimpleTaskApp.Web.Models.People;

namespace Acme.SimpleTaskApp.Web.Controllers

{

public class TasksController : SimpleTaskAppControllerBase

{

private readonly ITaskAppService _taskAppService;

private readonly ILookupAppService _lookupAppService;

public TasksController(

ITaskAppService taskAppService,

ILookupAppService lookupAppService)

{

_taskAppService = taskAppService;

_lookupAppService = lookupAppService;

}

//...

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 。ILookupAppService.GetPeopleComboboxItems 在应用层的定义如下:

代码如下

public interface ILookupAppService : IApplicationService

{

Task&lt;ListResultDto&lt;ComboboxItemDto>> GetPeopleComboboxItems();

}

public class LookupAppService : SimpleTaskAppAppServiceBase, ILookupAppService

{

private readonly IRepository&lt;Person, Guid> _personRepository;

public LookupAppService(IRepository&lt;Person, Guid> personRepository)

{

_personRepository = personRepository;

}

public async Task&lt;ListResultDto&lt;ComboboxItemDto>> GetPeopleComboboxItems()

{

var people = await _personRepository.GetAllListAsync();

return new ListResultDto&lt;ComboboxItemDto>(

people.Select(p => new ComboboxItemDto(p.Id.ToString("D"), p.Name)).ToList()

);

}

}

ComboboxItemDto 是一个简单的类(在 ABP 中定义),用于传递下拉框 Combobox 的项目的数据。 TaskController.Create 方法仅使用了这个方法并将返回的列表转换为 SelectListItem (在 AspNet Core 中定义),然后用 CreateTaskViewModel 返回给视图。

代码如下

using System.Collections.Generic;

using Microsoft.AspNetCore.Mvc.Rendering;

namespace Acme.SimpleTaskApp.Web.Models.People

{

public class CreateTaskViewModel

{

public List&lt;SelectListItem> People { get; set; }

public CreateTaskViewModel(List&lt;SelectListItem> people)

{

People = people;

}

}

}

Create 视图如下:

代码如下

@using Acme.SimpleTaskApp.Web.Models.People

@model CreateTaskViewModel

@section scripts

{

&lt;environment names="Development">

&lt;script src="~/js/views/tasks/create.js">&lt;/script>

&lt;/environment>

&lt;environment names="Staging,Production">

&lt;script src="~/js/views/tasks/create.min.js">&lt;/script>

&lt;/environment>

}

&lt;h2>

@L("NewTask")

&lt;/h2>

&lt;form id="TaskCreationForm">

&lt;div class="form-group">

&lt;label for="Title">@L("Title")&lt;/label>

&lt;input type="text" name="Title" class="form-control" placeholder="@L("Title")" required maxlength="@Acme.SimpleTaskApp.Tasks.Task.MaxTitleLength">

&lt;/div>

&lt;div class="form-group">

&lt;label for="Description">@L("Description")&lt;/label>

&lt;input type="text" name="Description" class="form-control" placeholder="@L("Description")" maxlength="@Acme.SimpleTaskApp.Tasks.Task.MaxDescriptionLength">

&lt;/div>

&lt;div class="form-group">

@Html.Label(L("AssignedPerson"))

@Html.DropDownList(

"AssignedPersonId",

Model.People,

new

{

@class = "form-control",

id = "AssignedPersonCombobox"

})

&lt;/div>

&lt;button type="submit" class="btn btn-default">@L("Save")&lt;/button>

&lt;/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);

让我们一起来看看这个 javascript 代码都做了什么:

在表单里预先做好验证(使用 jquery validation插件)准备,并在保存 Save 按钮被点击后进行验证。

使用序列化表格为对象 serializeFormToObject 插件 (在解决方案中的 jquery-extensions.js 中定义), 将表格数据 forum data 转换为 JSON 对象(我们将 jquery-extensions.js 添加到最后的脚本文件 _Layout.cshtml )

使用 abp.services.task.create 方法调用 TaskAppService.Create 方法。这是 ABP 中的一个很重要的特性。我们可以在 javascript 代码中使用应用服务,简单的就想在代码里直接调用 javascript 方法 (详情请见 details)

最后,我们在任务列表页面里添加一个 “添加任务 Add Task”按钮,点击后就可以导航到任务创建页面:

代码如下

1 &lt;a class="btn btn-primary btn-sm" asp-action="Create">@L("AddNew")&lt;/a>

删除主页和关于页

如果我们不需要主页和关于页,我们可以从应用里删除掉它们。首先,删除主页控制器 HomeController :

代码如下

using Microsoft.AspNetCore.Mvc;

namespace Acme.SimpleTaskApp.Web.Controllers

{

public class HomeController : SimpleTaskAppControllerBase

{

public ActionResult Index()

{

return RedirectToAction("Index", "Tasks");

}

}

}

然后删除 视图里的主页 Views/Home 文件夹并从 SimpleTaskAppNavigationProvider 类里删除菜单项。我们也可以从本地化 JSON 文件中删除点不需要的关键词。

其他相关内容

我们将不断改进本篇内容

从任务列表里打开/关闭任务,然后刷新任务项目。

为责任人下拉框使用组件

等等

文章更改历史

-07-30:将文章中的 ListResultOutput 替换为 ListResultDto

-06-02:将项目和文章修改为支持 .net core

-08-09:根据反馈修改文章

-08-08:初次发布文章

相关文章:

手把手引进门之 Core & Entity Framework Core(官方教程翻译版 版本3.2.5)

ABP .Net Core Entity Framework迁移使用MySql数据库

[52ABP实战系列] .NET CORE实战入门第三章更新了

ABP从入门到精通(1):aspnet-zero-core项目启动及各项目源码说明

ABP从入门到精通(2):aspnet-zero-core 使用MySql数据库

ABP从入门到精通(3):aspnet-zero-core 使用Redis缓存

ABP从入门到精通(4):使用基于JWT标准的Token访问WebApi

ABP从入门到精通(5):.扩展国际化语言资源

原文地址:/yabu007/p/8117792.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。