Web Application Development Tutorial - Part 9: Authors: User Interface

    • Entity Framework Core as the ORM provider.
    • MVC / Razor Pages as the UI Framework.

    This tutorial is organized as the following parts;

    This tutorials has multiple versions based on your UI and Database preferences. We’ve prepared two combinations of the source code to be downloaded:

    Introduction

    This part explains how to create a CRUD page for the Author entity introduced in previous parts.

    Create a new razor page, Index.cshtml under the Pages/Authors folder of the Acme.BookStore.Web project and change the content as given below.

    Index.cshtml

    This is a simple page similar to the Books page we had created before. It imports a JavaScript file which will be introduced below.

    IndexModel.cshtml.cs

    1. using Microsoft.AspNetCore.Mvc.RazorPages;
    2. namespace Acme.BookStore.Web.Pages.Authors
    3. {
    4. public class IndexModel : PageModel
    5. {
    6. public void OnGet()
    7. {
    8. }
    9. }
    10. }
    1. $(function () {
    2. var l = abp.localization.getResource('BookStore');
    3. var createModal = new abp.ModalManager(abp.appPath + 'Authors/CreateModal');
    4. var editModal = new abp.ModalManager(abp.appPath + 'Authors/EditModal');
    5. var dataTable = $('#AuthorsTable').DataTable(
    6. abp.libs.datatables.normalizeConfiguration({
    7. serverSide: true,
    8. paging: true,
    9. order: [[1, "asc"]],
    10. searching: false,
    11. scrollX: true,
    12. ajax: abp.libs.datatables.createAjax(acme.bookStore.authors.author.getList),
    13. columnDefs: [
    14. {
    15. title: l('Actions'),
    16. rowAction: {
    17. items:
    18. [
    19. {
    20. text: l('Edit'),
    21. visible:
    22. abp.auth.isGranted('BookStore.Authors.Edit'),
    23. action: function (data) {
    24. editModal.open({ id: data.record.id });
    25. }
    26. },
    27. {
    28. text: l('Delete'),
    29. visible:
    30. abp.auth.isGranted('BookStore.Authors.Delete'),
    31. return l(
    32. 'AuthorDeletionConfirmationMessage',
    33. data.record.name
    34. );
    35. },
    36. action: function (data) {
    37. acme.bookStore.authors.author
    38. .delete(data.record.id)
    39. .then(function() {
    40. abp.notify.info(
    41. l('SuccessfullyDeleted')
    42. );
    43. dataTable.ajax.reload();
    44. });
    45. }
    46. }
    47. ]
    48. }
    49. },
    50. {
    51. title: l('Name'),
    52. data: "name"
    53. },
    54. {
    55. title: l('BirthDate'),
    56. data: "birthDate",
    57. render: function (data) {
    58. return luxon
    59. .DateTime
    60. locale: abp.localization.currentCulture.name
    61. }).toLocaleString();
    62. }
    63. }
    64. ]
    65. })
    66. );
    67. createModal.onResult(function () {
    68. dataTable.ajax.reload();
    69. });
    70. editModal.onResult(function () {
    71. dataTable.ajax.reload();
    72. });
    73. $('#NewAuthorButton').click(function (e) {
    74. e.preventDefault();
    75. createModal.open();
    76. });
    77. });

    Briefly, this JavaScript page;

    • Creates a Data table with Actions, Name and BirthDate columns.
      • Actions column is used to add Edit and Delete actions.
      • BirthDate provides a render function to format the DateTime value using the luxon library.
    • Uses the abp.ModalManager to open Create and Edit modal forms.

    This code is very similar to the Books page created before, so we will not explain it more.

    Localizations

    This page uses some localization keys we need to declare. Open the en.json file under the Localization/BookStore folder of the Acme.BookStore.Domain.Shared project and add the following entries:

    Notice that we’ve added more keys. They will be used in the next sections.

    Add to the Main Menu

    1. if (await context.IsGrantedAsync(BookStorePermissions.Authors.Default))
    2. {
    3. bookStoreMenu.AddItem(new ApplicationMenuItem(
    4. "BooksStore.Authors",
    5. l["Menu:Authors"],
    6. url: "/Authors"
    7. ));
    8. }

    Run and login to the application. You can not see the menu item since you don’t have permission yet. Go to the Identity/Roles page, click to the Actions button and select the Permissions action for the admin role:

    As you see, the admin role has no Author Management permissions yet. Click to the checkboxes and save the modal to grant the necessary permissions. You will see the Authors menu item under the Book Store in the main menu, after refreshing the page:

    bookstore-authors-page

    The page is fully working except New author and Actions/Edit since we haven’t implemented them yet.

    Create Modal

    Create a new razor page, CreateModal.cshtml under the Pages/Authors folder of the Acme.BookStore.Web project and change the content as given below.

    CreateModal.cshtml

    1. @page
    2. @using Acme.BookStore.Localization
    3. @using Acme.BookStore.Web.Pages.Authors
    4. @using Microsoft.Extensions.Localization
    5. @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
    6. @model CreateModalModel
    7. @inject IStringLocalizer<BookStoreResource> L
    8. @{
    9. Layout = null;
    10. }
    11. <form asp-page="/Authors/CreateModal">
    12. <abp-modal-header title="@L["NewAuthor"].Value"></abp-modal-header>
    13. <abp-modal-body>
    14. <abp-input asp-for="Author.Name" />
    15. <abp-input asp-for="Author.BirthDate" />
    16. <abp-input asp-for="Author.ShortBio" />
    17. </abp-modal-body>
    18. <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
    19. </abp-modal>
    20. </form>

    We had used of the ABP Framework for the books page before. We could use the same approach here, but we wanted to show how to do it manually. Actually, not so manually, because we’ve used abp-input tag helper in this case to simplify creating the form elements.

    You can definitely use the standard Bootstrap HTML structure, but it requires to write a lot of code. abp-input automatically adds validation, localization and other standard elements based on the data type.

    CreateModal.cshtml.cs

    The main reason of this decision was to show you how to use a different model class inside the page. But there is one more benefit: We added two attributes to the class members, which were not present in the CreateAuthorDto:

    • Added [DataType(DataType.Date)] attribute to the which shows a date picker on the UI for this property.
    • Added [TextArea] attribute to the ShortBio which shows a multi-line text area instead of a standard textbox.

    In this way, you can specialize the view model class based on your UI requirements without touching to the DTO. As a result of this decision, we have used ObjectMapper to map CreateAuthorViewModel to CreateAuthorDto. To be able to do that, you need to add a new mapping code to the BookStoreWebAutoMapperProfile constructor:

    1. using Acme.BookStore.Authors; // ADDED NAMESPACE IMPORT
    2. using Acme.BookStore.Books;
    3. using AutoMapper;
    4. namespace Acme.BookStore.Web
    5. {
    6. public class BookStoreWebAutoMapperProfile : Profile
    7. {
    8. public BookStoreWebAutoMapperProfile()
    9. {
    10. CreateMap<BookDto, CreateUpdateBookDto>();
    11. // ADD a NEW MAPPING
    12. CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
    13. CreateAuthorDto>();
    14. }
    15. }
    16. }

    “New author” button will work as expected and open a new model when you run the application again:

    Create a new razor page, EditModal.cshtml under the Pages/Authors folder of the Acme.BookStore.Web project and change the content as given below.

    1. @page
    2. @using Acme.BookStore.Localization
    3. @using Acme.BookStore.Web.Pages.Authors
    4. @using Microsoft.Extensions.Localization
    5. @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
    6. @model EditModalModel
    7. @inject IStringLocalizer<BookStoreResource> L
    8. @{
    9. Layout = null;
    10. }
    11. <form asp-page="/Authors/EditModal">
    12. <abp-modal>
    13. <abp-modal-header title="@L["Update"].Value"></abp-modal-header>
    14. <abp-modal-body>
    15. <abp-input asp-for="Author.Id" />
    16. <abp-input asp-for="Author.Name" />
    17. <abp-input asp-for="Author.BirthDate" />
    18. <abp-input asp-for="Author.ShortBio" />
    19. </abp-modal-body>
    20. <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
    21. </abp-modal>
    22. </form>

    EditModal.cshtml.cs

    This class is similar to the CreateModal.cshtml.cs while there are some main differences;

    • Uses the IAuthorAppService.GetAsync(...) method to get the editing author from the application layer.
    • EditAuthorViewModel has an additional Id property which is marked with the [HiddenInput] attribute that creates a hidden input for this property.

    This class requires to add two object mapping declarations to the BookStoreWebAutoMapperProfile class:

    1. using Acme.BookStore.Authors;
    2. using Acme.BookStore.Books;
    3. using AutoMapper;
    4. namespace Acme.BookStore.Web
    5. {
    6. public class BookStoreWebAutoMapperProfile : Profile
    7. {
    8. public BookStoreWebAutoMapperProfile()
    9. {
    10. CreateMap<BookDto, CreateUpdateBookDto>();
    11. CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
    12. CreateAuthorDto>();
    13. // ADD THESE NEW MAPPINGS
    14. CreateMap<AuthorDto, Pages.Authors.EditModalModel.EditAuthorViewModel>();
    15. CreateMap<Pages.Authors.EditModalModel.EditAuthorViewModel,
    16. UpdateAuthorDto>();
    17. }
    18. }

    That’s all! You can run the application and try to edit an author.

    The Next Part

    See the next part of this tutorial.