## 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). > You can also watch [this video course](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) 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: ![bookstore-create-dialog](images/bookstore-create-dialog-2.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 : BookStorePageModel { [BindProperty] public CreateUpdateBookDto Book { get; set; } private readonly IBookAppService _bookAppService; public CreateModalModel(IBookAppService bookAppService) { _bookAppService = bookAppService; } public async Task OnPostAsync() { await _bookAppService.CreateAsync(Book); return NoContent(); } } } ```` * This class is derived from the `BookStorePageModel` instead of standard `PageModel`. `BookStorePageModel` 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.BookStorePage @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @model Acme.BookStore.Web.Pages.Books.CreateModalModel @{ Layout = null; } ```` * 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

@L["Books"]

```` 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 : 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]` 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(); } } } ```` * Just added `CreateMap();` as the mapping definition. #### EditModal.cshtml Replace `EditModal.cshtml` content with the following content: ````html @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; } ```` 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 @L["Actions"] @L["Name"] @L["Type"] @L["PublishDate"] @L["Price"] @L["CreationTime"] ```` * 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 `createModal` to open the create modal dialog. * 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. * "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`: ````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({ 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: ````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.