ASP.NET Core MVC Tutorial - Part II
About this Tutorial
This is the second part of the ASP.NET Core MVC 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 access to the source code of the application from the GitHub repository.
You can also watch this video course prepared by an ABP community member, based on this tutorial.
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.Web.Pages.Books
{
public class CreateModalModel : BookStorePageModel
{
[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
BookStorePageModel
instead of standardPageModel
.BookStorePageModel
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.Web.Pages.BookStorePage
@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>
- 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 page).
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 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.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]
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.
BookDto to CreateUpdateBookDto Mapping
In order to perform BookDto
to CreateUpdateBookDto
object mapping, open the BookStoreWebAutoMapperProfile.cs
in the Acme.BookStore.Web
project and change it as shown below:
using AutoMapper;
namespace Acme.BookStore.Web
{
public class BookStoreWebAutoMapperProfile : Profile
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
}
}
}
- Just added
CreateMap<BookDto, CreateUpdateBookDto>();
as the mapping definition.
EditModal.cshtml
Replace EditModal.cshtml
content with the following content:
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@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>
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 (which is a hidden input). - 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 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({
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();
});
});
- Used
abp.localization.getResource('BookStore')
to be able to use the same localization texts defined on the server side. - Added a new
ModalManager
namedcreateModal
to open the create modal dialog. - 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. - "New Book" action simply calls
createModal.open
to open the create dialog. - "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 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({
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 });
}
},
{
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();
});
});
Open the en.json
in the Acme.BookStore.Domain.Shared
project and add the following line:
"BookDeletionConfirmationMessage": "Are you sure to delete the book {0}?",
"SuccessfullyDeleted": "Successfully deleted"
Run the application and try to delete a book.
Next Part
See the next part of this tutorial.