ProductAppService
类的GetListAsync
方法写单元测试代码(构建自动化测试细节后续再议)。
在.Application.Tests项目中创建Products文件夹,并在其中创建一个ProductAppService_Tests
类:
using Shouldly; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Xunit; namespace ProductManagement.Products { public class ProductAppService_Tests : ProductManagementApplicationTestBase { private readonly IProductAppService _productAppService; public ProductAppService_Tests() { _productAppService = GetRequiredService<IProductAppService>(); } /* TODO: Test methods */ } }
该类继承自ProductManagementApplicationTestBase
,它默认集成 ABP 框架和其他基础设施库,这样我们就可以直接使用内置的测试能力。另外,我们使用方法GetRequiredService
来解决测试代码中的依赖关系,而不是构造函数注入(这在测试中是不可能的)。
ProductAppService_Tests
类中添加如下代码:
[Fact] public async Task Should_Get_Product_List() { //Act var output = await _productAppService.GetListAsync( new PagedAndSortedResultRequestDto() ); //Assert output.TotalCount.ShouldBe(3); output.Items.ShouldContain( x => x.Name.Contains("Acme Monochrome Laser Printer") ); }
该方法调用该GetListAsync
方法并检查结果是否正确。如果您打开测试资源管理器窗口(在 Visual Studio 中的查看|测试资源管理器菜单下),您可以看到我们添加的测试方法。测试资源管理器用于显示和运行解决方案中的测试:
运行测试到检查它是否按预期工作。如果方法正常工作,将在测试方法名称的左侧看到一个绿色图标。 自动 API 控制器和 Swagger UI Swagger一款服务于开发和测试HTTP API 的的流行工具。它启动模板中已经预先装了。 设置.Web项目为启动项目,然后按 Ctrl+F5运行该项目,启动后,输入
/swagger
URL,如图所示:你会看到内置的很多 API。如果向下滚动,也会看到一个Product接口。您可以对其进行测试以获取产品列表:
我们没有创建ProductController接口。这个接口是如何出现的?这里运用的是ABP 框架的自动 API 控制器功能。它会根据命名约定和配置自动将您的应用服务公开为 HTTP API(通常,我们不会手动编写控制器)。 自动 API 控制器功能将在[第 14 章] 构建 HTTP API 和实时服务 中详细介绍。 有了 HTTP API 来获取产品列表。下一步是在客户端代码中使用此 API。 动态 JavaScript 代理 通常,您通过 JavaScript 调用 HTTP API 接口。ABP 会为所有 HTTP API 动态创建客户端代理。然后,就可以使用这些动态 JavaScript 函数从客户端调用我们的 API。 再次运行ProductManagement.Web项目,并在登录页面上使用F12快捷键打开浏览器的开发者控制台,然后输入以下 JavaScript 代码:
productManagement.products.product.getList({}).then(function(result) {
console.log(result);
});
执行此代码后,将向服务器发出请求,并将返回结果记录在Console选项卡中,如图所示:
我们可以看到返回的产品列表数据显示在控制台选项卡中。这意味着我们可以轻松地运用 JavaScript 调用服务器端 API,而无需处理低级细节。 如果您想知道JavaScript 是在哪里定义getList
的,您可以定位到/Abp/ServiceProxyScript
地址,查看由 ABP 框架动态创建的 JavaScript 代理函数。
产品列表
推荐使用 Razor Pages在 ASP.NET Core MVC 框架中创建 UI。
首先,在ProductManagement.Web项目的Pages文件夹下创建一个Products文件夹。然后,右键单击Products文件夹,然后选择Add|Razor Page。选择Razor 页面 - 空选项,命名为Index.cshtml
。下图显示了我们添加的页面的位置:编辑内容,Index.cshtml
如下代码块所示:
@page
@using ProductManagement.Web.Pages.Products
@model IndexModel
<h1>Products Page</h1>
在这里,我放置一个h1
元素作为页眉。接下来我们在主菜单中添加一个菜单来打开这个页面。
ProductManagementMenuContributor
类,并在ConfigureMainMenuAsync
方法末尾添加以下代码:
context.Menu.AddItem( new ApplicationMenuItem( "ProductManagement", l["Menu:ProductManagement"], icon: "fas fa-shopping-cart" ).AddItem( new ApplicationMenuItem( "ProductManagement.Products", l["Menu:Products"], url: "/Products" ) ) );
此代码添加了一个产品管理主菜单,其中包含产品菜单项。里面的l["…"]
语法是用来获取本地化的值。
en.json
文件,并将以下代码添加到该texts
部分的末尾:
"Menu:ProductManagement": "Product Management", "Menu:Products": "Products"
我们可以使用任意字符串值作为本地化键。在本例中,我们使用Menu:
作为菜单的本地化键的前缀,例如Menu:Products
。我们将在[第 8 章] 使用 ABP 的功能和服务中探讨本地化主题。
创建产品数据表 接下来我们将创建一个数据表显示带有分页和排序的产品列表。ABP 启动模板带有预安装和配置的JS 库 Datatables.net,用于显示表格数据。 打开
Index.cshtml
页面(在Pages/Products文件夹),并将其内容更改为以下内容:
@page @using ProductManagement.Web.Pages.Products @using Microsoft.Extensions.Localization @using ProductManagement.Localization @model IndexModel @inject IStringLocalizer<ProductManagementResource> L @section scripts { <abp-script src="/Pages/Products/Index.cshtml.js" /> } <abp-card> <abp-card-header> <h2>@L["Menu:Products"]</h2> </abp-card-header> <abp-card-body> <abp-table id="ProductsTable" striped-rows="true" /> </abp-card-body> </abp-card>
abp-script
是一个 ABP 标签助手,用于将脚本文件添加到页面,并具有自动捆绑、压缩和版本控制功能。abp-card
是另一个标签助手,以一种类型安全且简单的方式渲染 Card 组件。
我们可以使用标准的 HTML 标签。但是,ABP 标签助手极大地简化了 MVC/Razor 页面中的 UI 创建。此外,它们支持智能感知和编译时错误类型检查。我们将在[第 12 章] 使用 MVC/Razor 页面中研究标签助手。在Pages/Products文件夹下创建一个新的 JavaScript 文件,命名为
Index.cshtml.js
,内容如下:
$(function () { var l = abp.localization.getResource('ProductManagement'); var dataTable = $('#ProductsTable').DataTable( abp.libs.datatables.normalizeConfiguration({ serverSide: true, paging: true, order: [[0, "asc"]], searching: false, scrollX: true, ajax: abp.libs.datatables.createAjax( productManagement.products.product.getList), columnDefs: [ /* TODO: Column definitions */ ] }) ); });
ABP 简化了数据表配置并提供了内置集成:
-
abp.localization.getResource
返回一个本地化对象,ABP 允许您在 JS中重用服务器端定义的本地化。 -
abp.libs.datatables.normalizeConfiguration
是 ABP 框架定义的辅助函数。它通过为缺失选项提供常规默认值来简化数据表的配置。 -
abp.libs.datatables.createAjax
使 ABP 的动态 JS 客户端代理来适配数据表的参数格式。 -
productManagement.products.product.getList
是动态JS代理方法。
columnDefs
数组用于定义数据表中的列:
{ title: l('Name'), data: "name" }, { title: l('CategoryName'), data: "categoryName", orderable: false }, { title: l('Price'), data: "price" }, { title: l('StockState'), data: "stockState", render: function (data) { return l('Enum:StockState:' + data); } }, { title: l('CreationTime'), data: "creationTime", dataFormat: 'date' }
通常,列有一个title
字段和一个data
字段。data
字段匹配ProductDto
类中的属性名称,格式为驼峰式(一种命名风格,其中每个单词的第一个字母大写,第一个单词除外;它是JavaScript 语言中常用的命名风格)。
render
选项用于精细控制如何显示列数据。
在此页面上,我们使用了一些本地化键。我们应该先在本地化资源中定义它们。打开ProductManagement.Domain.Shared项目的Localization/ProductManagement 文件夹中的en.json
文件,并在该部分的末尾添加以下条目texts
:
"Name": "Name", "CategoryName": "Category name", "Price": "Price", "StockState": "Stock state", "Enum:StockState:0": "Pre-order", "Enum:StockState:1": "In stock", "Enum:StockState:2": "Not available", "Enum:StockState:3": "Stopped", "CreationTime": "Creation time"
看一下实际的产品数据表:
至此,我们创建了一个完整的工作页面,列出了支持分页和排序的产品。在接下来的部分中,我们将添加创建、编辑和删除产品的功能。 创建产品 在本节中,我们将开发新增产品所需的功能。我们的大致思路如下:定义新的应用服务方法来获取类别和创建产品。
- 定义应用服务的获取类别和创建产品方法。
- 在 UI 部分,使用 ABP 的动态表单功能,基于 C# 类自动生成产品创建表单。
IProductAppService
接口添加两个新方法开始:
Task CreateAsync(CreateUpdateProductDto input);
Task<ListResultDto<CategoryLookupDto>> GetCategoriesAsync();
在创建产品时,我们使用GetCategoriesAsync
方法获取产品类别的下拉数据。我们定义了两个新的 DTO。
CreateUpdateProductDto
用于创建和更新产品(我们将在编辑产品时候重复使用它)。我们在ProductManagement.Application.Contracts项目的Products文件夹中定义它:
using System; using System.ComponentModel.DataAnnotations; namespace ProductManagement.Products { public class CreateUpdateProductDto { public Guid CategoryId { get; set; } [Required] [StringLength(ProductConsts.MaxNameLength)] public string Name { get; set; } public float Price { get; set; } public bool IsFreeCargo { get; set; } public DateTime ReleaseDate { get; set; } public ProductStockState StockState { get; set; } } }
接下来,在ProductManagement.Application.Contracts项目的Categories文件夹中定义一个CategoryLookupDto
类:
using System; namespace ProductManagement.Categories { public class CategoryLookupDto { public Guid Id { get; set; } public string Name { get; set; } } }
定了接口相关类,现在我们可以在应用层实现接口了。
实现应用服务 在ProductAppService
中实现CreateAsync
和GetCategoriesAsync
方法(ProductManagement.Application项目中),如下代码块:
public async Task CreateAsync(CreateUpdateProductDto input) { await _productRepository.InsertAsync( ObjectMapper.Map<CreateUpdateProductDto, Product>(input) ); } public async Task<ListResultDto<CategoryLookupDto>> GetCategoriesAsync() { var categories = await _categoryRepository.GetListAsync(); return new ListResultDto<CategoryLookupDto>( ObjectMapper.Map<List<Category>, List<CategoryLookupDto>>(categories) ); }
这里,_categoryRepository
属于IRepository<Category, Guid>
服务类型,通过构造函数注入,方法实现很简单,无需解释。
ProductManagementApplicationAutoMapperProfile.cs
文件(在ProductManagement.Application项目中),添加以下代码:
CreateMap<CreateUpdateProductDto, Product>();
CreateMap<Category, CategoryLookupDto>();
用户界面
在ProductManagement.Web项目的Pages/Products文件夹下创建一个CreateProductModal.cshtml
Razor 页面。打开CreateProductModal.cshtml.cs
文件,更改CreateProductModalModel
代码:
using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using ProductManagement.Products; namespace ProductManagement.Web.Pages.Products { Public class CreateProductModalModel:ProductManagementPageModel { [BindProperty] public CreateEditProductViewModel Product { get; set; } public SelectListItem[] Categories { get; set; } private readonly IProductAppService _productAppService; public CreateProductModalModel(IProductAppService productAppService) { _productAppService = productAppService; } public async Task OnGetAsync() { // TODO } public async Task<IActionResult> OnPostAsync() { // TODO } } }
这里的ProductManagementPageModel
是基类。你可以继承它来创建PageModel
类。[BindProperty]
是一个标准的 ASP.NET Core 属性,在HTTP Post 请求时,会将数据绑定到Product
属性。Categories
将用于显示下拉列表中的类别。我们通过注入IProductAppService
接口以使用之前定义的方法。
CreateEditProductViewModel
还没定义,我们将其定义在与CreateProductModal.cshtml
相同的文件夹下:
using ProductManagement.Products; using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; namespace ProductManagement.Web.Pages.Products { public class CreateEditProductViewModel { [SelectItems("Categories")] [DisplayName("Category")] public Guid CategoryId { get; set; } [Required] [StringLength(ProductConsts.MaxNameLength)] public string Name { get; set; } public float Price { get; set; } public bool IsFreeCargo { get; set; } [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } public ProductStockState StockState { get; set; } } }
SelectItems
告诉我们CategoryId
属性将从Categories
列表中选择。我们将在编辑模式对话框中重用此类。这就是我为什么命名它为CreateEditProductViewModel
。
DTO 与 ViewModel
定义视图模型CreateEditProductViewModel
似乎没有必要,因为它与 CreateUpdateProductDto
DTO非常相似。当然你也可以在视图里复用DTO。但是,考虑到这些类具有不同的用途,并且随着时间的推移会向不同的方向发展,所更推荐的办法是将每个关注点分开。例如,[SelectItems("Categories")]
属性指向 Razor Page 模型,它在应用层没有任何意义。
现在,我们可以在CreateProductModalModel
类中实现OnGetAsync
方法:
public async Task OnGetAsync() { Product = new CreateEditProductViewModel { ReleaseDate = Clock.Now, StockState = ProductStockState.PreOrder }; var categoryLookup = await _productAppService.GetCategoriesAsync(); Categories = categoryLookup.Items.Select(x => new SelectListItem(x.Name, x.Id.ToString())).ToArray(); }
我们使用默认值创建Product
类,然后使用产品应用服务填充Categories
列表。Clock
是 ABP 框架提供的服务,用于获取当前时间(在不处理时区和本地/UTC 时间的情况下),这里我们不再使用DateTime.Now
。具体内容这将在[第 8 章] 使用 ABP 的功能和服务中进行解释。
OnPostAsync
代码块:
public async Task<IActionResult> OnPostAsync() { await _productAppService.CreateAsync( ObjectMapper.Map<CreateEditProductViewModel,CreateUpdateProductDto> (Product) ); return NoContent(); }
由于我们要映射CreateEditProductViewModel
到CreateProductDto
,所以需要定义映射配置。我们在ProductManagement.Web项目中打开ProductManagementWebAutoMapperProfile
类,并更改以下代码块内容:
public class ProductManagementWebAutoMapperProfile : Profile { public ProductManagementWebAutoMapperProfile() { CreateMap<CreateEditProductViewModel, CreateUpdateProductDto>(); } }
我们已经完成了产品创建 UI 的 C# 端,接下来可以开始构建 UI 和 JavaScript 代码。打开CreateProductModal.cshtml
文件,并将内容更改如下:
@page @using Microsoft.AspNetCore.Mvc.Localization @using ProductManagement.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @model ProductManagement.Web.Pages.Products.CreateProductModalModel @inject IHtmlLocalizer<ProductManagementResource> L @{ Layout = null; } <abp-dynamic-form abp-model="Product" asp-page="/Products/CreateProductModal"> <abp-modal> <abp-modal-header title="@L["NewProduct"].Value"></abp-modal-header> <abp-modal-body> <abp-form-content /> </abp-modal-body> <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> </abp-modal> </abp-dynamic-form>
在这里,abp-dynamic-form
会根据 C# 模型类自动创建表单元素。abp-form-content
是呈现表单元素的地方。abp-modal
用于创建模态对话框。
Index.cshtml
文件,然后将abp-card-header
部分更改如下:
<abp-card-header> <abp-row> <abp-column size-md="_6"> <abp-card-title>@L["Menu:Products"]</abp-card-title> </abp-column> <abp-column size-md="_6" class="text-end"> <abp-button id="NewProductButton" text="@L["NewProduct"].Value" icon="plus" button-type="Primary"/> </abp-column> </abp-row> </abp-card-header>
我添加了 2 列,其中每列都有一个size-md="_6"
属性(即 12 列 Bootstrap 网格的一半)。左侧设置卡片标题,右侧放置了一个按钮。
Index.cshtml.js
文件末尾(在})
之前):
var createModal = new abp.ModalManager(abp.appPath + 'Products/CreateProductModal'); createModal.onResult(function () { dataTable.ajax.reload(); }); $('#NewProductButton').click(function (e) { e.preventDefault(); createModal.open(); });
-
abp.ModalManager
用于在客户端管理模式对话框。在内部,它使用 Twitter Bootstrap 的标准模态组件,封装了很多细节,并提供了一个简单的 API。当模型触发保存时会返回一个回调函数createModal.onResult()
。 -
createModal.open()
用于打开模态对话框。
en.json
文件中定义一些本地化文本(.Domain.Shared项目的Localization/ProductManagement 文件夹下):
"NewProduct": "New Product", "Category": "Category", "IsFreeCargo": "Free Cargo", "ReleaseDate": "Release Date"
再次运行 Web 尝试创建新产品
ABP基于 C# 类模型自动创建表单字段。本地化和验证也可以通过读取属性和使用约定来自动工作。我们将在[第 12 章] 使用 MVC/Razor 页面 中更详细地介绍验证和本地化主题。
我们现在可以在 UI 上创建产品了。 编辑产品 编辑产品类似于添加新产品,现在让我们看看如何编辑产品: 定义应用接口 让我们从为IProductAppService
接口定义两个新方法:
Task<ProductDto> GetAsync(Guid id);
Task UpdateAsync(Guid id, CreateUpdateProductDto input);
第一种方法用于通过ID获取产品。我们在UpdateAsync
方法中重用之前定义的CreateUpdateProductDto
。
ProductAppService
类中:
public async Task<ProductDto> GetAsync(Guid id) { return ObjectMapper.Map<Product, ProductDto>( await _productRepository.GetAsync(id) ); } public async Task UpdateAsync(Guid id, CreateUpdateProductDto input) { var product = await _productRepository.GetAsync(id); ObjectMapper.Map(input, product); }
GetAsync
方法用于从数据库中获取产品,并将其映射到ProductDto
对象后进行返回。UpdateAsync
方法获取到一个产品后,将给定的DTO输入映射到产品。通过这种方式,我们用新值覆盖产品。
_productRepository.UpdateAsync
,因为 EF Core有一个变更跟踪系统。ABP 的工作单元如果没有抛出异常,则在请求结束时会自动保存更改。我们将在[第 6 章] *使用数据访问基础架构”*中介绍工作单元系统。
应用层已完成。接下来,我们将创建一个产品编辑 UI。
用户界面
创建一个EditProductModal.cshtml
Razor 页面(ProductManagement.Web项目的 Pages/Products文件夹下)。打开EditProductModal.cshtml.cs
,代码更改如下:
using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using ProductManagement.Products; namespace ProductManagement.Web.Pages.Products { public class EditProductModalModel : ProductManagementPageModel { [HiddenInput] [BindProperty(SupportsGet = true)] public Guid Id { get; set; } [BindProperty] public CreateEditProductViewModel Product { get; set; } public SelectListItem[] Categories { get; set; } private readonly IProductAppService _productAppService; public EditProductModalModel(IProductAppService productAppService) { _productAppService = productAppService; } public async Task OnGetAsync() { // TODO } public async Task<IActionResult> OnPostAsync() { // TODO } } }
表单中Id
字段将被隐藏。
Product
和Categories
属性类似于创建产品。我们还将
IProductAppService
接口注入到构造函数。
我们实现OnGetAsync
方法,如下代码块所示:
public async Task OnGetAsync() { var productDto = await _productAppService.GetAsync(Id); Product = ObjectMapper.Map<ProductDto, CreateEditProductViewModel>(productDto); var categoryLookup = await _productAppService.GetCategoriesAsync(); Categories = categoryLookup.Items .Select(x => new SelectListItem(x.Name, x.Id.ToString())) .ToArray(); }
首先,我们要先获取一个产品 ( ProductDto
),再将其转换为CreateEditProductViewModel
,使用它在 UI 上来创建编辑表单。然后,我们在表单上选择产品类别。
ProductDto
到CreateEditProductViewModel
,所以我们需要在ProductManagementWebAutoMapperProfile
类中定义配置映射(ProductManagement.Web项目中),这和我们之前操作是一样的:
CreateMap<ProductDto, CreateEditProductViewModel>();
我们再看下OnPostAsync()
方法:
public async Task<IActionResult> OnPostAsync() { await _productAppService.UpdateAsync(Id, ObjectMapper.Map<CreateEditProductViewModel, CreateUpdateProductDto>(Product) ); return NoContent(); }
OnPostAsync
方法很简单,把CreateEditProductViewModel
转换为CreateUpdateProductDto
。
EditProductModal.cshtml
,内容更改如下:
@page @using Microsoft.AspNetCore.Mvc.Localization @using ProductManagement.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @model ProductManagement.Web.Pages.Products.EditProductModalModel @inject IHtmlLocalizer<ProductManagementResource> L @{ Layout = null; } <abp-dynamic-form abp-model="Product" asp-page="/Products/EditProductModal"> <abp-modal> <abp-modal-header title="@Model.Product.Name"></abp-modal-header> <abp-modal-body> <abp-input asp-for="Id" /> <abp-form-content/> </abp-modal-body> <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> </abp-modal> </abp-dynamic-form>
页面与CreateProductModal.cshtml
非常相似。我刚刚将Id
字段作为隐藏字段添加到表单,用来存储Id
编辑的产品的属性。
Index.cshtml.js
文件,并在dataTable
代码的头部添加一个ModalManager
对象:
var editModal = new abp.ModalManager(abp.appPath + 'Products/EditProductModal');
然后,在dataTable
内部的columnDefs
数组中定义一个列(第一项):
{ title: l('Actions'), rowAction: { items: [ { text: l('Edit'), action: function (data) { editModal.open({ id: data.record.id }); } } ] } },
此代码向数据表添加了一个新的Actions列,并添加了一个Edit操作按钮,单击即可打开编辑窗口。rowAction
是 ABP Framework 提供的一个特殊选项。它用于在表中的一行添加一个或多个操作按钮。
dataTable
初始化代码后添加如下:
editModal.onResult(function () {
dataTable.ajax.reload();
});
在保存产品编辑对话框后刷新数据表,确保我们可以看到表上的最新数据。最终的 UI 类似于下图:
我们现在可以查看、创建和编辑产品了。最后一部分将实现删除产品。
删除产品 删除产品与创建或编辑操作相比,非常简单,因为删除我们不需要构建表单。 首先,在IProductAppService
接口中添加一个新方法:
Task DeleteAsync(Guid id);
然后,在ProductAppService
类中实现它:
public async Task DeleteAsync(Guid id) { await _productRepository.DeleteAsync(id); }
现在向产品列表添加一个新删除按钮。打开Index.cshtml.js
,并在Edit操作之后添加以下定义(在rowAction.items
数组中):
{ text: l('Delete'), confirmMessage: function (data) { return l('ProductDeletionConfirmationMessage',data.record.name); }, action: function (data) { productManagement.products.product .delete(data.record.id) .then(function() { abp.notify.info(l('SuccessfullyDeleted')); dataTable.ajax.reload(); }); } }
confirmMessage
用于在删除之前获得用户确认。productManagement.products.product.delete
函数由 ABP 框架动态创建。通过这种方式,可以直接在 JS 代码中调用服务器端方法。我们只需传递当前记录的 ID。then
函数传递一个回调函数,用于删除之后的操作。最后,我们使用abp.notify.info
通知用户,最后刷新数据表。
en.json
文件中添加以下代码:
"ProductDeletionConfirmationMessage": "Are you sure to delete this book: {0}",
"SuccessfullyDeleted": "Successfully deleted!"
再次访问 web 查看结果:因为现在有两个操作按钮,所以编辑按钮会自动变成一个下拉选项。当您单击删除操作时,您会收到一条确认消息:
如果你点击在是按钮上,您将在页面上看到一条通知,并且数据表将被刷新。 实施产品删除非常简单。ABP 的内置功能帮助我们实现了常见的模式,例如客户端到服务器的通信、确认对话框和 UI 通知。 请注意,Product
实体派生于FullAuditedAggregateRoot
,所以它使用了软删除。删除产品后检查数据库,您会看到它并没有真正删除,但是IsDeleted
字段已经设置为true
(逻辑删除不是物理删除)。下次查询商品时,已删除的商品会自动过滤掉,不包含在查询结果中。这是由 ABP 框架的数据过滤系统完成的。
概括
至此上下篇章全部完成了,在本篇中,我们创建了一个完整的 CRUD 页面。我们介绍了解决方案中的所有层,并了解了ABP 的程序开发的基本方法。
同时,也向您介绍了许多不同的概念,例如实体、存储库、数据库映射和迁移、自动化测试、API 控制器、动态 JavaScript 代理、对象映射、软删除等。ABP 是一个全栈应用程序框架,可帮助您通过最佳实践来实现这些概念。它提供了必要的基础设施,使您的日常开发更容易。
此时您可能不了解所有细节。其余篇幅会深入研究这些概念并展示它们的细节和不同的用例。
以上的示例相对简单,它不包含任何重要的业务逻辑,因为我引入了许多概念目的是想让大家对这些基础概念有个初步的理解而不是业务复杂性。
希望以上分享对你有所帮助,感谢您的捧场。