介绍

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

  • 使用 ASP.NET Core ,Entity Framework Core 和 ASP.NET 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 = TaskState.Completed }

);

}

}

然后我们修改 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 System.ComponentModel.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 Acme.SimpleTaskApp.Common;

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 文件中删除点不需要的关键词。

其他相关内容

我们将不断改进本篇内容

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

  • 为责任人下拉框使用组件

  • 等等

文章更改历史

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

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

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

  • 2016-08-08:初次发布文章

相关文章:

  • 手把手引进门之 ASP.NET 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):.扩展国际化语言资源

原文地址:http://www.cnblogs.com/yabu007/p/8117792.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

使用 ASP.NET Core, Entity Framework Core 和 ABP 创建N层Web应用 第二篇相关推荐

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

    以下是手把手引进门教程,基于 ASP.NET Core, Entity Framework Core ,ABP 框架 创建Web 应用, PS: 自带自动的测试模块哦. 样例下载 (上 github  ...

  2. ASP.NET Core 入门教程 8、ASP.NET Core + Entity Framework Core 数据访问入门

    ASP.NET Core 入门教程 8.ASP.NET Core + Entity Framework Core 数据访问入门 原文:ASP.NET Core 入门教程 8.ASP.NET Core ...

  3. 尝试.Net Core—使用.Net Core + Entity FrameWork Core构建WebAPI(一)

    想尝试.Net Core很久了,一直没有时间,今天回家,抛开一切,先搭建一个.Net Core的Demo出来玩玩. 废话少说,咱直奔主题: 一.开发环境 VS2015 Update3 Microsof ...

  4. .net core Entity Framework Core Code First 框架 分层开发

    由于之前苦于无法把 Entityframework 跟Web层剥离.找了很久..找到了这个框架..分享给大家..  GitHub 地址:https://github.com/chsakell/dotn ...

  5. abp mysql .net core_ABP .Net Core Entity Framework迁移使用MySql数据库

    一.迁移说明 ABP模板项目Entity Framework Core默认使用的是Sql Server,也很容易将数据库迁移到MySQL,步骤如下. 二.迁移MySQL步骤 1. 下载项目 请到 ht ...

  6. Entity Framework Core

    文章目录 一.Entity Framework Core 二.使用步骤 1.引入NuGet包 2.创建实体 3.实现实体配置类 5.默认约定都有那些 6.创建继承自DbContext的类 7.使用迁移 ...

  7. 使用Entity Framework Core,Swagger和Postman创建ASP.NET Core Web API的分步指南

    目录 介绍 背景 第1步:创建一个新项目 第2步:添加模型类 第3步:使用Entity Framework Core 第4步:添加数据库上下文和控制器 步骤5:在Package Manager控制台中 ...

  8. [转帖]2016年时的新闻:ASP.NET Core 1.0、ASP.NET MVC Core 1.0和Entity Framework Core 1.0

    ASP.NET Core 1.0.ASP.NET MVC Core 1.0和Entity Framework Core 1.0 http://www.cnblogs.com/webapi/p/5673 ...

  9. 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表

    创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添 ...

最新文章

  1. 数学建模 时间序列模型
  2. Pycharm增加新安装Python的路径
  3. 03 | 事务隔离:为什么你改了我还看不见?
  4. ResNet才是YYDS!新研究:不用蒸馏、无额外数据,性能还能涨一波
  5. access开发内销核算系统
  6. 使用abapGit在ABAP On-Premises系统和SAP云平台ABAP环境之间进行代码传输
  7. Linux下如何安装最新版本工具
  8. CGCKD2021大会报告整理(4)--风格迁移
  9. Linux Shell 文本处理工具集锦 zz
  10. 关于spring mvc时间类型绑定失败解决方法
  11. Python-Spyder中文包正式发布!
  12. Barcode for Mac(条形码生成器)
  13. 数字高程模型|DEM采集的主要方式
  14. 利用Python一键爬取上海二手房信息
  15. 0基础UnityURP渲染管线人物渲染_皮肤_头发_眼睛_各向异性_SSS之实践
  16. 【5年Android从零复盘系列之二十八】Android存储(3):assets文件详解
  17. [Pytorch系列-42]:工具集 - torchvision常见预训练模型的下载地址
  18. 程序员如何在社交领域成长快一点?
  19. 国家教育部牵手曙光公司——“百校工程”助力教育行业大数据平台建设
  20. make[2]:***没有规则制作目标XXX,由XXX需求。停止。

热门文章

  1. opencv---颜色空间转化并实现物体跟踪
  2. Composer快速入门
  3. PostgreSQL忘记输入where条件update更新整张表的解决办法
  4. 编码之道:取个好名字很重要
  5. 编译AjaxControlToolkit发生错误如何解决?
  6. .NET 6 Talk Party 2|.NET Core 与行业
  7. dotnet中的counters说明(三)
  8. OpenTelemetry - 云原生下可观测性的新标准
  9. MiniProfiler,一个.NET简单但有效的微型分析器
  10. 使用 docker 构建分布式调用链跟踪框架skywalking