13 KiB
ASP.NET Core MVC 教程 - 第二章
关于本教程
这是ASP.NET Core MVC教程系列的第二章. 查看其它章节
- Part I: 创建项目和书籍列表页面
- Part II: 创建,编辑,删除书籍(本章)
- Part III: 集成测试
你可以从GitHub存储库访问应用程序的源代码.
新增 Book 实体
通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示:
新建 modal form
在 Acme.BookStore.Web 项目的 Pages/Books 目录下新建一个 CreateModal.cshtml Razor页面:
CreateModal.cshtml.cs
展开 CreateModal.cshtml,打开 CreateModal.cshtml.cs 代码文件,用如下代码替换 CreateModalModel 类的实现:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Acme.BookStore.Web.Pages.Books
{
public class CreateModalModel : BookStorePageModelBase
{
[BindProperty]
public CreateUpdateBookDto Book { get; set; }
private readonly IBookAppService _bookAppService;
public CreateModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.CreateAsync(Book);
return NoContent();
}
}
}
- 该类派生于
BookStorePageModelBase而非默认的PageModel.BookStorePageModelBase继承了PageModel并且添加了一些可以被你的page model类使用的通用属性和方法. Book属性上的[BindProperty]特性将post请求提交上来的数据绑定到该属性上.- 该类通过构造函数注入了
IBookAppService应用服务,并且在OnPostAsync处理程序中调用了服务的CreateAsync方法.
CreateModal.cshtml
打开 CreateModal.cshtml 文件并粘贴如下代码:
@page
@inherits Acme.BookStore.Web.Pages.BookStorePageBase
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model Acme.BookStore.Web.Pages.Books.CreateModalModel
@{
Layout = null;
}
<abp-dynamic-form abp-model="Book" data-ajaxForm="true" 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>
- 这个 modal 使用
abp-dynamic-formTag Helper 根据CreateBookViewModel类自动构建了表单.abp-model指定了Book属性为模型对象.data-ajaxForm设置了表单通过AJAX提交,而不是经典的页面回发.abp-form-contenttag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在abp-dynamic-form中像本示例这样添加了其他内容才需要).
添加 "New book" 按钮
打开 Pages/Books/Index.cshtml 并按如下代码修改 abp-card-header :
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<h2>@L["Books"]</h2>
</abp-column>
<abp-column size-md="_6" class="text-right">
<abp-button id="NewBookButton"
text="@L["NewBook"].Value"
icon="plus"
button-type="Primary" />
</abp-column>
</abp-row>
</abp-card-header>
如下图所示,只是在表格 右上方 添加了 New book 按钮:
打开 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是一个在客户端打开和管理modal的辅助类.它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节.
现在,你可以 运行程序 通过新的 modal form 来创建书籍了.
编辑更新已存在的 Book 实体
在 Acme.BookStore.Web 项目的 Pages/Books 目录下新建一个名叫 EditModal.cshtml 的Razor页面:
EditModal.cshtml.cs
展开 EditModal.cshtml,打开 EditModal.cshtml.cs 文件( EditModalModel 类) 并替换成以下代码:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Acme.BookStore.Web.Pages.Books
{
public class EditModalModel : BookStorePageModelBase
{
[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从Http请求的查询字符串中获取Id的值.- 在
OnGetAsync方法中,将BookAppService.GetAsync方法返回的BookDto映射成CreateUpdateBookDto并赋值给Book属性. OnPostAsync方法直接使用BookAppService.UpdateAsync来更新实体.
BookDto到CreateUpdateBookDto对象映射
为了执行BookDto到CreateUpdateBookDto对象映射,请打开Acme.BookStore.Web项目中的BookStoreWebAutoMapperProfile.cs并更改它,如下所示:
using AutoMapper;
namespace Acme.BookStore.Web
{
public class BookStoreWebAutoMapperProfile : Profile
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
}
}
}
- 刚刚添加了
CreateMap<BookDto, CreateUpdateBookDto>();作为映射定义.
EditModal.cshtml
将 EditModal.cshtml 页面内容替换成如下代码:
@page
@inherits Acme.BookStore.Web.Pages.BookStorePageBase
@using Acme.BookStore.Web.Pages.Books
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel
@{
Layout = null;
}
<abp-dynamic-form abp-model="Book" data-ajaxForm="true" 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(它是隐藏的Input) - 此页面指定的post地址是
Books/EditModal, 并用文本 Update 作为 modal 标题.
为表格添加 "操作(Actions)" 下拉菜单
我们将为表格每行添加下拉按钮 ("Actions") . 最终效果如下:
打开 Pages/Books/Index.cshtml 页面,并按下方所示修改表格部分的代码:
<abp-table striped-rows="true" id="BooksTable">
<thead>
<tr>
<th>@L["Actions"]</th>
<th>@L["Name"]</th>
<th>@L["Type"]</th>
<th>@L["PublishDate"]</th>
<th>@L["Price"]</th>
<th>@L["CreationTime"]</th>
</tr>
</thead>
</abp-table>
- 只是为"Actions"增加了一个
th标签.
打开 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({
processing: true,
serverSide: true,
paging: true,
searching: false,
autoWidth: false,
scrollCollapse: true,
order: [[1, "asc"]],
ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList),
columnDefs: [
{
rowAction: {
items:
[
{
text: l('Edit'),
action: function (data) {
editModal.open({ id: data.record.id });
}
}
]
}
},
{ data: "name" },
{ data: "type" },
{ data: "publishDate" },
{ data: "price" },
{ data: "creationTime" }
]
}));
createModal.onResult(function () {
dataTable.ajax.reload();
});
editModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
- 通过
abp.localization.getResource('BookStore')可以在客户端使用服务器端定义的相同的本地化语言文本. - 添加了一个名为
editModal的新的ModalManager来打开编辑用的 modal 对话框. - 在
columnDefs起始处新增一列用于显示 "Actions" 下拉按钮. - "Edit" 操作只是简单调用
editModal.open来打开编辑对话框.
现在,你可以运行程序,通过编辑操作来更新任一个book实体.
删除一个已有的Book实体
打开 Pages/books/index.js 文件,在 rowAction items 下新增一项:
{
text: l('Delete'),
confirmMessage: function (data) {
return l('BookDeletionConfirmationMessage', data.record.name);
},
action: function (data) {
acme.bookStore.book
.delete(data.record.id)
.then(function() {
abp.notify.info(l('SuccessfullyDeleted'));
dataTable.ajax.reload();
});
}
}
confirmMessage用来在实际执行action之前向用户进行确认.- 通过javascript代理方法
acme.bookStore.book.delete执行一个AJAX请求来删除一个book实体. abp.notify.info用来在执行删除操作后显示一个toastr通知信息.
最终的 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({
order: [[1, "asc"]],
ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList),
columnDefs: [
{
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.book
.delete(data.record.id)
.then(function() {
abp.notify.info(l('SuccessfullyDeleted'));
dataTable.ajax.reload();
});
}
}
]
}
},
{ data: "name" },
{ data: "type" },
{ data: "publishDate" },
{ data: "price" },
{ data: "creationTime" }
]
}));
createModal.onResult(function () {
dataTable.ajax.reload();
});
editModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
打开Acme.BookStore.Domain.Shared项目中的en.json并添加以下行:
"BookDeletionConfirmationMessage": "Are you sure to delete the book {0}?"
运行程序并尝试删除一个book实体.
下一章
查看本教程的 下一章 .




