## 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-I.md )
* **Part II: Create, Update and Delete books (this tutorial)**
* [Part III: Integration Tests ](Part-III.md )
You can access to the **source code** of the application from [the GitHub repository ](https://github.com/volosoft/abp/tree/master/samples/BookStore ).
### 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:
![bookstore-create-dialog ](images/bookstore-create-dialog.png )
#### Create the Modal Form
Create a new razor page, named `CreateModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project:
![bookstore-add-create-dialog ](images/bookstore-add-create-dialog-v2.png )
##### CreateModal.cshtml.cs
Open the `CreateModal.cshtml.cs` file (`CreateModalModel` class) and replace with the following code:
````C#
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();
}
}
}
````
* This class is derived from the `BookStorePageModelBase` instead of standard `PageModel` . `BookStorePageModelBase` inherits the `PageModel` and adds some common properties/methods those can be used by your page model classes.
* `[BindProperty]` attribute on the `Book` property binds post request data to this property.
* This class simply injects the `IBookAppService` in its constructor and calls the `CreateAsync` method in the `OnPostAsync` handler.
##### CreateModal.cshtml
Open the `CreateModal.cshtml` file and paste the code below:
````html
@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 >
````
* This modal uses `abp-dynamic-form` tag helper to automatically create the form from the `CreateBookViewModel` class.
* `abp-model` attribute indicates the model object, the `Book` 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 the `abp-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:
````html
< 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:
![bookstore-new-book-button ](images/bookstore-new-book-button.png )
Open the `pages/books/index.js` and add the following code just after the datatable configuration:
````js
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:
![bookstore-add-edit-dialog ](images/bookstore-add-edit-dialog.png )
#### EditModal.cshtml.cs
Open the `EditModal.cshtml.cs` file (`EditModalModel` class) and replace with the following code:
````csharp
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]` and `[BindProperty]` are standard ASP.NET Core MVC attributes. Used `SupportsGet` to be able to get Id value from query string parameter of the request.
* Mapped `BookDto` (received from the `BookAppService.GetAsync` ) to `CreateUpdateBookDto` in the `GetAsync` method.
* The `OnPostAsync` simply uses `BookAppService.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:
````csharp
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:
````html
@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 >
````
This page is very similar to the `CreateModal.cshtml` except;
* It includes an `abp-input` for the `Id` 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:
![bookstore-books-table-actions ](images/bookstore-books-table-actions.png )
Open the `Pages/Books/Index.cshtml` page and change the table section as shown below:
````html
< 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:
````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();
});
});
````
* Used `abp.localization.getResource('BookStore')` to be able to use the same localization texts defined on the server side.
* Added a new `ModalManager` named `editModal` 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 `pages/books/index.js` and add a new item to the `rowAction` `items` :
````js
{
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 the `action` .
* 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:
````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();
});
});
````
Open the `en.json` in the `Acme.BookStore.Domain.Shared` project and add the following line:
````json
"BookDeletionConfirmationMessage": "Are you sure to delete the book {0}?"
````
Run the application and try to delete a book.
### Next Part
See the [next part ](Part-III.md ) of this tutorial.