Browse Source

Merge pull request #6241 from abpframework/blazor-tutorial-update-4

Updated the Tutorial for ABP version 4.0 changes
pull/6249/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
f0ec3d9bf8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 41
      docs/en/Tutorials/Part-10.md
  2. 5
      docs/en/Tutorials/Part-2.md
  3. 449
      docs/en/Tutorials/Part-3.md
  4. 4
      docs/en/Tutorials/Part-4.md
  5. 126
      docs/en/Tutorials/Part-5.md
  6. 17
      docs/en/Tutorials/Part-9.md
  7. BIN
      docs/en/Tutorials/images/blazor-bookstore-book-list.png
  8. BIN
      docs/en/Tutorials/images/blazor-delete-book-action.png
  9. BIN
      docs/en/Tutorials/images/blazor-edit-book-action-2.png

41
docs/en/Tutorials/Part-10.md

@ -50,7 +50,7 @@ public Guid AuthorId { get; set; }
{{if DB=="EF"}}
> In this tutorial, we preferred to not add a **navigation property** to the `Author` entity from the `Book` class (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their authors (just like we will done below) which makes your application code simpler.
> In this tutorial, we preferred to not add a **navigation property** to the `Author` entity from the `Book` class (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their authors (like we will done below) which makes your application code simpler.
{{end}}
@ -362,7 +362,7 @@ namespace Acme.BookStore.Books
DeletePolicyName = BookStorePermissions.Books.Create;
}
public async override Task<BookDto> GetAsync(Guid id)
public override async Task<BookDto> GetAsync(Guid id)
{
await CheckGetPolicyAsync();
@ -384,8 +384,8 @@ namespace Acme.BookStore.Books
return bookDto;
}
public async override Task<PagedResultDto<BookDto>>
GetListAsync(PagedAndSortedResultRequestDto input)
public override async Task<PagedResultDto<BookDto>> GetListAsync(
PagedAndSortedResultRequestDto input)
{
await CheckGetListPolicyAsync();
@ -1082,36 +1082,37 @@ Add the following field to the `@code` section of the `Books.razor` file:
IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();
````
And fill it in the `OnInitializedAsync` method, by adding the following code to the end of the method:
Override the `OnInitializedAsync` method and adding the following code:
````csharp
authorList = (await AppService.GetAuthorLookupAsync()).Items;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
authorList = (await AppService.GetAuthorLookupAsync()).Items;
}
````
* It is essential to call the `base.OnInitializedAsync()` since `AbpCrudPageBase` has some initialization code to be executed.
The final `@code` block should be the following:
````csharp
@code
{
bool canCreateBook;
bool canEditBook;
bool canDeleteBook;
//ADDED A NEW FIELD
IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();
protected async override Task OnInitializedAsync()
public Books() // Constructor
{
await base.OnInitializedAsync();
canCreateBook = await
AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create);
canEditBook = await
AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Edit);
canDeleteBook = await
AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Delete);
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Delete;
}
//GET AUTHORS
//GET AUTHORS ON INITIALIZATION
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
authorList = (await AppService.GetAuthorLookupAsync()).Items;
}
}

5
docs/en/Tutorials/Part-2.md

@ -590,7 +590,6 @@ Open the `Books.razor` and replace the content as the following:
````xml
@page "/books"
@using Volo.Abp.Application.Dtos
@using Volo.Abp.BlazoriseUI
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@ -620,14 +619,14 @@ Open the `Books.razor` and replace the content as the following:
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.PublishDate)"
Field="@nameof(BookDto.PublishDate)"
Caption="@L["PublishDate"]">
<DisplayTemplate>
@context.PublishDate.ToShortDateString()
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Price)"
Field="@nameof(BookDto.Price)"
Caption="@L["Price"]">
</DataGridColumn>
<DataGridColumn TItem="BookDto"

449
docs/en/Tutorials/Part-3.md

@ -1171,15 +1171,13 @@ Open the `Books.razor` and replace the `<CardHeader>` section with the following
````xml
<CardHeader>
<Row>
<Column ColumnSize="ColumnSize.Is6">
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Books"]</h2>
</Column>
<Column ColumnSize="ColumnSize.Is6">
<Paragraph Alignment="TextAlignment.Right">
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Paragraph>
<Column ColumnSize="ColumnSize.IsAuto">
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Column>
</Row>
</CardHeader>
@ -1196,48 +1194,67 @@ Now, we can add a modal that will be opened when we click to the button.
Open the `Books.razor` and add the following code to the end of the page:
````xml
<Modal @ref="CreateModal">
<Modal @ref="@CreateModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="NewEntity.Name" />
</Field>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate" />
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
<Form>
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
````
This code requires a service; Inject the `AbpBlazorMessageLocalizerHelper<T>` at the top of the file, just before the `@inherits...` line:
````csharp
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
````
* The form implements validation and the `AbpBlazorMessageLocalizerHelper` is used to simply localize the validation messages.
* `CreateModal` object, `CloseCreateModalAsync` and `CreateEntityAsync` method are defined by the base class. See the [Blazorise documentation](https://blazorise.com/docs/) if you want to understand the `Modal` and the other components.
That's all. Run the application and try to add a new book:
@ -1250,78 +1267,81 @@ Editing a books is similar to the creating a new book.
### Actions Dropdown
Open the `Books.razor` and add the following `DataGridColumn` section inside the `DataGridColumns` as the first item:
Open the `Books.razor` and add the following `DataGridEntityActionsColumn` section inside the `DataGridColumns` as the first item:
````xml
<DataGridColumn Width="150px"
TItem="BookDto"
Field="@nameof(BookDto.Id)"
Sortable="false"
Caption="@L["Actions"]">
<DataGridEntityActionsColumn TItem="BookDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<Dropdown>
<DropdownToggle Color="Color.Primary">
@L["Actions"]
</DropdownToggle>
<DropdownMenu>
<DropdownItem Clicked="() => OpenEditModalAsync(context)">
@L["Edit"]
</DropdownItem>
</DropdownMenu>
</Dropdown>
<EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="BookDto"
Text="@L["Edit"]"
Clicked="() => OpenEditModalAsync(context)" />
</EntityActions>
</DisplayTemplate>
</DataGridColumn>
</DataGridEntityActionsColumn>
````
* `OpenEditModalAsync` is defined in the base class which takes the entity (book) to edit.
This adds an "Actions" dropdown to all the books inside the `DataGrid` with an `Edit` action:
`DataGridEntityActionsColumn` component is used to show an "Actions" dropdown for each row in the `DataGrid`. `DataGridEntityActionsColumn` shows a **single button** instead of a dropdown if there is only one available action inside it:
![blazor-edit-book-action](images/blazor-edit-book-action.png)
![blazor-edit-book-action](images/blazor-edit-book-action-2.png)
### Edit Modal
We can now define a modal to edit the book. Add the following code to the end of the `Books.razor` page:
````xml
<Modal @ref="EditModal">
<Modal @ref="@EditModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="EditingEntity.Name" />
</Field>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate" />
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
<Form>
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
````
@ -1356,17 +1376,26 @@ You can now run the application and try to edit a book.
![blazor-edit-book-modal](images/blazor-edit-book-modal.png)
> Tip: Try to leave the *Name* field empty and submit the form to show the validation error message.
## Deleting a Book
Open the `Books.razor` page and add the following `DropdownItem` under the "Edit" action inside the "Actions" `DropdownMenu`:
Open the `Books.razor` page and add the following `EntityAction` under the "Edit" action inside the `EntityActions`:
````xml
<DropdownItem Clicked="() => DeleteEntityAsync(context)">
@L["Delete"]
</DropdownItem>
<EntityAction TItem="BookDto"
Text="@L["Delete"]"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="@() => GetDeleteConfirmationMessage(context)" />
````
* `DeleteEntityAsync` is defined in the base class.
* `DeleteEntityAsync` is defined in the base class that deletes the entity by performing a call to the server.
* `ConfirmationMessage` is a callback to show a confirmation message before executing the action.
* `GetDeleteConfirmationMessage` is defined in the base class. You can override this method (or pass another value to the `ConfirmationMessage` parameter) to customize the localization message.
The "Actions" button becomes a dropdown since it has two actions now:
![blazor-edit-book-action](images/blazor-delete-book-action.png)
Run the application and try to delete a book.
@ -1377,26 +1406,22 @@ Here the complete code to create the book management CRUD page, that has been de
````xml
@page "/books"
@using Volo.Abp.Application.Dtos
@using Volo.Abp.BlazoriseUI
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<BookStoreResource> L
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
@inherits AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<Card>
<CardHeader>
<Row>
<Column ColumnSize="ColumnSize.Is6">
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Books"]</h2>
</Column>
<Column ColumnSize="ColumnSize.Is6">
<Paragraph Alignment="TextAlignment.Right">
<Column ColumnSize="ColumnSize.IsAuto">
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">
@L["NewBook"]
</Button>
</Paragraph>
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Column>
</Row>
</CardHeader>
@ -1408,27 +1433,19 @@ Here the complete code to create the book management CRUD page, that has been de
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridColumn Width="150px"
TItem="BookDto"
Field="@nameof(BookDto.Id)"
Sortable="false"
Caption="@L["Actions"]">
<DataGridEntityActionsColumn TItem="BookDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<Dropdown>
<DropdownToggle Color="Color.Primary">
@L["Actions"]
</DropdownToggle>
<DropdownMenu>
<DropdownItem Clicked="() => OpenEditModalAsync(context)">
@L["Edit"]
</DropdownItem>
<DropdownItem Clicked="() => DeleteEntityAsync(context)">
@L["Delete"]
</DropdownItem>
</DropdownMenu>
</Dropdown>
<EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="BookDto"
Text="@L["Edit"]"
Clicked="() => OpenEditModalAsync(context)" />
<EntityAction TItem="BookDto"
Text="@L["Delete"]"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)" />
</EntityActions>
</DisplayTemplate>
</DataGridColumn>
</DataGridEntityActionsColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Name)"
Caption="@L["Name"]"></DataGridColumn>
@ -1436,7 +1453,7 @@ Here the complete code to create the book management CRUD page, that has been de
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
<DisplayTemplate>
@L[$"Enum:BookType:{(int)context.Type}"]
@L[$"Enum:BookType:{(int) context.Type}"]
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
@ -1462,85 +1479,109 @@ Here the complete code to create the book management CRUD page, that has been de
</CardBody>
</Card>
<Modal @ref="CreateModal">
<Modal @ref="@CreateModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="NewEntity.Name" />
</Field>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate" />
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
<Form>
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
<Modal @ref="EditModal">
<Modal @ref="@EditModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="EditingEntity.Name" />
</Field>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate" />
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
<Form>
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
````

4
docs/en/Tutorials/Part-4.md

@ -126,7 +126,7 @@ public async Task Should_Create_A_Valid_Book()
{
Name = "New test book 42",
Price = 10,
PublishDate = System.DateTime.Now,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
@ -208,7 +208,7 @@ namespace Acme.BookStore.Books
{
Name = "New test book 42",
Price = 10,
PublishDate = System.DateTime.Now,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);

126
docs/en/Tutorials/Part-5.md

@ -70,7 +70,7 @@ namespace Acme.BookStore.Permissions
}
````
This is a hierarchical way of defining permission names. For example, "create book" permission name was defined as `BookStore.Books.Create`.
This is a hierarchical way of defining permission names. For example, "create book" permission name was defined as `BookStore.Books.Create`. ABP doesn't force you to a structure, but we find this way useful.
### Permission Definitions
@ -476,29 +476,29 @@ Adding this attribute prevents to enter this page if the current hasn't logged i
The book management page has a *New Book* button and *Edit* and *Delete* actions for each book. We should hide these buttons/actions if the current user has not granted for the related permissions.
#### Get the Permissions On Initialization
The base `AbpCrudPageBase` class already has the necessary functionality for these kind of operations.
#### Set the Policy (Permission) Names
Add the following code block to the end of the `Books.razor` file:
````csharp
@code
{
bool canCreateBook;
bool canEditBook;
bool canDeleteBook;
protected async override Task OnInitializedAsync()
public Books() // Constructor
{
await base.OnInitializedAsync();
canCreateBook =await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create);
canEditBook = await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Edit);
canDeleteBook = await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Delete);
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Delete;
}
}
````
We will use these `bool` fields to check the permissions. `AuthorizationService` comes from the base class as an injected property.
The base `AbpCrudPageBase` class automatically checks these permissions on the related operations. It also defines the given properties for us if we need to check them manually:
* `HasCreatePermission`: True, if the current user has permission to create the entity.
* `HasUpdatePermission`: True, if the current user has permission to edit/update the entity.
* `HasDeletePermission`: True, if the current user has permission to delete the entity.
> **Blazor Tip**: While adding the C# code into a `@code` block is fine for small code parts, it is suggested to use the code behind approach to develop a more maintainable code base when the code block becomes longer. We will use this approach for the authors part.
@ -507,32 +507,31 @@ We will use these `bool` fields to check the permissions. `AuthorizationService`
Wrap the *New Book* button by an `if` block as shown below:
````xml
@if (canCreateBook)
@if (HasCreatePermission)
{
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">
@L["NewBook"]
</Button>
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
}
````
#### Hide the Edit/Delete Actions
As similar to the *New Book* button, we can use `if` blocks to conditionally show/hide the *Edit* and *Delete* actions:
`EntityAction` component defines `RequiredPolicy` attribute (parameter) to conditionally show the action based on the user permissions.
Update the `EntityActions` section as shown below:
````xml
@if (canEditBook)
{
<DropdownItem Clicked="() => OpenEditModalAsync(context)">
@L["Edit"]
</DropdownItem>
}
@if (canDeleteBook)
{
<DropdownItem Clicked="() => DeleteEntityAsync(context)">
@L["Delete"]
</DropdownItem>
}
<EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="BookDto"
Text="@L["Edit"]"
RequiredPolicy="@UpdatePolicyName"
Clicked="() => OpenEditModalAsync(context)" />
<EntityAction TItem="BookDto"
Text="@L["Delete"]"
RequiredPolicy="@DeletePolicyName"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)" />
</EntityActions>
````
#### About the Permission Caching
@ -587,54 +586,39 @@ if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
}
````
You also need to add `async` keyword to the `ConfigureMenuAsync` method and re-arrange the return values. The final `BookStoreMenuContributor` class should be the following:
You also need to add `async` keyword to the `ConfigureMenuAsync` method and re-arrange the return value. The final `ConfigureMainMenuAsync` method should be the following:
````csharp
using System.Threading.Tasks;
using Acme.BookStore.Localization;
using Acme.BookStore.Permissions;
using Volo.Abp.UI.Navigation;
namespace Acme.BookStore.Blazor
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
public class BookStoreMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if(context.Menu.DisplayName != StandardMenus.Main)
{
return;
}
var l = context.GetLocalizer<BookStoreResource>();
var l = context.GetLocalizer<BookStoreResource>();
context.Menu.Items.Insert(
0,
new ApplicationMenuItem(
"BookStore.Home",
l["Menu:Home"],
"/",
icon: "fas fa-home"
)
);
context.Menu.Items.Insert(
0,
new ApplicationMenuItem(
"BookStore.Home",
l["Menu:Home"],
"/",
icon: "fas fa-home"
)
);
var bookStoreMenu = new ApplicationMenuItem(
"BooksStore",
l["Menu:BookStore"],
icon: "fa fa-book"
);
var bookStoreMenu = new ApplicationMenuItem(
"BooksStore",
l["Menu:BookStore"],
icon: "fa fa-book"
);
context.Menu.AddItem(bookStoreMenu);
context.Menu.AddItem(bookStoreMenu);
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
{
bookStoreMenu.AddItem(new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/books"
));
}
}
//CHECK the PERMISSION
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
{
bookStoreMenu.AddItem(new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/books"
));
}
}
````

17
docs/en/Tutorials/Part-9.md

@ -843,15 +843,8 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the `Acme.BookStor
````xml
@page "/authors"
@using Acme.BookStore.Authors
@using Acme.BookStore.Localization
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@using Volo.Abp.ObjectMapping
@inherits BookStoreComponentBase
@inject IAuthorAppService AuthorAppService
@inject IStringLocalizer<BookStoreResource> L
@inject IAuthorizationService AuthorizationService
@inject IUiMessageService UiMessageService
@inject IObjectMapper ObjectMapper
<Card>
<CardHeader>
<Row>
@ -1038,7 +1031,7 @@ namespace Acme.BookStore.Blazor.Pages
EditingAuthor = new UpdateAuthorDto();
}
protected async override Task OnInitializedAsync()
protected override async Task OnInitializedAsync()
{
await SetPermissionsAsync();
await GetAuthorsAsync();
@ -1048,10 +1041,10 @@ namespace Acme.BookStore.Blazor.Pages
{
CanCreateAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Create);
CanEditAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Edit);
CanDeleteAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Delete);
}
@ -1105,7 +1098,7 @@ namespace Acme.BookStore.Blazor.Pages
private async Task DeleteAuthorAsync(AuthorDto author)
{
var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name];
if (!await UiMessageService.ConfirmAsync(confirmMessage))
if (!await Message.Confirm(confirmMessage))
{
return;
}

BIN
docs/en/Tutorials/images/blazor-bookstore-book-list.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 60 KiB

BIN
docs/en/Tutorials/images/blazor-delete-book-action.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/en/Tutorials/images/blazor-edit-book-action-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Loading…
Cancel
Save