Completed the part 9

pull/4864/head
Halil İbrahim Kalkan 5 years ago
parent fd39fb9f4e
commit 56a653db10

@ -53,4 +53,459 @@ This tutorials has multiple versions based on your **UI** and **Database** prefe
This part explains how to create a CRUD page for the `Author` entity introduced in previous parts.
TODO...
{{if UI == "MVC"}}
## The Book List Page
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
````html
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Permissions
@using Acme.BookStore.Web.Pages.Authors
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<BookStoreResource> L
@inject IAuthorizationService AuthorizationService
@model IndexModel
@section scripts
{
<abp-script src="/Pages/Authors/Index.js"/>
}
<abp-card>
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<abp-card-title>@L["Authors"]</abp-card-title>
</abp-column>
<abp-column size-md="_6" class="text-right">
@if (await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Create))
{
<abp-button id="NewAuthorButton"
text="@L["NewAuthor"].Value"
icon="plus"
button-type="Primary"/>
}
</abp-column>
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="AuthorsTable"></abp-table>
</abp-card-body>
</abp-card>
````
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
````csharp
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Acme.BookStore.Web.Pages.Authors
{
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}
````
### Index.js
````js
$(function () {
var l = abp.localization.getResource('BookStore');
var createModal = new abp.ModalManager(abp.appPath + 'Authors/CreateModal');
var editModal = new abp.ModalManager(abp.appPath + 'Authors/EditModal');
var dataTable = $('#AuthorsTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
order: [[1, "asc"]],
searching: false,
scrollX: true,
ajax: abp.libs.datatables.createAjax(acme.bookStore.authors.author.getList),
columnDefs: [
{
title: l('Actions'),
rowAction: {
items:
[
{
text: l('Edit'),
visible:
abp.auth.isGranted('BookStore.Authors.Edit'),
action: function (data) {
editModal.open({ id: data.record.id });
}
},
{
text: l('Delete'),
visible:
abp.auth.isGranted('BookStore.Authors.Delete'),
confirmMessage: function (data) {
return l(
'AuthorDeletionConfirmationMessage',
data.record.name
);
},
action: function (data) {
acme.bookStore.authors.author
.delete(data.record.id)
.then(function() {
abp.notify.info(
l('SuccessfullyDeleted')
);
dataTable.ajax.reload();
});
}
}
]
}
},
{
title: l('Name'),
data: "name"
},
{
title: l('BirthDate'),
data: "birthDate",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString();
}
}
]
})
);
createModal.onResult(function () {
dataTable.ajax.reload();
});
editModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewAuthorButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
````
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](https://moment.github.io/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:
````json
"Menu:Authors": "Authors",
"Authors": "Authors",
"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?",
"BirthDate": "Birth date",
"NewAuthor": "New author"
````
Notice that we've added more keys. They will be used in the next sections.
### Add to the Main Menu
Open the `BookStoreMenuContributor.cs` in the `Menus` folder of the `Acme.BookStore.Web` project and add the following code in the end of the `ConfigureMainMenuAsync` method:
````csharp
if (await context.IsGrantedAsync(BookStorePermissions.Authors.Default))
{
bookStoreMenu.AddItem(new ApplicationMenuItem(
"BooksStore.Authors",
l["Menu:Authors"],
url: "/Authors"
));
}
````
### Run the Application
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**:
![bookstore-author-permissions](images/bookstore-author-permissions.png)
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](images/bookstore-authors-page.png)
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
```html
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Authors
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model CreateModalModel
@inject IStringLocalizer<BookStoreResource> L
@{
Layout = null;
}
<form asp-page="/Authors/CreateModal">
<abp-modal>
<abp-modal-header title="@L["NewAuthor"].Value"></abp-modal-header>
<abp-modal-body>
<abp-input asp-for="Author.Name" />
<abp-input asp-for="Author.BirthDate" />
<abp-input asp-for="Author.ShortBio" />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</form>
```
We had used [dynamic forms](../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) 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
```csharp
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace Acme.BookStore.Web.Pages.Authors
{
public class CreateModalModel : BookStorePageModel
{
[BindProperty]
public CreateAuthorViewModel Author { get; set; }
private readonly IAuthorAppService _authorAppService;
public CreateModalModel(IAuthorAppService authorAppService)
{
_authorAppService = authorAppService;
}
public void OnGet()
{
Author = new CreateAuthorViewModel();
}
public async Task<IActionResult> OnPostAsync()
{
var dto = ObjectMapper.Map<CreateAuthorViewModel, CreateAuthorDto>(Author);
await _authorAppService.CreateAsync(dto);
return NoContent();
}
public class CreateAuthorViewModel
{
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime BirthDate { get; set; }
[TextArea]
public string ShortBio { get; set; }
}
}
}
```
This page model class simply injects and uses the `IAuthorAppService` to create a new author. The main difference between the book creation model class is that this one is declaring a new class, `CreateAuthorViewModel`, for the view model instead of re-using the `CreateAuthorDto`.
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 `BirthDate` 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:
````csharp
using Acme.BookStore.Authors; // ADDED NAMESPACE IMPORT
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore.Web
{
public class BookStoreWebAutoMapperProfile : Profile
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
// ADD a NEW MAPPING
CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
CreateAuthorDto>();
}
}
}
````
"New author" button will work as expected and open a new model when you run the application again:
![bookstore-new-author-modal](images/bookstore-new-author-modal.png)
## Edit Modal
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.
### EditModal.cshtml
````html
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Authors
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel
@inject IStringLocalizer<BookStoreResource> L
@{
Layout = null;
}
<form asp-page="/Authors/EditModal">
<abp-modal>
<abp-modal-header title="@L["Update"].Value"></abp-modal-header>
<abp-modal-body>
<abp-input asp-for="Author.Id" />
<abp-input asp-for="Author.Name" />
<abp-input asp-for="Author.BirthDate" />
<abp-input asp-for="Author.ShortBio" />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</form>
````
### EditModal.cshtml.cs
```csharp
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace Acme.BookStore.Web.Pages.Authors
{
public class EditModalModel : BookStorePageModel
{
[BindProperty]
public EditAuthorViewModel Author { get; set; }
private readonly IAuthorAppService _authorAppService;
public EditModalModel(IAuthorAppService authorAppService)
{
_authorAppService = authorAppService;
}
public async Task OnGetAsync(Guid id)
{
var authorDto = await _authorAppService.GetAsync(id);
Author = ObjectMapper.Map<AuthorDto, EditAuthorViewModel>(authorDto);
}
public async Task<IActionResult> OnPostAsync()
{
await _authorAppService.UpdateAsync(
Author.Id,
ObjectMapper.Map<EditAuthorViewModel, UpdateAuthorDto>(Author)
);
return NoContent();
}
public class EditAuthorViewModel
{
[HiddenInput]
public Guid Id { get; set; }
[Required]
[StringLength(AuthorConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime BirthDate { get; set; }
[TextArea]
public string ShortBio { get; set; }
}
}
}
```
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:
```csharp
using Acme.BookStore.Authors;
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore.Web
{
public class BookStoreWebAutoMapperProfile : Profile
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
CreateAuthorDto>();
// ADD THESE NEW MAPPINGS
CreateMap<AuthorDto, Pages.Authors.EditModalModel.EditAuthorViewModel>();
CreateMap<Pages.Authors.EditModalModel.EditAuthorViewModel,
UpdateAuthorDto>();
}
}
}
```
That's all! You can run the application and try to edit an author.
{{else if UI == "NG"}}
TODO: Angular UI is being prepared...
{{end}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Loading…
Cancel
Save