diff --git a/docs/en/Tutorials/Part-10.md b/docs/en/Tutorials/Part-10.md index e161a267cc..08643eae67 100644 --- a/docs/en/Tutorials/Part-10.md +++ b/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 GetAsync(Guid id) + public override async Task GetAsync(Guid id) { await CheckGetPolicyAsync(); @@ -384,8 +384,8 @@ namespace Acme.BookStore.Books return bookDto; } - public async override Task> - GetListAsync(PagedAndSortedResultRequestDto input) + public override async Task> GetListAsync( + PagedAndSortedResultRequestDto input) { await CheckGetListPolicyAsync(); @@ -1082,36 +1082,37 @@ Add the following field to the `@code` section of the `Books.razor` file: IReadOnlyList authorList = Array.Empty(); ```` -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 authorList = Array.Empty(); - 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; } } diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index f4fcf61e25..7bc42b1fc9 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/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: @context.PublishDate.ToShortDateString() ` section with the following ````xml - - + +

@L["Books"]

- - - - + +
@@ -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 - + - - @L["NewBook"] - - - - - @L["Name"] - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - +
+ + @L["NewBook"] + + + + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + +
```` +This code requires a service; Inject the `AbpBlazorMessageLocalizerHelper` at the top of the file, just before the `@inherits...` line: + +````csharp +@inject AbpBlazorMessageLocalizerHelper 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 - + - - - @L["Actions"] - - - - @L["Edit"] - - - + + + - + ```` * `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 - + - - @EditingEntity.Name - - - - - @L["Name"] - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - +
+ + @EditingEntity.Name + + + + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + +
```` @@ -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 - - @L["Delete"] - + ```` -* `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 L +@inject AbpBlazorMessageLocalizerHelper LH @inherits AbpCrudPageBase - - + +

@L["Books"]

- - + - + Clicked="OpenCreateModalAsync">@L["NewBook"]
@@ -1408,27 +1433,19 @@ Here the complete code to create the book management CRUD page, that has been de ShowPager="true" PageSize="PageSize"> - + - - - @L["Actions"] - - - - @L["Edit"] - - - @L["Delete"] - - - + + + + - + @@ -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"]"> - @L[$"Enum:BookType:{(int)context.Type}"] + @L[$"Enum:BookType:{(int) context.Type}"]
- + - - @L["NewBook"] - - - - - @L["Name"] - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - +
+ + @L["NewBook"] + + + + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + +
- + - - @EditingEntity.Name - - - - - @L["Name"] - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - +
+ + @EditingEntity.Name + + + + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + +
```` diff --git a/docs/en/Tutorials/Part-4.md b/docs/en/Tutorials/Part-4.md index d9c000be5c..8dab28a8db 100644 --- a/docs/en/Tutorials/Part-4.md +++ b/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 } ); diff --git a/docs/en/Tutorials/Part-5.md b/docs/en/Tutorials/Part-5.md index 4243bfdad7..bba99f21df 100644 --- a/docs/en/Tutorials/Part-5.md +++ b/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) { + Clicked="OpenCreateModalAsync">@L["NewBook"] } ```` #### 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) -{ - - @L["Edit"] - -} -@if (canDeleteBook) -{ - - @L["Delete"] - -} + + + + ```` #### 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(); - var l = context.GetLocalizer(); - - 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" + )); } } ```` diff --git a/docs/en/Tutorials/Part-9.md b/docs/en/Tutorials/Part-9.md index fce94633d2..65a59f39e1 100644 --- a/docs/en/Tutorials/Part-9.md +++ b/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 L -@inject IAuthorizationService AuthorizationService -@inject IUiMessageService UiMessageService -@inject IObjectMapper ObjectMapper @@ -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; } diff --git a/docs/en/Tutorials/images/blazor-bookstore-book-list.png b/docs/en/Tutorials/images/blazor-bookstore-book-list.png index 91450f47c2..18bb26ccb1 100644 Binary files a/docs/en/Tutorials/images/blazor-bookstore-book-list.png and b/docs/en/Tutorials/images/blazor-bookstore-book-list.png differ diff --git a/docs/en/Tutorials/images/blazor-delete-book-action.png b/docs/en/Tutorials/images/blazor-delete-book-action.png new file mode 100644 index 0000000000..f2b0b83f30 Binary files /dev/null and b/docs/en/Tutorials/images/blazor-delete-book-action.png differ diff --git a/docs/en/Tutorials/images/blazor-edit-book-action-2.png b/docs/en/Tutorials/images/blazor-edit-book-action-2.png new file mode 100644 index 0000000000..70856ab325 Binary files /dev/null and b/docs/en/Tutorials/images/blazor-edit-book-action-2.png differ