- 关于本教程
- 下载源代码
- 创建新书
- 创建模态表单
- 添加“新书”按钮
- 更新书
- EditModal.cshtml.cs
- 从BookDto映射到CreateUpdateBookDto
- EditModal.cshtml
- 为表格添加 "操作(Actions)" 下拉菜单
- 删除书
- 下一部分
本教程基于版本3.1
在本教程系列中,您将构建一个名为Acme.BookStore
的基于ABP的Web应用程序。该应用程序用于管理书籍及其作者的列表。它是使用以下技术开发的:
- 实体框架核心作为ORM提供者。
- MVC/Razor页面作为UI框架。
本教程分为以下部分:
第1部分:创建服务器端
第2部分:图书列表页面
第3部分:创建、更新和删除书籍(此部分)
第4部分:集成测试
第5部分:授权
第6部分:作者:领领域层
第7部分:作者:数据库集成
第8部分:作者:应用程序层
第9部分:作者:用户界面
第10部分:书与作者的关系
下载源代码MVC (Razor Pages) UI with EF Core
创建新书在本节中,您将学习如何创建新的模态对话框形式来创建新书。模态对话框如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GZKn09IM-1601124148452)(bookstore-create-dialog-2.png)]
创建模态表单创建一个名为CreateModal.cshtml
新的razor页面,在Acme.BookStore.Web
项目Pages/Books
的文件夹下。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HH4l1JWn-1601124148453)(bookstore-add-create-dialog-v2.png)]
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 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 L
@{
Layout = null;
}
-
该模式使用
abp-dynamic-form
标签帮助程序从CreateBookViewModel
模型类自动创建表单 。 -
在这种情况下,
abp-model
attribute指示模型对象所在的Book
属性。 -
abp-form-content
标记帮助程序是呈现表单控件的占位符(仅当您在abp-dynamic-form
标记中添加了其他内容(如此页面一样)时,它才是可选的,并且是必需的)。
提示:Layout
应该是null
,像在这个例子中做的那样,因为当通过AJAX加载时,我们不希望包括模态(modals)的所有布局。
打开Pages/Books/Index.cshtml
并设置abp-card-header
标签内容,如下所示:
@L["Books"]
Index.cshtml
的最终内容如下所示:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@model IndexModel
@inject IStringLocalizer L
@section scripts
{
}
@L["Books"]
这将在表格的右上角添加一个名为New book的新按钮:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CjWfKqS6-1601124148454)(bookstore-new-book-button-2.png)]
打开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",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString();
}
},
{
title: l('Price'),
data: "price"
},
{
title: l('CreationTime'), data: "creationTime",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString(luxon.DateTime.DATETIME_SHORT);
}
}
]
})
);
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
createModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
现在,您可以运行该应用程序,并使用新的模态形式添加一些新书。
更新书创建一个名为EditModal.cshtml
新的razor页面,在Acme.BookStore.Web
项目下Pages/Books
的文件夹:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ush3qQLy-1601124148457)(bookstore-add-edit-dialog.png)]
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);
}
public async Task 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
,请配置新的映射。为此,请在Acme.BookStore.Web
项目中打开BookStoreWebAutoMapperProfile.cs
并按如下所示进行更改:
using AutoMapper;
namespace Acme.BookStore.Web
{
public class BookStoreWebAutoMapperProfile : Profile
{
public BookStoreWebAutoMapperProfile()
{
CreateMap();
}
}
}
- 我们刚刚添加了
CreateMap();
以定义此映射。
注意,由于我们仅在该层中需要映射定义,因此我们将其作为在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 L
@{
Layout = null;
}
该页面与十分相似CreateModal.cshtml,除了:
-
它包括一个abp-input用于Id存储Id编辑书的属性(这是一个隐藏的输入)。
-
它Books/EditModal用作发布URL。
我们将在名为Actions
的表中添加一个下拉按钮。
打开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",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString();
}
},
{
title: l('Price'),
data: "price"
},
{
title: l('CreationTime'), data: "creationTime",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString(luxon.DateTime.DATETIME_SHORT);
}
}
]
})
);
createModal.onResult(function () {
dataTable.ajax.reload();
});
editModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
-
添加了一个新的命名为
editModal
的ModalManager
以打开编辑模态对话框。 -
在本
columnDefs
节的开头添加了新列。此列用于“操作(Actions)”下拉按钮。 -
“Edit”操作只需调用
editModal.open()
即可打开编辑对话框。 -
关闭编辑模态时,
editModal.onResult(...)
回调会刷新数据表。
您可以通过选择一本书的编辑操作来运行该应用程序并编辑任何一本书。
最终的UI如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWznCP1U-1601124148458)(bookstore-edit-button-2.png)]
删除书打开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": "Are you sure to delete the book '{0}'?",
"SuccessfullyDeleted": "Successfully deleted!"
最终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",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString();
}
},
{
title: l('Price'),
data: "price"
},
{
title: l('CreationTime'), data: "creationTime",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString(luxon.DateTime.DATETIME_SHORT);
}
}
]
})
);
createModal.onResult(function () {
dataTable.ajax.reload();
});
editModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
您可以运行该应用程序并尝试删除一本书。
下一部分请参阅本教程的下一部分。