在本节中,我们将开发新增产品所需的功能。我们的大致思路如下: 定义新的应用服务方法来获取类别和创建产品。 1. 定义应用服务的获取类别和创建产品方法。 2. 在 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*](http://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](http://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*](http://xn--ProductManagement-6793a.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`用于创建模态对话框。 您也可以使用标准的 Bootstrap HTML 元素和 [ASP.NET](http://ASP.NET) Core 的绑定来创建表单元素。但是,ABP 的 Bootstrap 和动态表单标签助手大大简化了 UI 代码。我们将在\[*第 12 章*\] *使用 MVC/Razor 页面*中介绍 ABP 标签助手。 我们已经完成创建产品的模态窗口代码。现在,我们将在产品页面添加一个**新产品**按钮以打开该窗口。打开*Pages/Products*文件夹中的`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 尝试创建新产品 ![](https://img.kancloud.cn/b2/2f/b22fa36b220d23e6467e087d1a4ccc87_1136x668.png) ABP基于 C# 类模型自动创建表单字段。本地化和验证也可以通过读取属性和使用约定来自动工作。我们将在\[*第 12 章*\] *使用 MVC/Razor 页面* 中更详细地介绍验证和本地化主题。 我们现在可以在 UI 上创建产品了。