Web 应用开发教程 - 第 3 部分:创建、更新和删除书籍
创建新书
在本节中,你将学习如何创建一个新的模态对话框表单来创建新书。该模态对话框将如下图所示:
创建模态表单
在 Acme.BookStore.Web 项目的 Pages/Books 文件夹下创建一个名为 CreateModal.cshtml 的新 Razor 页面。
CreateModal.cshtml.cs
打开 CreateModal.cshtml.cs 文件(CreateModalModel 类),并用以下代码替换其内容:
using System.Threading.Tasks;
using Acme.BookStore.Books;
using Microsoft.AspNetCore.Mvc;
namespace Acme.BookStore.Web.Pages.Books
{
public class CreateModalModel : BookStorePageModel
{
[BindProperty]
public CreateUpdateBookDto Book { get; set; }
private readonly IBookAppService _bookAppService;
public CreateModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public void OnGet()
{
Book = new CreateUpdateBookDto();
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.CreateAsync(Book);
return NoContent();
}
}
}
- 该类派生自
BookStorePageModel而不是标准的PageModel。BookStorePageModel间接继承PageModel,并添加了一些可以在你的页面模型类中共享的通用属性和方法。 Book属性上的[BindProperty]特性将 POST 请求数据绑定到该属性。- 此类在构造函数中简单地注入了
IBookAppService,并在OnPostAsync处理程序中调用CreateAsync方法。 - 它在
OnGet方法中创建一个新的CreateUpdateBookDto对象。ASP.NET Core 在没有创建此类实例的情况下也可以工作。但是,它不会为你创建实例,如果你的类在构造函数中有一些默认值赋值或代码执行,它们将不会生效。因此,我们为CreateUpdateBookDto的某些属性设置了默认值。
CreateModal.cshtml
打开 CreateModal.cshtml 文件并粘贴以下代码:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model CreateModalModel
@inject IStringLocalizer<BookStoreResource> L
@{
Layout = null;
}
<abp-dynamic-form abp-model="Book" asp-page="/Books/CreateModal">
<abp-modal>
<abp-modal-header title="@L["NewBook"].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标记帮助程序 根据CreateUpdateBookDto模型类自动创建表单。 abp-model属性指示模型对象,本例中是Book属性。abp-form-content标记帮助程序是一个占位符,用于渲染表单控件(它是可选的,仅当你需要在abp-dynamic-form标记内添加其他内容时才需要,就像本页面一样)。
提示: 如本例所示,
Layout应设置为null,因为我们不希望模态框通过 AJAX 加载时包含所有布局。
添加“新建图书”按钮
打开 Pages/Books/Index.cshtml,并按如下所示设置 abp-card-header 标记的内容:
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<abp-card-title>@L["Books"]</abp-card-title>
</abp-column>
<abp-column size-md="_6" class="text-end">
<abp-button id="NewBookButton"
text="@L["NewBook"].Value"
icon="plus"
button-type="Primary"/>
</abp-column>
</abp-row>
</abp-card-header>
Index.cshtml 的最终内容如下所示:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@model IndexModel
@inject IStringLocalizer<BookStoreResource> L
@section scripts
{
<abp-script src="/Pages/Books/Index.js"/>
}
<abp-card>
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<abp-card-title>@L["Books"]</abp-card-title>
</abp-column>
<abp-column size-md="_6" class="text-end">
<abp-button id="NewBookButton"
text="@L["NewBook"].Value"
icon="plus"
button-type="Primary"/>
</abp-column>
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="BooksTable"></abp-table>
</abp-card-body>
</abp-card>
这将向表格的右上角添加一个名为新建图书的新按钮:
打开 Pages/Books/Index.js 文件,并在 Datatable 配置之后添加以下代码:
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
createModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
abp.ModalManager是一个辅助类,用于在客户端管理模态框。它在内部使用 Twitter Bootstrap 的标准模态框,但通过提供简单的 API 抽象了许多细节。createModal.onResult(...)用于在创建新书后刷新数据表。createModal.open();用于打开创建新书的模态框。
Index.js 文件的最终内容应如下所示:
$(function () {
var l = abp.localization.getResource('BookStore');
var dataTable = $('#BooksTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
order: [[1, "asc"]],
searching: false,
scrollX: true,
ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList),
columnDefs: [
{
title: l('Name'),
data: "name"
},
{
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType.' + data);
}
},
{
title: l('PublishDate'),
data: "publishDate",
dataFormat: "datetime"
},
{
title: l('Price'),
data: "price"
},
{
title: l('CreationTime'), data: "creationTime",
dataFormat: "datetime"
}
]
})
);
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
createModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
现在,你可以运行应用程序并使用新的模态表单添加一些新书。
更新书籍
在 Acme.BookStore.Web 项目的 Pages/Books 文件夹下创建一个名为 EditModal.cshtml 的新 Razor 页面:
EditModal.cshtml.cs
打开 EditModal.cshtml.cs 文件(EditModalModel 类),并用以下代码替换其内容:
using System;
using System.Threading.Tasks;
using Acme.BookStore.Books;
using Microsoft.AspNetCore.Mvc;
namespace Acme.BookStore.Web.Pages.Books;
public class EditModalModel : BookStorePageModel
{
[HiddenInput]
[BindProperty(SupportsGet = true)]
public Guid Id { get; set; }
[BindProperty]
public CreateUpdateBookDto Book { get; set; }
private readonly IBookAppService _bookAppService;
public EditModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public async Task OnGetAsync()
{
var bookDto = await _bookAppService.GetAsync(Id);
Book = ObjectMapper.Map<BookDto, CreateUpdateBookDto>(bookDto);
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.UpdateAsync(Id, Book);
return NoContent();
}
}
[HiddenInput]和[BindProperty]是标准的 ASP.NET Core MVC 特性。SupportsGet用于能够从请求的查询字符串参数中获取Id值。- 在
OnGetAsync方法中,我们从BookAppService获取BookDto,并将其映射到 DTO 对象CreateUpdateBookDto。 OnPostAsync使用BookAppService.UpdateAsync(...)来更新实体。
从 BookDto 映射到 CreateUpdateBookDto
为了能够将 BookDto 映射到 CreateUpdateBookDto,需要配置一个新的映射。为此,请打开 Acme.BookStore.Web 项目中的 BookStoreWebMappers.cs 文件,并按如下所示进行更改:
[Mapper]
public partial class BookDtoToCreateUpdateBookDtoMapper : MapperBase<BookDto, CreateUpdateBookDto>
{
public override partial CreateUpdateBookDto Map(BookDto source);
public override partial void Map(BookDto source, CreateUpdateBookDto destination);
}
注意: 作为最佳实践,我们在 Web 层中进行映射定义,因为只有该层需要它。
EditModal.cshtml
将 EditModal.cshtml 内容替换为以下内容:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel
@inject IStringLocalizer<BookStoreResource> L
@{
Layout = null;
}
<abp-dynamic-form abp-model="Book" asp-page="/Books/EditModal">
<abp-modal>
<abp-modal-header title="@L["Update"].Value"></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>
此页面与 CreateModal.cshtml 非常相似,不同之处在于:
- 它包含一个用于
Id属性的abp-input,用于存储要编辑书籍的Id(这是一个隐藏输入)。 - 它使用
Books/EditModal作为 POST URL。
向表格添加“操作”下拉菜单
我们将向表格添加一个名为操作的下拉按钮。
打开 Pages/Books/Index.js 文件,并按如下所示替换内容:
$(function () {
var l = abp.localization.getResource('BookStore');
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');
var dataTable = $('#BooksTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
order: [[1, "asc"]],
searching: false,
scrollX: true,
ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList),
columnDefs: [
{
title: l('Actions'),
rowAction: {
items:
[
{
text: l('Edit'),
action: function (data) {
editModal.open({ id: data.record.id });
}
}
]
}
},
{
title: l('Name'),
data: "name"
},
{
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType.' + data);
}
},
{
title: l('PublishDate'),
data: "publishDate",
dataFormat: "datetime"
},
{
title: l('Price'),
data: "price"
},
{
title: l('CreationTime'), data: "creationTime",
dataFormat: "datetime"
}
]
})
);
createModal.onResult(function () {
dataTable.ajax.reload();
});
editModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
- 添加了一个名为
editModal的新ModalManager来打开编辑模态对话框。 - 在
columnDefs部分的开头添加了一个新列。此列用于“操作”下拉按钮。 - “编辑”操作只是调用
editModal.open()来打开编辑对话框。 - 当你关闭编辑模态框时,
editModal.onResult(...)回调会刷新数据表。
你可以运行应用程序并通过选择某本书的编辑操作来编辑任何书籍。
最终用户界面如下所示:
注意: 在下图中你看不到“操作”按钮。相反,你看到一个“编辑”按钮。当下拉菜单中只有一个项目时,ABP 足够智能,会显示一个简单的按钮而不是操作下拉按钮。在下一节之后,它将变成一个下拉按钮。
删除书籍
打开 Pages/Books/Index.js 文件,并向 rowAction 的 items 添加一个新项目:
{
text: l('Delete'),
confirmMessage: function (data) {
return l('BookDeletionConfirmationMessage', data.record.name);
},
action: function (data) {
acme.bookStore.books.book
.delete(data.record.id)
.then(function() {
abp.notify.info(l('SuccessfullyDeleted'));
dataTable.ajax.reload();
});
}
}
confirmMessage选项用于在执行action之前询问确认问题。acme.bookStore.books.book.delete(...)方法向服务器发出 AJAX 请求以删除书籍。abp.notify.info()在删除操作后显示通知。
由于我们使用了两个新的本地化文本(BookDeletionConfirmationMessage 和 SuccessfullyDeleted),你需要将它们添加到本地化文件中(Acme.BookStore.Domain.Shared 项目的 Localization/BookStore 文件夹下的 en.json):
"BookDeletionConfirmationMessage": "确定要删除书籍 '{0}' 吗?",
"SuccessfullyDeleted": "删除成功!"
最终的 Index.js 内容如下所示:
$(function () {
var l = abp.localization.getResource('BookStore');
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');
var dataTable = $('#BooksTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
order: [[1, "asc"]],
searching: false,
scrollX: true,
ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList),
columnDefs: [
{
title: l('Actions'),
rowAction: {
items:
[
{
text: l('Edit'),
action: function (data) {
editModal.open({ id: data.record.id });
}
},
{
text: l('Delete'),
confirmMessage: function (data) {
return l(
'BookDeletionConfirmationMessage',
data.record.name
);
},
action: function (data) {
acme.bookStore.books.book
.delete(data.record.id)
.then(function() {
abp.notify.info(
l('SuccessfullyDeleted')
);
dataTable.ajax.reload();
});
}
}
]
}
},
{
title: l('Name'),
data: "name"
},
{
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType.' + data);
}
},
{
title: l('PublishDate'),
data: "publishDate",
dataFormat: "datetime"
},
{
title: l('Price'),
data: "price"
},
{
title: l('CreationTime'), data: "creationTime",
dataFormat: "datetime"
}
]
})
);
createModal.onResult(function () {
dataTable.ajax.reload();
});
editModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
你可以运行应用程序并尝试删除一本书。
抠丁客







