通付盾 建设公司网站,饿了么企业网站,做58网站每天可以发几条,如何保存网站上的图片不显示图片以下是手把手引进门教程#xff0c;基于 ASP.NET Core#xff0c; Entity Framework Core #xff0c;ABP 框架 创建Web 应用#xff0c; PS#xff1a; 自带自动的测试模块哦。样例下载 #xff08;上 github 的请自便#xff09;介绍这是系列文章的第一部分#xff1… 以下是手把手引进门教程基于 ASP.NET Core Entity Framework Core ABP 框架 创建Web 应用 PS 自带自动的测试模块哦。样例下载 上 github 的请自便介绍这是系列文章的第一部分使用 ASP.NET Core, Entity Framework Core 和 ASP.NET Boilerplate 创建N层Web应用 在本文中我将指导大家创建一个样例跨平台的多层Web应用该样例会用到如下工具请读者提前准备Net Core 跨平台应用的基础开发框架ASP.NET Boilerplate (ABP) 开发的基础框架模板ASP.NET Core web开发框架Entity Framework Core ORM 数据框架Twitter Bootstrap HTMLCSS 前端开发框架jQuery 客户端 AJAX/DOM 类库xUnit 和 Shouldly 服务端测试工具单元测试/集成测试 ABP 框架中会默认使用 Log4Net 和 AutoMapper 。我们同时还会使用以下技术Layered Architecture 分层架构Domain Driven Design (DDD) DDD领域模型Dependency injection (DI) DI 依赖注入Integration Testing 集成测试演示的开发项目是一个简单的任务管理应用用于将任务分配出去。我不会一层一层的进行开发而是随着应用的拓展直接切换所需的层次。随着应用的推拓展我将会介绍所需的ABP和其他框架的特性。 前期准备开发样例时需要以下工具请提前在你的机器上进行安装Visual Studio 2017SQL Server (你可以更改连接字符串为 localdb)Visual Studio Extensions:Bundler MinifierWeb Compiler创建应用首先使用ABP模版http://www.aspnetboilerplate.com/Templates创建一个web应用项目命名为Acme.SimpleTaskApp 。创建模板时可以设置自己的公司名称比如Acme。本样例使用MPAMulti 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 coreWeb 展示层 即Asp.net MVC层Tests 单元测试和集成测试含应用层领域层基础设施层不含Web展示层Web.Tests ASP.NET Core集成测试包含web展示层的全部集成测试以上是没有选择zero的项目结果如果你选择了zero项目结构就会变成下图当你把应用运行起来后你会看到下图所示的用户界面 这个应用包含一个顶级菜单栏包含空的首页关于页还有一个语言的下拉选项。 正式开发创建任务实体 Entity我们从创建一个简单的任务实体 Task Entity 开始由于它属于领域层把它加到 core 项目里。 代码如下 using System;using System.ComponentModel.DataAnnotations;using System.ComponentModel.DataAnnotations.Schema;using Abp.Domain.Entities;using Abp.Domain.Entities.Auditing;using Abp.Timing;namespace Acme.SimpleTaskApp.Tasks{ [Table(AppTasks)] public class Task : Entity, IHasCreationTime { public const int MaxTitleLength 256; public const int MaxDescriptionLength 64 * 1024; //64KB [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) : this() { Title title; Description description; } } public enum TaskState : byte { Open 0, Completed 1 }}Task 实体从 ABP 的 Entity 基类继承Entity 基类默认ID属性是 int 类型。如果主键类型为非 int 类型也可以选择范型版本的 EntityTPrimaryKey.IHasCreationTime 是一个简单的接口只定义了 CreationTime 属性 统一规范 CreationTime 的名字Task 实体定义了一个必填的 Title 和 非必填的 DescriptionTaskState 是一个简单枚举定义了 Task 任务的状态Clock.Now 返回默认的 DateTime.Now 。但它提供了一个抽象方法使得我们可以在将来有需要的时候很轻松就可以转换为 DateTime.UtcNow 。在 ABP 框架中总是使用Clock.Now 而不使用 DateTime.Now 。将 Task 实体存储到数据库的 AppTasks 表中。 将任务添加到数据库上下文 DbContext.EntityFrameworkCore 包含一个预定义的 DbContext 。将 Task 实体的 DbSet 加到 DbContext 里。代码如下public class SimpleTaskAppDbContext : AbpDbContext{ public DbSetlt;Task Tasks { get; set; } public SimpleTaskAppDbContext(DbContextOptionslt;SimpleTaskAppDbContext options) : base(options) { }}现在EF core 知道我们有了一个 Task 的实体。 创建第一个数据迁移我们将创建一个初始化数据库迁移文件它会自动创建数据库和数据库表 AppTasks 。打开源管理器 Package Manager Console from Visual Studio , 执行 Add-Migration 命令默认的项目必须是 .EntityFrameworkCore 项目如图这个命令会在 . EntityFrameworkCore 项目下创建一个迁移 Migrations 文件夹文件夹包含一个迁移类和数据库模型的快照如图 如下代码所示自动创建了 “初始化 Initial ”迁移类public partial class Initial : Migration{ protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: AppTasks, columns: table new { Id table.Columnlt;int(nullable: false) .Annotation(SqlServer:ValueGenerationStrategy, SqlServerValueGenerationStrategy.IdentityColumn), CreationTime table.Columnlt;DateTime(nullable: false), Description table.Columnlt;string(maxLength: 65536, nullable: true), State table.Columnlt;byte(nullable: false), Title table.Columnlt;string(maxLength: 256, nullable: false) }, constraints: table { table.PrimaryKey(PK_AppTasks, x x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: AppTasks); }}当我们执行数据库迁移命令时这些代码会创建 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 : IApplicationService2 {3 Tasklt;ListResultDtolt;TaskListDto GetAll(GetAllTasksInput input);4 }我们推荐先定义接口但不是非这样做不可。按照惯例ABP 中所有的应用服务都需要实现 IApplicationService 接口 它只是一个空的标记接口。我们创建了一个 GetAll 方法去查询任务列表同时我们定义了如下的 DTOs 代码如下 public class GetAllTasksInput{ public TaskState? State { get; set; }}[AutoMapFrom(typeof(Task))]public class TaskListDto : EntityDto, IHasCreationTime{ public string Title { get; set; } public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; }}GetAllTasksInput DTO 为 GetAll 应用服务方法定义了一个输入参数 。 我们将 状态 state 定义为 DTO 对象 而不定义为方法的参数。 这样我们将来需要的时候可以在这个DTO增加其他的参数同时兼容现有的客户端 当然我们也可以在方法里加一个 state 参数。TaskListDto 用开返回任务数据。该Dto 从 EntityDto 继承EntityDto 只是定义了 Id 属性我们可以不继承 EntityDto 直接自己将 Id 加到我们的Dto里。我们定义了[AutoMapFrom] 特性来创建 AutoMapper 自动映射任务实体到任务列表Dto TaskListDto 。这个特性在 Abp.AutoMapper nuget 包里进行了定义。ListResultDto 是一个简单的类包含了一个列表我们可以直接返回一个 ListTaskListDto 列表。现在我们可以实现 ITaskAppService 了。代码如下using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Abp.Application.Services.Dto;using Abp.Domain.Repositories;using Abp.Linq.Extensions;using Acme.SimpleTaskApp.Tasks.Dtos;using Microsoft.EntityFrameworkCore;namespace Acme.SimpleTaskApp.Tasks{ public class TaskAppService : SimpleTaskAppAppServiceBase, ITaskAppService { private readonly IRepositorylt;Task _taskRepository; public TaskAppService(IRepositorylt;Task taskRepository) { _taskRepository taskRepository; } public async Tasklt;ListResultDtolt;TaskListDto GetAll(GetAllTasksInput input) { var tasks await _taskRepository .GetAll() .WhereIf(input.State.HasValue, t t.State input.State.Value) .OrderByDescending(t t.CreationTime) .ToListAsync(); return new ListResultDtolt;TaskListDto( ObjectMapper.Maplt;Listlt;TaskListDto(tasks) ); } }}TaskAppService 该类从 SimpleTaskAppAppServiceBase 继承SimpleTaskAppAppServiceBase 从 ABP 的 ApplicationService 类继承在模板里已经自动生成。 TaskAppService 不是必须从 SimpleTaskAppAppServiceBase 继承应用服务可以是普通类。但是 ApplicationService 基类有一些预先注入的服务就像这里使用的 ObjectMapper 我们使用依赖注入 dependency injection 来获取数据仓储 repository Repositories 数据仓储用于为数据实体抽象数据库操作。ABP 为每个实体创建了预定义的数据库仓储就像这里用到了 IRepositoryTask 用于实现通用的任务。IRepository.GetAll() 方法用于查询数据实体它返回了一个 IQueryable 接口。WhereIf 这是 ABP 里的一个拓展方法该方法提供了一个 IQueryable.Where 方法的简便条件语法。ObjectMapper 用于将任务对象列表映射到任务列表Dto对象列表 基于 Application Service 基类并默认实现 AutoMapper 测试任务服务在创建用户接口钱我们需要测试一下任务应用服务 TaskAppService 。 如果你对自动化测试不感兴趣的话可以忽略这个部分。我们的模板包含 .Tests 项目这可以测试我们的代码。这个项目不使用 SQL Server数据库而是使用EF core 的内存数据库。所以我们可以不用真实数据库来进行单元测试。它为每个测试创建了单独的数据库。所以每个测试都是隔离的。我们需要在开始测试前使用 TestDataBuilder 类添加初始测试数据到内存数据库里。我修改了 TestDataBuilder 。代码如下public class TestDataBuilder{ private readonly SimpleTaskAppDbContext _context; public TestDataBuilder(SimpleTaskAppDbContext context) { _context context; } public void Build() { _context.Tasks.AddRange( new Task(Follow the white rabbit, Follow the white rabbit in order to know the reality.), new Task(Clean your room) { State TaskState.Completed } ); }}通过样例项目的源代码你可以看懂 TestDataBuilder 在哪里用具体怎么用。我们添加2个任务其中一个已经完成到数据库上下文 dbcontext 。我们可以假定数据库中有2个任务开始编写测试用例。 第一个继承测试用来测试 TaskAppService.GetAll 方法。代码如下public class TaskAppService_Tests : SimpleTaskAppTestBase{ private readonly ITaskAppService _taskAppService; public TaskAppService_Tests() { _taskAppService Resolvelt;ITaskAppService(); } [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); } [Fact] public async System.Threading.Tasks.Task Should_Get_Filtered_Tasks() { //Act var output await _taskAppService.GetAll(new GetAllTasksInput { State TaskState.Open }); //Assert output.Items.ShouldAllBe(t t.State TaskState.Open); }}我们创建2个不同的测试用例来测试 GetAll 方法。现在我们打开测试浏览器在VS主菜单的 Test\Windows\Test Explorer 菜单下开始进行单元测试。 所有测试均成功。最后一个我们现在可以忽略它他是一个模板生成的测试。友情提示 ABP 模板默认安装使用 xUnit 和 Shouldly 。我们使用它们编写我们的测试。 任务列表展示 现在我们确定 TaskAppService 服务可以正常工作。 我们可以开始创建页面来展示所有的任务。 添加菜单首先我们在顶级菜单上添加一个新的菜单代码如下public class SimpleTaskAppNavigationProvider : NavigationProvider{ public override void SetNavigation(INavigationProviderContext context) { context.Manager.MainMenu .AddItem( new MenuItemDefinition( Home, L(HomePage), url: , icon: fa fa-home ) ).AddItem( new MenuItemDefinition( About, L(About), url: Home/About, icon: fa fa-info ) ).AddItem( new MenuItemDefinition( TaskList, L(TaskList), url: Tasks, icon: fa fa-tasks ) ); } private static ILocalizableString L(string name) { return new LocalizableString(name, SimpleTaskAppConsts.LocalizationSourceName); }}模板自带两个页面首页和关于页如上代码所示。我们也可以修改它们创建新的页面。但现在我们不修改首页和关于页我们创建新的菜单项。 创建任务 Controller 和 视图模型我们在 .Web 项目下创建一个新的 controller 类命名为 TasksController 。代码如下public class TasksController : SimpleTaskAppControllerBase{ private readonly ITaskAppService _taskAppService; public TasksController(ITaskAppService taskAppService) { _taskAppService taskAppService; } public async Tasklt;ActionResult Index(GetAllTasksInput input) { var output await _taskAppService.GetAll(input); var model new IndexViewModel(output.Items); return View(model); }}TasksController 从 SimpleTaskAppControllerBase SimpleTaskAppControllerBase 从 AbpController 继承继承该类包含应用程序 Controllers 需要的通用基础代码。我们反射了 ITaskAppService 以获取到所有的任务列表。我们在 .Web 项目中创建了一个 IndexViewModel 类来将数据展示到视图上这样可以不直接将 GetAll 方法的结果直接暴露到视图上。代码如下public class IndexViewModel{ public IReadOnlyListlt;TaskListDto Tasks { get; } public IndexViewModel(IReadOnlyListlt;TaskListDto tasks) { Tasks tasks; } public string GetTaskLabel(TaskListDto task) { switch (task.State) { case TaskState.Open: return label-success; default: return label-default; } }}我们创建了一个简单的视图模型在它的构造函数中我们获取了一个任务列表由 ITaskAppService 提供。同时它还有一个 GetTaskLabel 方法用于在视图中通过一个 选择 Bootstrap 标签来标示任务。 任务列表页面最后完成实际的 Index 视图。代码如下model Acme.SimpleTaskApp.Web.Models.Tasks.IndexViewModel{ ViewBag.Title L(TaskList); ViewBag.ActiveMenu TaskList; //Matches with the menu name in SimpleTaskAppNavigationProvider to highlight the menu item}lt;h2L(TaskList)lt;/h2lt;div classrow lt;div lt;ul classlist-group foreach (var task in Model.Tasks) { lt;li classlist-group-item lt;span classpull-right label Model.GetTaskLabel(task)L($TaskState_{task.State})lt;/span lt;h4 classlist-group-item-headingtask.Titlelt;/h4 lt;div classlist-group-item-text task.CreationTime.ToString(yyyy-MM-dd HH:mm:ss) lt;/div lt;/li } lt;/ul lt;/divlt;/div我们使用 Bootstrap 的 list group 组件和定义好的模型来渲染视图。我们使用 IndexViewModel.GetTaskLable() 方法来获得任务的标签类型。渲染后的界面如下图 本地化我们在视图里使用 ABP 框架自带的 L 方法。 它用于本地化语言。我们在 .Core 项目下的 Localization/Source 文件夹中定义好了本地化字符串使用 .json 文件。英语版本的本地化语言设置代码如下{ culture: en, texts: { HelloWorld: Hello World!, ChangeLanguage: Change language, HomePage: HomePage, About: About, Home_Description: Welcome to SimpleTaskApp..., About_Description: This is a simple startup template to use ASP.NET Core with ABP framework., TaskList: Task List, TaskState_Open: Open, TaskState_Completed: Completed }}模板自带了大多数的文本当然它们可以删除掉。在上面的代码中我只是加了最后的三行。使用 ABP 的本地化是相当的简单如果你想了解本地化系统更多的信息请查阅文档 localization document 任务过滤正如之前说过的TaskController 实际上使用的是 GetAllTasksInput 可以灵活的过滤任务。我们可以添加一个任务列表的下拉菜单来过滤任务。首先我们添加一个下拉菜单到视图上我们加到 header 里代码如下lt;h2 L(TaskList) lt;span classpull-right Html.DropDownListFor( model model.SelectedTaskState, Model.GetTasksStateSelectListItems(LocalizationManager), new { class form-control, id TaskStateCombobox }) lt;/spanlt;/h2然后我修改了 IndexViewModel 增加了 SeletedTaskState 属性和 GetTaskStateSelectListItems 方法代码如下public class IndexViewModel{ //... public TaskState? SelectedTaskState { get; set; } public Listlt;SelectListItem GetTasksStateSelectListItems(ILocalizationManager localizationManager) { var list new Listlt;SelectListItem { new SelectListItem { Text localizationManager.GetString(SimpleTaskAppConsts.LocalizationSourceName, AllTasks), Value , Selected SelectedTaskState null } }; list.AddRange(Enum.GetValues(typeof(TaskState)) .Castlt;TaskState() .Select(state new SelectListItem { Text localizationManager.GetString(SimpleTaskAppConsts.LocalizationSourceName, $TaskState_{state}), Value state.ToString(), Selected state SelectedTaskState }) ); return list; }}我们也可以在 controller 里设置 SelectedTaskState 代码如下public async Tasklt;ActionResult Index(GetAllTasksInput input){ var output await _taskAppService.GetAll(input); var model new IndexViewModel(output.Items) { SelectedTaskState input.State }; return View(model);}现在我们运行程序可以看到视图的右上角有个下拉框如图我们添加了下拉框但它现在还不能用。我们需要编写一些简单的 javascript 代码当下拉框内容更改后可以重新请求/刷新任务列表页面。我们在 .Web 项目里创建了 wwwroot\js\views\tasks\index.js 文件代码如下(function ($) { $(function () { var _$taskStateCombobox $(#TaskStateCombobox); _$taskStateCombobox.change(function() { location.href /Tasks?state _$taskStateCombobox.val(); }); });})(jQuery);我们首先添加 Bundler Minifier 扩展程序这是 ASP.NET Core 项目标配的压缩文件来压缩脚本的大小 然后开始在视图里编写 javascript 这将在 .Web 项目中的 bundleconfig.json 中添加以下代码代码如下{ outputFileName: wwwroot/js/views/tasks/index.min.js, inputFiles: [ wwwroot/js/views/tasks/index.js ]}同时创建了 script 的压缩版本无论我何时修改了index.js index.min.js 都会自动重新生成。现在我们可以在我们的页面里插入 javascript 文件了。代码如下section scripts{ lt;environment namesDevelopment lt;script src~/js/views/tasks/index.jslt;/script lt;/environment lt;environment namesStaging,Production lt;script src~/js/views/tasks/index.min.jslt;/script lt;/environment}至此我们的视图将在开发环境下使用 index.js 包而在生产环境中使用 index.min.js 压缩版本包。这是在 ASP.Net Core MVC 项目中通用的做法。 任务列表页面的自动化测试ASP.NET Core MVC 基础框架中集成了一个继承测试模块。我们可以完整的测试我们的服务端代码了。如果你对自动化测试不感兴趣的话你可以忽略这个部分。ABP 模板中自带 .Web.Tests 项目。我们创建一个普通的测试来请求 TaskController.Index 然后检查反馈内容代码如下public class TasksController_Tests : SimpleTaskAppWebTestBase{ [Fact] public async System.Threading.Tasks.Task Should_Get_Tasks_By_State() { //Act var response await GetResponseAsStringAsync( GetUrllt;TasksController(nameof(TasksController.Index), new { state TaskState.Open } ) ); //Assert response.ShouldNotBeNullOrWhiteSpace(); }}GetResponseAsStringAsync 和 GetUrl 是 ABP 的 AbpAspNetCoreIntrgratedTestBase 类中很有用的方法。使用这些快捷方法我们可以比较容易的创建请求如果直接使用客户端请求一个 HttpClient 的实例会相对复杂一些。如果想深入了解请参考 ASP.NET Core 的 integration testing documentation当我们开始 debug 测试模块式我们可以看到反馈的 HTML 如下图上图显示 Index 页面的反馈很正常。但是我们更想知道返回的 HTML 是否正如我们所预期的那样。 有很多类库可以用来解析 HTML 。ABP 模板的 .Web.Tests 项目预先安装了其中的一个类库 AngleSharp 我们用它来检查创建的 HTML 代码。代码如下public class TasksController_Tests : SimpleTaskAppWebTestBase{ [Fact] public async System.Threading.Tasks.Task Should_Get_Tasks_By_State() { //Act var response await GetResponseAsStringAsync( GetUrllt;TasksController(nameof(TasksController.Index), new { state TaskState.Open } ) ); //Assert response.ShouldNotBeNullOrWhiteSpace(); //Get tasks from database var tasksInDatabase await UsingDbContextAsync(async dbContext { return await dbContext.Tasks .Where(t t.State TaskState.Open) .ToListAsync(); }); //Parse HTML response to check if tasks in the database are returned var document new HtmlParser().Parse(response); var listItems document.QuerySelectorAll(#TaskList li); //Check task count listItems.Length.ShouldBe(tasksInDatabase.Count); //Check if returned list items are same those in the database foreach (var listItem in listItems) { var header listItem.QuerySelector(.list-group-item-heading); var taskTitle header.InnerHtml.Trim(); tasksInDatabase.Any(t t.Title taskTitle).ShouldBeTrue(); } }}你可以深入检查 HTML 的更多细节。但一般来说检查基本的标签就够了。 其他相关内容第二篇 Second article 接着开发这个应用服务。原文地址:http://www.cnblogs.com/yabu007/p/8067694.html.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com