13 KiB
ASP.NET Core MVC Tutorial - Part II
About this Tutorial
This is the second part of the tutorial series. See all parts:
- Part I: Create the project and a book list page
- Part II: Create, Update and Delete books (this tutorial)
- Part III: Integration Tests
You can download the source code of the application from here.
Creating a New Book
In this section, you will learn how to create a new modal dialog form to create a new book. The result dialog will be like that:
Create the Modal Form
Create a new razor page, named CreateModal.cshtml
under the Pages/Books
folder of the Acme.BookStore.Web
project:
CreateModal.cshtml.cs
Open the CreateModal.cshtml.cs
file (CreateModalModel
class) and replace with the following code:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Acme.BookStore.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();
}
}
}
- This class is derived from the
BookStorePageModelBase
instead of standardPageModel
.BookStorePageModelBase
inherits thePageModel
and adds some common properties/methods those can be used by your page model classes. [BindProperty]
attribute on theBook
property binds post request data to this property.- This class simply injects the
IBookAppService
in its constructor and calls theCreateAsync
method in theOnPostAsync
handler.
CreateModal.cshtml
Open the CreateModal.cshtml
file and paste the code below:
@page
@inherits Acme.BookStore.Pages.BookStorePageBase
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model Acme.BookStore.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>
- This modal uses
abp-dynamic-form
tag helper to automatically create the form from theCreateBookViewModel
class.abp-model
attribute indicates the model object, theBook
property in this case.data-ajaxForm
attribute makes the form submitting via AJAX, instead of a classic page post.abp-form-content
tag helper is a placeholder to render the form controls (this is optional and needed only if you added some other content in theabp-dynamic-form
tag, just like in this view).
Add the "New book" Button
Open the Pages/Books/Index.cshtml
and change the abp-card-header
tag as shown below:
<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>
Just added a New book button to the top right of the table:
Open the wwwroot/pages/books/index.js
and add the following code just after the datatable configuration:
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
is a helper class to open and manage modals in the client side. It internally uses Twitter Bootstrap's standard modal, but abstracts many details by providing a simple API.
Now, you can run the application and add new books using the new modal form.
Updating An Existing Book
Create a new razor page, named EditModal.cshtml
under the Pages/Books
folder of the Acme.BookStore.Web
project:
EditModal.cshtml.cs
Open the EditModal.cshtml.cs
file (EditModalModel
class) and replace with the following code:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Acme.BookStore.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]
and[BindProperty]
are standard ASP.NET Core MVC attributes. UsedSupportsGet
to be able to get Id value from query string parameter of the request.- Mapped
BookDto
(received from theBookAppService.GetAsync
) toCreateUpdateBookDto
in theGetAsync
method. - The
OnPostAsync
simply usesBookAppService.UpdateAsync
to update the entity.
CreateUpdateBookDto
In order to perform BookDto
to CreateUpdateBookDto
object mapping, change the CreateUpdateBookDto
class as shown below:
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.AutoMapper;
namespace Acme.BookStore
{
[AutoMapTo(typeof(Book))]
[AutoMapFrom(typeof(BookDto))]
public class CreateUpdateBookDto
{
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
public DateTime PublishDate { get; set; }
[Required]
public float Price { get; set; }
}
}
- Just added the
[AutoMapFrom(typeof(BookDto))]
attribute to create the mapping.
EditModal.cshtml
Replace EditModal.cshtml
content with the following content:
@page
@inherits Acme.BookStore.Pages.BookStorePageBase
@using Acme.BookStore.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>
This page is very similar to the CreateModal.cshtml
except;
- It includes an
abp-input
for theId
property to store id of the editing book. - It uses
Books/EditModal
as the post URL and Update text as the modal header.
Add "Actions" Dropdown to the Table
We will add a dropdown button ("Actions") for each row of the table. The final UI looks like this:
Open the Pages/Books/Index.cshtml
page and change the table section as shown below:
<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>
- Just added a new
th
tag for the "Actions".
Open the wwwroot/pages/books/index.js
and replace the content as below:
$(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 });
}
}
]
}
},
{ 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();
});
});
- Used
abp.localization.getResource('BookStore')
to be able to use the same localization texts defined on the server side. - Added a new
ModalManager
namededitModal
to open the edit modal dialog. - Added a new column at the beginning of the
columnDefs
section. This column is used for the "Actions" dropdown button. - "Edit" action simply calls
editModal.open
to open the edit dialog.
You can run the application and edit any book by selecting the edit action.
Deleting an Existing Book
Open the wwwroot/pages/books/index.js
and add a new item to the 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
option is used to ask a confirmation question before executing theaction
.- Used
acme.bookStore.book.delete
javascript proxy function to perform an AJAX request to delete a book. abp.notify.info
is used to show a toastr notification just after the deletion.
The final index.js
content is shown below:
$(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();
});
});
Run the application and try to delete a book.
Next Part
See the next part of this tutorial.