diff --git a/docs/en/tutorials/book-store/part-02.md b/docs/en/tutorials/book-store/part-02.md
index 429d7ec17c1..f6d7f708e12 100644
--- a/docs/en/tutorials/book-store/part-02.md
+++ b/docs/en/tutorials/book-store/part-02.md
@@ -584,14 +584,14 @@ Open the `Books.razor` and replace the content as the following:
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
- @L[$"Enum:BookType.{context.Type}"]
+ @L[$"Enum:BookType.{(int)context.Item.Type}"]
- @context.PublishDate.ToShortDateString()
+ @context.Item.PublishDate.ToShortDateString()
- @context.CreationTime.ToLongDateString()
+ @context.Item.CreationTime.ToLongDateString()
@@ -651,9 +651,11 @@ Open the `Books.razor` and replace the content as the following:
-
+ RowsPerPage="@PageSize"
+ SortMode="SortMode.None">
@@ -678,12 +680,17 @@ Open the `Books.razor` and replace the content as the following:
+
+
+
@code
{
+ private MudDataGrid _booksDataGrid = default!;
+
public Books() // Constructor
{
LocalizationResource = typeof(BookStoreResource);
@@ -697,6 +704,7 @@ Open the `Books.razor` and replace the content as the following:
* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base class.
* `LocalizationResource` is set to the `BookStoreResource` to localize the texts.
* This page uses the standard MudBlazor `MudDataGrid` with `` definitions. ABP also ships an `AbpMudExtensibleDataGrid` that integrates with the [data table column extension system](../../framework/ui/blazor/data-table-column-extensions.md) when you need to extend module pages.
+* `AbpMudCrudPageBase` uses its own `AbpMudExtensibleDataGrid` to reload the data and to resolve the sort columns. Since this page uses the standard `MudDataGrid`, it keeps a reference to the grid (`_booksDataGrid`) to reload it after the create, update and delete operations in the next part, and disables sorting with `SortMode.None`.
While the code above is pretty easy to understand, you can check the MudBlazor [Card](https://mudblazor.com/components/card) and [DataGrid](https://mudblazor.com/components/datagrid) documents to understand them better.
@@ -712,6 +720,14 @@ You can run the application! The final UI of this part is shown below:

+{{if BlazorUI == "MudBlazor"}}
+
+This is a fully working, server side paged and localized table of books.
+
+{{else}}
+
This is a fully working, server side paged, sorted and localized table of books.
+{{end}}
+
{{end # UI }}
diff --git a/docs/en/tutorials/book-store/part-03.md b/docs/en/tutorials/book-store/part-03.md
index 259475b27db..45319f8409a 100644
--- a/docs/en/tutorials/book-store/part-03.md
+++ b/docs/en/tutorials/book-store/part-03.md
@@ -1147,9 +1147,8 @@ Now, we can add a modal that will be opened when we click the button.
Open the `Books.razor` and add the following code to the end of the page:
````xml
-
-
-
+
+
+
+
+
@@ -1769,7 +1831,7 @@ Here's the complete code to create the book management CRUD page, that has been
@L[$"Enum:BookType.{(int)bookTypeValue}"]
}
- @L[$"Enum:BookType.{(int)bookTypeValue}"]
}
- NewEntity?.PublishDate;
+ set { if (NewEntity != null && value.HasValue) NewEntity.PublishDate = value.Value; }
+ }
+
+ private DateTime? EditingEntityPublishDate
+ {
+ get => EditingEntity?.PublishDate;
+ set { if (EditingEntity != null && value.HasValue) EditingEntity.PublishDate = value.Value; }
+ }
+
+ private MudDataGrid _booksDataGrid = default!;
+
+ protected override async Task OnCreatedEntityAsync()
+ {
+ await InvokeAsync(() => _booksDataGrid.ReloadServerData());
+ await CloseCreateDialogAsync();
+ Snackbar.Add(GetCreateMessage(), Severity.Success);
+ }
+
+ protected override async Task OnUpdatedEntityAsync()
+ {
+ await InvokeAsync(() => _booksDataGrid.ReloadServerData());
+ await CloseEditDialogAsync();
+ Snackbar.Add(GetUpdateMessage(), Severity.Success);
+ }
+
+ protected override async Task OnDeletedEntityAsync()
+ {
+ // Go back to the previous page if the deleted book was the only one on the current page
+ if (_booksDataGrid.CurrentPage > 0 && _booksDataGrid.ServerItems.Count() == 1)
+ {
+ await InvokeAsync(() => _booksDataGrid.NavigateTo(Page.Previous));
+ }
+ else
+ {
+ await InvokeAsync(() => _booksDataGrid.ReloadServerData());
+ }
+
+ Snackbar.Add(GetDeleteMessage(), Severity.Success);
+ }
+
public Books() // Constructor
{
LocalizationResource = typeof(BookStoreResource);
diff --git a/docs/en/tutorials/book-store/part-05.md b/docs/en/tutorials/book-store/part-05.md
index 7449b4b65d9..e21f1209b34 100644
--- a/docs/en/tutorials/book-store/part-05.md
+++ b/docs/en/tutorials/book-store/part-05.md
@@ -80,10 +80,6 @@ public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvide
{
var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore"));
- //Dashboard permissions
- bookStoreGroup.AddPermission(BookStorePermissions.Dashboard.Host, L("Permission:Dashboard"), MultiTenancySides.Host);
- bookStoreGroup.AddPermission(BookStorePermissions.Dashboard.Tenant, L("Permission:Dashboard"), MultiTenancySides.Tenant);
-
//Books permissions
var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books"));
booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create"));
@@ -421,19 +417,16 @@ The base `AbpCrudPageBase` class already has the necessary functionality for the
#### Set the Policy (Permission) Names
-Add the following code block to the end of the `Books.razor` file:
+Update the constructor in the `@code` block of the `Books.razor` file as shown below:
````csharp
-@code
+public Books() // Constructor
{
- public Books() // Constructor
- {
- LocalizationResource = typeof(BookStoreResource);
-
- CreatePolicyName = BookStorePermissions.Books.Create;
- UpdatePolicyName = BookStorePermissions.Books.Edit;
- DeletePolicyName = BookStorePermissions.Books.Delete;
- }
+ LocalizationResource = typeof(BookStoreResource);
+
+ CreatePolicyName = BookStorePermissions.Books.Create;
+ UpdatePolicyName = BookStorePermissions.Books.Edit;
+ DeletePolicyName = BookStorePermissions.Books.Delete;
}
````
@@ -449,6 +442,8 @@ The base `AbpCrudPageBase` class automatically checks these permissions on the r
Wrap the *New Book* button by an `if` block as shown below:
+{{if BlazorUI == "Blazorise"}}
+
````xml
@if (HasCreatePermission)
{
@@ -457,8 +452,25 @@ Wrap the *New Book* button by an `if` block as shown below:
}
````
+{{end}}
+
+{{if BlazorUI == "MudBlazor"}}
+
+````razor
+@if (HasCreatePermission)
+{
+ @L["NewBook"]
+}
+````
+
+{{end}}
+
#### Hide the Edit/Delete Actions
+{{if BlazorUI == "Blazorise"}}
+
`EntityAction` component defines `Visible` attribute (parameter) to conditionally show the action.
Update the `EntityActions` section as shown below:
@@ -468,15 +480,36 @@ Update the `EntityActions` section as shown below:
+ Clicked="() => OpenEditModalAsync(context.Item)" />
+ Clicked="() => DeleteEntityAsync(context.Item)"
+ ConfirmationMessage="()=>GetDeleteConfirmationMessage(context.Item)" />
````
+{{end}}
+
+{{if BlazorUI == "MudBlazor"}}
+
+Wrap the menu items inside the actions `MudMenu` by `if` blocks as shown below:
+
+````razor
+
+ @if (HasUpdatePermission)
+ {
+ @L["Edit"]
+ }
+ @if (HasDeletePermission)
+ {
+ @L["Delete"]
+ }
+
+````
+
+{{end}}
+
#### About the Permission Caching
You can run and test the permissions. Remove a book related permission from the admin role to see the related button/action disappears from the UI.
diff --git a/docs/en/tutorials/book-store/part-09.md b/docs/en/tutorials/book-store/part-09.md
index 22c144dd14d..29a5acbae39 100644
--- a/docs/en/tutorials/book-store/part-09.md
+++ b/docs/en/tutorials/book-store/part-09.md
@@ -873,7 +873,7 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B
ShowPager="true"
PageSize="PageSize">
-
@if (CanEditAuthor)
{
-
+
@L["Edit"]
}
@if (CanDeleteAuthor)
{
-
+
@L["Delete"]
}
@@ -907,7 +907,7 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B
Field="@nameof(AuthorDto.BirthDate)"
Caption="@L["BirthDate"]">
- @context.BirthDate.ToShortDateString()
+ @context.Item.BirthDate.ToShortDateString()
@@ -915,9 +915,8 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B
-
-
-
+
+
@L["NewAuthor"]
@@ -928,25 +927,25 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B
@L["Name"]
-
+
-
+
@L["BirthDate"]
-
+ @L["ShortBio"]
-
+
-
+
@@ -967,9 +966,8 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B
-
-
-
+
+
@EditingAuthor.Name
@@ -980,25 +978,25 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B
@L["Name"]
-
+
-
+
@L["BirthDate"]
-
+ @L["ShortBio"]
-
+
-
+
@@ -1048,7 +1046,8 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B
-
@@ -1079,6 +1078,9 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B
+
+
+
@@ -1238,26 +1240,26 @@ public partial class Authors
await InvokeAsync(StateHasChanged);
}
- private void OpenCreateAuthorModal()
+ private async Task OpenCreateAuthorModal()
{
- CreateValidationsRef.ClearAll();
+ await CreateValidationsRef.ClearAll();
NewAuthor = new CreateAuthorDto();
- CreateAuthorModal.Show();
+ await CreateAuthorModal.Show();
}
- private void CloseCreateAuthorModal()
+ private async Task CloseCreateAuthorModal()
{
- CreateAuthorModal.Hide();
+ await CreateAuthorModal.Hide();
}
- private void OpenEditAuthorModal(AuthorDto author)
+ private async Task OpenEditAuthorModal(AuthorDto author)
{
- EditValidationsRef.ClearAll();
+ await EditValidationsRef.ClearAll();
EditingAuthorId = author.Id;
EditingAuthor = ObjectMapper.Map(author);
- EditAuthorModal.Show();
+ await EditAuthorModal.Show();
}
private async Task DeleteAuthorAsync(AuthorDto author)
@@ -1279,9 +1281,9 @@ public partial class Authors
}
}
- private void CloseEditAuthorModal()
+ private async Task CloseEditAuthorModal()
{
- EditAuthorModal.Hide();
+ await EditAuthorModal.Hide();
}
private async Task CreateAuthorAsync()
@@ -1292,7 +1294,7 @@ public partial class Authors
{
await AuthorAppService.CreateAsync(NewAuthor);
await GetAuthorsAsync();
- CreateAuthorModal.Hide();
+ await CreateAuthorModal.Hide();
}
}
catch(Exception ex)
@@ -1309,7 +1311,7 @@ public partial class Authors
{
await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
await GetAuthorsAsync();
- EditAuthorModal.Hide();
+ await EditAuthorModal.Hide();
}
}
catch(Exception ex)
@@ -1328,6 +1330,7 @@ public partial class Authors
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Acme.BookStore.Permissions;
@@ -1355,6 +1358,8 @@ public partial class Authors
private Guid EditingAuthorId { get; set; }
private UpdateAuthorDto EditingAuthor { get; set; }
+ private MudDataGrid AuthorsGrid { get; set; }
+
private MudDialog CreateAuthorDialog { get; set; }
private MudDialog EditAuthorDialog { get; set; }
@@ -1365,7 +1370,7 @@ public partial class Authors
// Bind to a nullable wrapper and sync back to the DTO before saving.
private DateTime? NewAuthorBirthDate
{
- get => NewAuthor?.BirthDate;
+ get => NewAuthor == null || NewAuthor.BirthDate == default ? null : NewAuthor.BirthDate;
set { if (NewAuthor != null && value.HasValue) NewAuthor.BirthDate = value.Value; }
}
@@ -1414,7 +1419,7 @@ public partial class Authors
TotalCount = (int)result.TotalCount;
}
- private async Task> OnDataGridReadAsync(GridState state)
+ private async Task> OnDataGridReadAsync(GridState state, CancellationToken cancellationToken = default)
{
CurrentSorting = state.SortDefinitions
.Where(s => !string.IsNullOrWhiteSpace(s.SortBy?.ToString()))
@@ -1463,7 +1468,16 @@ public partial class Authors
}
await AuthorAppService.DeleteAsync(author.Id);
- await GetAuthorsAsync();
+
+ // Go back to the previous page if the deleted author was the only one on the current page
+ if (AuthorsGrid.CurrentPage > 0 && AuthorsGrid.ServerItems.Count() == 1)
+ {
+ await InvokeAsync(() => AuthorsGrid.NavigateTo(Page.Previous));
+ }
+ else
+ {
+ await InvokeAsync(() => AuthorsGrid.ReloadServerData());
+ }
}
catch(Exception ex)
{
@@ -1475,11 +1489,11 @@ public partial class Authors
{
try
{
- await CreateFormRef.Validate();
+ await CreateFormRef.ValidateAsync();
if (CreateFormRef.IsValid)
{
await AuthorAppService.CreateAsync(NewAuthor);
- await GetAuthorsAsync();
+ await InvokeAsync(() => AuthorsGrid.ReloadServerData());
await InvokeAsync(() => CreateAuthorDialog.CloseAsync());
}
}
@@ -1493,11 +1507,11 @@ public partial class Authors
{
try
{
- await EditFormRef.Validate();
+ await EditFormRef.ValidateAsync();
if (EditFormRef.IsValid)
{
await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
- await GetAuthorsAsync();
+ await InvokeAsync(() => AuthorsGrid.ReloadServerData());
await InvokeAsync(() => EditAuthorDialog.CloseAsync());
}
}
@@ -1540,11 +1554,11 @@ public partial class AuthorDtoToUpdateAuthorDtoMapper : MapperBase NewEntity?.PublishDate;
+ set { if (NewEntity != null && value.HasValue) NewEntity.PublishDate = value.Value; }
+ }
+
+ private DateTime? EditingEntityPublishDate
+ {
+ get => EditingEntity?.PublishDate;
+ set { if (EditingEntity != null && value.HasValue) EditingEntity.PublishDate = value.Value; }
+ }
+
+ private MudDataGrid _booksDataGrid = default!;
+
+ protected override async Task OnCreatedEntityAsync()
+ {
+ await InvokeAsync(() => _booksDataGrid.ReloadServerData());
+ await CloseCreateDialogAsync();
+ Snackbar.Add(GetCreateMessage(), Severity.Success);
+ }
+
+ protected override async Task OnUpdatedEntityAsync()
+ {
+ await InvokeAsync(() => _booksDataGrid.ReloadServerData());
+ await CloseEditDialogAsync();
+ Snackbar.Add(GetUpdateMessage(), Severity.Success);
+ }
+
+ protected override async Task OnDeletedEntityAsync()
+ {
+ // Go back to the previous page if the deleted book was the only one on the current page
+ if (_booksDataGrid.CurrentPage > 0 && _booksDataGrid.ServerItems.Count() == 1)
+ {
+ await InvokeAsync(() => _booksDataGrid.NavigateTo(Page.Previous));
+ }
+ else
+ {
+ await InvokeAsync(() => _booksDataGrid.ReloadServerData());
+ }
+
+ Snackbar.Add(GetDeleteMessage(), Severity.Success);
+ }
+
//GET AUTHORS ON INITIALIZATION
protected override async Task OnInitializedAsync()
{
@@ -1279,7 +1330,7 @@ Finally, add the following field definition into the *Create* modal/dialog, as t
````xml
@L["Author"]
-