MudBlazor Theme in ABP Blazor WebAssembly PART 4

Introduction

In this part, I'll show how to create CRUD pages with MudBlazor components.

11. Make Sure You Have Completed Previous Parts

You must complete the steps in Part 1, Part 2, and Part 3 to continue.

12. Create CRUD Pages

Complete the Web Application Development Tutorial in ABP documentation:

13. Update Authors CRUD Page

Add @using MudBlazor to Acme.BookStore.Blazor/_Imports.razor

Update Authors.razor with the following content:

@page "/authors"
@using Acme.BookStore.Authors
@using Acme.BookStore.Localization
@using Volo.Abp.AspNetCore.Components.Web
@inherits BookStoreComponentBase
@inject IAuthorAppService AuthorAppService
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH

<MudDataGrid T="AuthorDto"
             @ref="_authorList"
             Elevation="8"
             Hideable="true"
             Striped="true"
             ServerData="LoadServerData">
<ToolBarContent>
    <MudText Typo="Typo.h5">@L["Authors"]</MudText>
    <MudSpacer />
    <MudButton Color="MudBlazor.Color.Primary"
               Variant="Variant.Outlined"
               Disabled="!CanCreateAuthor"
               OnClick="OpenCreateAuthorModal">
        @L["NewAuthor"]
    </MudButton>
</ToolBarContent>
<Columns>
    <MudBlazor.Column T="AuthorDto"
                      Field="@nameof(AuthorDto.Id)"
                      Hideable="false"
                      Title="@L["Actions"]">
        <CellTemplate>
            @if (CanEditAuthor)
            {
                <MudIconButton Icon="fas fa-edit" 
                               OnClick="@((e) => {OpenEditAuthorModal(context.Item);})"
                               Size="MudBlazor.Size.Small" />
            }
            @if (CanDeleteAuthor)
            {   
                <MudIconButton Icon="fas fa-trash" 
                               OnClick="@(async (e) => {await DeleteAuthorAsync(context.Item);})"
                               Size="MudBlazor.Size.Small" />
            }
        </CellTemplate>
    </MudBlazor.Column>
    <MudBlazor.Column T="AuthorDto"
                      Field="@nameof(AuthorDto.Name)"
                      Hideable="false"
                      Title="@L["Name"]" />
    <MudBlazor.Column T="AuthorDto"
                      Field="@nameof(AuthorDto.BirthDate)"
                      Title="@L["BirthDate"]">
        <CellTemplate>
            @context.Item.BirthDate?.ToShortDateString()
        </CellTemplate>
    </MudBlazor.Column>
</Columns>
</MudDataGrid>

<MudDialog @bind-IsVisible="_createAuthorDialogVisible">
    <TitleContent>
        <MudText Typo="Typo.h6">@L["NewAuthor"]</MudText>
    </TitleContent>
    <DialogContent>
        <MudForm Model="@NewAuthor"
                 @ref="_createForm">
            <MudTextField @bind-Value="@NewAuthor.Name" 
                          Label=@L["Name"]
                          For=@(() => NewAuthor.Name) />
            <br />
            <MudDatePicker @bind-Date="@NewAuthor.BirthDate" 
                           Editable="true"
                           Mask="@(new DateMask("dd.MM.yyyy"))" 
                           DateFormat="dd.MM.yyyy"
                           Label=@L["BirthDate"] />
            <br />
            <MudTextField @bind-Value="@NewAuthor.ShortBio" 
                          Label=@L["ShortBio"]
                          Lines="5"
                          For=@(() => NewAuthor.ShortBio) />
        </MudForm>
    </DialogContent>
    <DialogActions>
        <MudButton Color="MudBlazor.Color.Secondary" 
                   OnClick="CloseCreateAuthorModal">
            @L["Cancel"]
        </MudButton>
        <MudButton Variant="Variant.Filled" 
                   Color="MudBlazor.Color.Primary" 
                   OnClick="CreateAuthorAsync">
            @L["Save"]
        </MudButton>
    </DialogActions>
</MudDialog>

<MudDialog @bind-IsVisible="_editAuthorDialogVisible">
    <TitleContent>
        <MudText Typo="Typo.h6">@EditingAuthor.Name</MudText>
    </TitleContent>
    <DialogContent>
        <MudForm Model="@EditingAuthor"
                 @ref="_editForm">
            <MudTextField @bind-Value="@EditingAuthor.Name" 
                          Label=@L["Name"]
                          For=@(() => EditingAuthor.Name) />
            <br />
            <MudDatePicker @bind-Date="@EditingAuthor.BirthDate" 
                           Editable="true"
                           Mask="@(new DateMask("dd.MM.yyyy"))" 
                           DateFormat="dd.MM.yyyy"
                           Label=@L["BirthDate"] />
            <br />
            <MudTextField @bind-Value="@EditingAuthor.ShortBio" 
                          Label=@L["ShortBio"]
                          Lines="5"
                          For=@(() => EditingAuthor.ShortBio) />
        </MudForm>
    </DialogContent>
    <DialogActions>
        <MudButton Color="MudBlazor.Color.Secondary" 
                   OnClick="CloseEditAuthorModal">
            @L["Cancel"]
        </MudButton>
        <MudButton Variant="Variant.Filled" 
                   Color="MudBlazor.Color.Primary" 
                   OnClick="UpdateAuthorAsync">
            @L["Save"]
        </MudButton>
    </DialogActions>
</MudDialog>

Then, update Authors.razor.cs code behing file with the following content:

using System;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Acme.BookStore.Permissions;
using Microsoft.AspNetCore.Authorization;
using MudBlazor;

namespace Acme.BookStore.Blazor.Pages
{
    public partial class Authors
    {
        private async Task<GridData<AuthorDto>> LoadServerData(GridState<AuthorDto> state)
        {
            var input = new GetAuthorListDto 
            { 
                Filter = "", 
                MaxResultCount = state.PageSize, 
                SkipCount = state.Page * state.PageSize
            };
            var result = await AuthorAppService.GetListAsync(input);
            return new()
            {
                Items = result.Items,
                TotalItems = (int)result.TotalCount
            };
        }

        private bool CanCreateAuthor { get; set; }
        private bool CanEditAuthor { get; set; }
        private bool CanDeleteAuthor { get; set; }

        private MudDataGrid<AuthorDto> _authorList;

        private CreateAuthorDto NewAuthor { get; set; }

        private bool _createAuthorDialogVisible;
        private bool _editAuthorDialogVisible;

        private MudForm _createForm;
        private MudForm _editForm;

        private Guid EditingAuthorId { get; set; }
        private UpdateAuthorDto EditingAuthor { get; set; }

        public Authors()
        {
            NewAuthor = new CreateAuthorDto();
            EditingAuthor = new UpdateAuthorDto();
        }

        protected override async Task OnInitializedAsync()
        {
            await SetPermissionsAsync();
        }

        private async Task SetPermissionsAsync()
        {
            CanCreateAuthor = await AuthorizationService
                .IsGrantedAsync(BookStorePermissions.Authors.Create);

            CanEditAuthor = await AuthorizationService
                .IsGrantedAsync(BookStorePermissions.Authors.Edit);

            CanDeleteAuthor = await AuthorizationService
                .IsGrantedAsync(BookStorePermissions.Authors.Delete);
        }

        private void OpenCreateAuthorModal()
        {
            NewAuthor = new CreateAuthorDto();
            _createAuthorDialogVisible = true;
        }

        private void CloseCreateAuthorModal()
        {
            _createAuthorDialogVisible = false;
        }

        private void OpenEditAuthorModal(AuthorDto author)
        {
            EditingAuthorId = author.Id;
            EditingAuthor = ObjectMapper.Map<AuthorDto, UpdateAuthorDto>(author);
            _editAuthorDialogVisible = true;
        }

        private async Task DeleteAuthorAsync(AuthorDto author)
        {
            var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name];
            if (!await Message.Confirm(confirmMessage))
            {
                return;
            }

            await AuthorAppService.DeleteAsync(author.Id);
            await _authorList.ReloadServerData();
        }

        private void CloseEditAuthorModal()
        {
            _editAuthorDialogVisible = false;
        }

        private async Task CreateAuthorAsync()
        {
            if (_createForm.IsValid)
            {
                await AuthorAppService.CreateAsync(NewAuthor);
                _createAuthorDialogVisible = false;
                await _authorList.ReloadServerData();
            }
        }

        private async Task UpdateAuthorAsync()
        {
            if (_editForm.IsValid)
            {
                await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
                _editAuthorDialogVisible = false;
                await _authorList.ReloadServerData();
            }
        }
    }
}

Change type of BirthDate property in Author, AuthorDto, CreateAuthorDto, UpdateAuthorDto from DateTime to DateTime?

Now, the Authors page should look like this:

image

14. Define MudCrudPageBase Class

Create MudCrudPageBase.cs file in Volo.Abp.AspNetCore.Components.Web.BasicTheme/Components and paste the following code:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using MudBlazor;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization;
using Volo.Abp.BlazoriseUI.Components;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns;

namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme.Components;

public abstract class MudCrudPageBase<
        TAppService,
        TEntityDto,
        TKey>
    : MudCrudPageBase<
        TAppService,
        TEntityDto,
        TKey,
        PagedAndSortedResultRequestDto>
    where TAppService : ICrudAppService<
        TEntityDto,
        TKey>
    where TEntityDto : class, IEntityDto<TKey>, new()
{
}

public abstract class MudCrudPageBase<
        TAppService,
        TEntityDto,
        TKey,
        TGetListInput>
    : MudCrudPageBase<
        TAppService,
        TEntityDto,
        TKey,
        TGetListInput,
        TEntityDto>
    where TAppService : ICrudAppService<
        TEntityDto,
        TKey,
        TGetListInput>
    where TEntityDto : class, IEntityDto<TKey>, new()
    where TGetListInput : new()
{
}

public abstract class MudCrudPageBase<
        TAppService,
        TEntityDto,
        TKey,
        TGetListInput,
        TCreateInput>
    : MudCrudPageBase<
        TAppService,
        TEntityDto,
        TKey,
        TGetListInput,
        TCreateInput,
        TCreateInput>
    where TAppService : ICrudAppService<
        TEntityDto,
        TKey,
        TGetListInput,
        TCreateInput>
    where TEntityDto : IEntityDto<TKey>
    where TCreateInput : class, new()
    where TGetListInput : new()
{
}

public abstract class MudCrudPageBase<
        TAppService,
        TEntityDto,
        TKey,
        TGetListInput,
        TCreateInput,
        TUpdateInput>
    : MudCrudPageBase<
        TAppService,
        TEntityDto,
        TEntityDto,
        TKey,
        TGetListInput,
        TCreateInput,
        TUpdateInput>
    where TAppService : ICrudAppService<
        TEntityDto,
        TKey,
        TGetListInput,
        TCreateInput,
        TUpdateInput>
    where TEntityDto : IEntityDto<TKey>
    where TCreateInput : class, new()
    where TUpdateInput : class, new()
    where TGetListInput : new()
{
}

public abstract class MudCrudPageBase<
        TAppService,
        TGetOutputDto,
        TGetListOutputDto,
        TKey,
        TGetListInput,
        TCreateInput,
        TUpdateInput>
    : MudCrudPageBase<
        TAppService,
        TGetOutputDto,
        TGetListOutputDto,
        TKey,
        TGetListInput,
        TCreateInput,
        TUpdateInput,
        TGetListOutputDto,
        TCreateInput,
        TUpdateInput>
    where TAppService : ICrudAppService<
        TGetOutputDto,
        TGetListOutputDto,
        TKey,
        TGetListInput,
        TCreateInput,
        TUpdateInput>
    where TGetOutputDto : IEntityDto<TKey>
    where TGetListOutputDto : IEntityDto<TKey>
    where TCreateInput : class, new()
    where TUpdateInput : class, new()
    where TGetListInput : new()
{
}

public abstract class MudCrudPageBase<
        TAppService,
        TGetOutputDto,
        TGetListOutputDto,
        TKey,
        TGetListInput,
        TCreateInput,
        TUpdateInput,
        TListViewModel,
        TCreateViewModel,
        TUpdateViewModel>
    : AbpComponentBase
    where TAppService : ICrudAppService<
        TGetOutputDto,
        TGetListOutputDto,
        TKey,
        TGetListInput,
        TCreateInput,
        TUpdateInput>
    where TGetOutputDto : IEntityDto<TKey>
    where TGetListOutputDto : IEntityDto<TKey>
    where TCreateInput : class
    where TUpdateInput : class
    where TGetListInput : new()
    where TListViewModel : IEntityDto<TKey>
    where TCreateViewModel : class, new()
    where TUpdateViewModel : class, new()
{
    [Inject] protected TAppService AppService { get; set; }
    [Inject] protected IStringLocalizer<AbpUiResource> UiLocalizer { get; set; }

    protected virtual int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount;

    protected TGetListInput GetListInput = new();
    protected TCreateViewModel NewEntity;
    protected TKey EditingEntityId;
    protected TUpdateViewModel EditingEntity;

    protected MudDataGrid<TListViewModel> DataGrid;

    protected bool CreateDialogVisible;
    protected bool EditDialogVisible;
    
    protected MudForm CreateForm;
    protected MudForm EditForm;

    protected DataGridEntityActionsColumn<TListViewModel> EntityActionsColumn;
    protected EntityActionDictionary EntityActions { get; set; }
    protected TableColumnDictionary TableColumns { get; set; }

    protected string CreatePolicyName { get; set; }
    protected string UpdatePolicyName { get; set; }
    protected string DeletePolicyName { get; set; }

    public bool HasCreatePermission { get; set; }
    public bool HasUpdatePermission { get; set; }
    public bool HasDeletePermission { get; set; }

    protected DialogOptions DialogOptions 
    {
        get => new()
        { 
            CloseButton = true,
            CloseOnEscapeKey = true,
            DisableBackdropClick = true
        };
    }

    protected MudCrudPageBase()
    {
        NewEntity = new TCreateViewModel();
        EditingEntity = new TUpdateViewModel();
        TableColumns = new TableColumnDictionary();
        EntityActions = new EntityActionDictionary();
    }

    protected async override Task OnInitializedAsync()
    {
        await SetPermissionsAsync();
        await SetEntityActionsAsync();
        await SetTableColumnsAsync();
        await InvokeAsync(StateHasChanged);
    }

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await base.OnAfterRenderAsync(firstRender);
            await SetToolbarItemsAsync();
            await SetBreadcrumbItemsAsync();
        }
    }

    protected virtual async Task SetPermissionsAsync()
    {
        if (CreatePolicyName != null)
        {
            HasCreatePermission = await AuthorizationService.IsGrantedAsync(CreatePolicyName);
        }

        if (UpdatePolicyName != null)
        {
            HasUpdatePermission = await AuthorizationService.IsGrantedAsync(UpdatePolicyName);
        }

        if (DeletePolicyName != null)
        {
            HasDeletePermission = await AuthorizationService.IsGrantedAsync(DeletePolicyName);
        }
    }

    private IReadOnlyList<TListViewModel> MapToListViewModel(IReadOnlyList<TGetListOutputDto> dtos)
    {
        if (typeof(TGetListOutputDto) == typeof(TListViewModel))
        {
            return dtos.As<IReadOnlyList<TListViewModel>>();
        }

        return ObjectMapper.Map<IReadOnlyList<TGetListOutputDto>, List<TListViewModel>>(dtos);
    }

    protected async Task<GridData<TListViewModel>> LoadServerData(GridState<TListViewModel> state)
    {
        if (GetListInput is PagedAndSortedResultRequestDto pageResultRequest)
        {
            pageResultRequest.MaxResultCount = state.PageSize;
            pageResultRequest.SkipCount = state.Page * state.PageSize;
        }
        var result = await AppService.GetListAsync(GetListInput);
        return new()
        {
            Items = MapToListViewModel(result.Items),
            TotalItems = (int)result.TotalCount
        };
    }

    protected virtual async Task OpenCreateModalAsync()
    {
        try
        {
            await CheckCreatePolicyAsync();

            NewEntity = new TCreateViewModel();

            // Mapper will not notify Blazor that binded values are changed
            // so we need to notify it manually by calling StateHasChanged
            await InvokeAsync(() =>
            {
                StateHasChanged();
                CreateDialogVisible = true;
            });
        }
        catch (Exception ex)
        {
            await HandleErrorAsync(ex);
        }
    }

    protected virtual Task CloseCreateModalAsync()
    {
        NewEntity = new TCreateViewModel();
        return InvokeAsync(() => { CreateDialogVisible = false; });
    }

    protected virtual async Task OpenEditModalAsync(TListViewModel entity)
    {
        try
        {
            await CheckUpdatePolicyAsync();

            var entityDto = await AppService.GetAsync(entity.Id);

            EditingEntityId = entity.Id;
            EditingEntity = MapToEditingEntity(entityDto);

            await InvokeAsync(() =>
            {
                StateHasChanged();
                EditDialogVisible = true;
            });
        }
        catch (Exception ex)
        {
            await HandleErrorAsync(ex);
        }
    }

    protected virtual TUpdateViewModel MapToEditingEntity(TGetOutputDto entityDto)
    {
        return ObjectMapper.Map<TGetOutputDto, TUpdateViewModel>(entityDto);
    }

    protected virtual TCreateInput MapToCreateInput(TCreateViewModel createViewModel)
    {
        if (typeof(TCreateInput) == typeof(TCreateViewModel))
        {
            return createViewModel.As<TCreateInput>();
        }

        return ObjectMapper.Map<TCreateViewModel, TCreateInput>(createViewModel);
    }

    protected virtual TUpdateInput MapToUpdateInput(TUpdateViewModel updateViewModel)
    {
        if (typeof(TUpdateInput) == typeof(TUpdateViewModel))
        {
            return updateViewModel.As<TUpdateInput>();
        }

        return ObjectMapper.Map<TUpdateViewModel, TUpdateInput>(updateViewModel);
    }

    protected virtual Task CloseEditModalAsync()
    {
        InvokeAsync(() => { EditDialogVisible = false; });
        return Task.CompletedTask;
    }

    protected virtual async Task CreateEntityAsync()
    {
        try
        {
            var validate = true;
            if (CreateForm is not null)
            {
                validate = CreateForm.IsValid;
            }
            if (validate)
            {
                await OnCreatingEntityAsync();

                await CheckCreatePolicyAsync();
                var createInput = MapToCreateInput(NewEntity);
                await AppService.CreateAsync(createInput);

                await OnCreatedEntityAsync();
            }
        }
        catch (Exception ex)
        {
            await HandleErrorAsync(ex);
        }
    }

    protected virtual Task OnCreatingEntityAsync()
    {
        return Task.CompletedTask;
    }

    protected virtual async Task OnCreatedEntityAsync()
    {
        NewEntity = new TCreateViewModel();
        await DataGrid.ReloadServerData();

        await InvokeAsync(() => { CreateDialogVisible = false; });
    }

    protected virtual async Task UpdateEntityAsync()
    {
        try
        {
            var validate = true;
            if (EditForm is not null)
            {
                validate = EditForm.IsValid;
            }
            if (validate)
            {
                await OnUpdatingEntityAsync();

                await CheckUpdatePolicyAsync();
                var updateInput = MapToUpdateInput(EditingEntity);
                await AppService.UpdateAsync(EditingEntityId, updateInput);

                await OnUpdatedEntityAsync();
            }
        }
        catch (Exception ex)
        {
            await HandleErrorAsync(ex);
        }
    }

    protected virtual Task OnUpdatingEntityAsync()
    {
        return Task.CompletedTask;
    }

    protected virtual async Task OnUpdatedEntityAsync()
    {
        await DataGrid.ReloadServerData();

        await InvokeAsync(() => { EditDialogVisible = false; });
    }

    protected virtual async Task DeleteEntityAsync(TListViewModel entity)
    {
        try
        {
            await CheckDeletePolicyAsync();
            await OnDeletingEntityAsync();
            await AppService.DeleteAsync(entity.Id);
            await OnDeletedEntityAsync();
        }
        catch (Exception ex)
        {
            await HandleErrorAsync(ex);
        }
    }

    protected virtual Task OnDeletingEntityAsync()
    {
        return Task.CompletedTask;
    }

    protected virtual async Task OnDeletedEntityAsync()
    {
        await DataGrid.ReloadServerData();
        await InvokeAsync(StateHasChanged);
        await Notify.Success(L["SuccessfullyDeleted"]);
    }

    protected virtual string GetDeleteConfirmationMessage(TListViewModel entity)
    {
        return UiLocalizer["ItemWillBeDeletedMessage"];
    }

    protected virtual async Task CheckCreatePolicyAsync()
    {
        await CheckPolicyAsync(CreatePolicyName);
    }

    protected virtual async Task CheckUpdatePolicyAsync()
    {
        await CheckPolicyAsync(UpdatePolicyName);
    }

    protected virtual async Task CheckDeletePolicyAsync()
    {
        await CheckPolicyAsync(DeletePolicyName);
    }

    /// <summary>
    /// Calls IAuthorizationService.CheckAsync for the given <paramref name="policyName"/>.
    /// Throws <see cref="AbpAuthorizationException"/> if given policy was not granted for the current user.
    ///
    /// Does nothing if <paramref name="policyName"/> is null or empty.
    /// </summary>
    /// <param name="policyName">A policy name to check</param>
    protected virtual async Task CheckPolicyAsync([CanBeNull] string policyName)
    {
        if (string.IsNullOrEmpty(policyName))
        {
            return;
        }

        await AuthorizationService.CheckAsync(policyName);
    }

    protected virtual ValueTask SetBreadcrumbItemsAsync()
    {
        return ValueTask.CompletedTask;
    }

    protected virtual ValueTask SetEntityActionsAsync()
    {
        return ValueTask.CompletedTask;
    }

    protected virtual ValueTask SetTableColumnsAsync()
    {
        return ValueTask.CompletedTask;
    }

    protected virtual ValueTask SetToolbarItemsAsync()
    {
        return ValueTask.CompletedTask;
    }
}

This class will be used to replace AbpCrudPageBase.

15. Update Books CRUD Page

Change type of PublishDate property in Book, BookDto, CreateUpdateBookDto from DateTime to DateTime?

Update Books.razor with the following content:

@page "/books"
@attribute [Authorize(BookStorePermissions.Books.Default)]
@using Acme.BookStore.Permissions
@using Microsoft.AspNetCore.Authorization
@using Volo.Abp.Application.Dtos
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Components
@inherits MudCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH

<MudDataGrid T="BookDto"
             @ref="DataGrid"
             Striped="true"
             Hideable="true"
             Elevation="8"
             ServerData="LoadServerData">
<ToolBarContent>
    <MudText Typo="Typo.h5">@L["Books"]</MudText>
    <MudSpacer />
    <MudButton Color="MudBlazor.Color.Primary"
               Variant="Variant.Outlined"
               Disabled="!HasCreatePermission"
               OnClick="OpenCreateModalAsync">
        @L["NewBook"]
    </MudButton>
</ToolBarContent>
<Columns>
    <MudBlazor.Column T="BookDto"
                      Field="@nameof(BookDto.Id)"
                      Hideable="false"
                      Title="@L["Actions"]">
        <CellTemplate>
            @if (HasUpdatePermission)
            {
                <MudIconButton Icon="fas fa-edit" 
                               OnClick="@(async (_) => { await OpenEditModalAsync(context.Item); })"
                               Size="MudBlazor.Size.Small" />
            }
            @if (HasDeletePermission)
            {   
                <MudIconButton Icon="fas fa-trash" 
                               OnClick="@(async (_) => { await DeleteEntityAsync(context.Item);} )"
                               Size="MudBlazor.Size.Small" />
            }
        </CellTemplate>
    </MudBlazor.Column>
    <MudBlazor.Column T="BookDto"
                      Field="@nameof(BookDto.Name)"
                      Hideable="false"
                      Title=@L["Name"] />
    <MudBlazor.Column T="BookDto"
                      Field="@nameof(BookDto.Type)"
                      Title=@L["Type"]>
        <CellTemplate>
            @L[$"Enum:BookType:{(int)context.Item.Type}"]
        </CellTemplate>
    </MudBlazor.Column>
    <MudBlazor.Column T="BookDto"
                      Field="@nameof(BookDto.AuthorName)"
                      Title=@L["AuthorName"] />
    <MudBlazor.Column T="BookDto"
                      Field="@nameof(BookDto.PublishDate)"
                      Title=@L["PublishDate"]>
        <CellTemplate>
            @if (@context.Item.PublishDate.HasValue)
                @context.Item.PublishDate.Value.ToShortDateString()
        </CellTemplate>
    </MudBlazor.Column>
    <MudBlazor.Column T="BookDto"
                      Field="@nameof(BookDto.Price)"
                      Title=@L["Price"] />
</Columns>
</MudDataGrid>

<MudDialog @bind-IsVisible="CreateDialogVisible" 
           Options="DialogOptions">
    <TitleContent>
        <MudText Typo="Typo.h6">@L["NewBook"]</MudText>
    </TitleContent>
    <DialogContent>
        <MudForm Model="@NewEntity"
                 @ref="CreateForm">
            <MudSelect T="Guid" 
                       @bind-Value="NewEntity.AuthorId"
                       Label=@L["Author"]>
                <MudSelectItem Value="@Guid.Empty">
                    @L["PickAnAuthor"]
                </MudSelectItem>
                @foreach (var author in authorList)
                {
                    <MudSelectItem Value="@author.Id">
                        @author.Name
                    </MudSelectItem>
                }
            </MudSelect>
            <br />
            <MudTextField @bind-Value="@NewEntity.Name" 
                          Label=@L["Name"]
                          For=@(() => NewEntity.Name) />
            <br />
            <MudSelect T="BookType" 
                       @bind-Value="NewEntity.Type"
                       Label=@L["Type"]>
                @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType)))
                {
                    <MudSelectItem Value="@bookTypeValue">
                        @L[$"Enum:BookType:{(int)bookTypeValue}"]
                    </MudSelectItem>
                }
            </MudSelect>
            <br />
            <MudDatePicker @bind-Date="@NewEntity.PublishDate" 
                           Editable="true"
                           Mask="@(new DateMask("dd.MM.yyyy"))" 
                           DateFormat="dd.MM.yyyy"
                           Label=@L["PublishDate"] />
            <br />
            <MudNumericField @bind-Value="@NewEntity.Price" 
                             Label=@L["Price"]
                             Min=0f
                             HideSpinButtons="true"
                             For=@(() => NewEntity.Price) />
        </MudForm>
    </DialogContent>
    <DialogActions>
        <MudButton Color="MudBlazor.Color.Secondary" 
                   OnClick="CloseCreateModalAsync">
            @L["Cancel"]
        </MudButton>
        <MudButton Variant="Variant.Filled" 
                   Color="MudBlazor.Color.Primary" 
                   OnClick="CreateEntityAsync">
            @L["Save"]
        </MudButton>
    </DialogActions>
</MudDialog>

<MudDialog @bind-IsVisible="EditDialogVisible"
           Options="DialogOptions">
    <TitleContent>
        <MudText Typo="Typo.h6">@EditingEntity.Name</MudText>
    </TitleContent>
    <DialogContent>
        <MudForm Model="@EditingEntity"
                 @ref="EditForm">
            <MudSelect T="Guid" 
                       @bind-Value="EditingEntity.AuthorId"
                       Label=@L["Author"]>
                <MudSelectItem Value="@Guid.Empty">
                    @L["PickAnAuthor"]
                </MudSelectItem>
                @foreach (var author in authorList)
                {
                    <MudSelectItem Value="@author.Id">
                        @author.Name
                    </MudSelectItem>
                }
            </MudSelect>
            <br />
            <MudTextField @bind-Value="@EditingEntity.Name" 
                          Label=@L["Name"]
                          For=@(() => EditingEntity.Name) />
            <br />
            <MudSelect T="BookType" 
                       @bind-Value="EditingEntity.Type"
                       Label=@L["Type"]>
                @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType)))
                {
                    <MudSelectItem Value="@bookTypeValue">
                        @L[$"Enum:BookType:{(int)bookTypeValue}"]
                    </MudSelectItem>
                }
            </MudSelect>
            <br />
            <MudDatePicker @bind-Date="@EditingEntity.PublishDate" 
                           Editable="true"
                           Mask="@(new DateMask("dd.MM.yyyy"))" 
                           DateFormat="dd.MM.yyyy"
                           Label=@L["PublishDate"] />
            <br />
            <MudNumericField @bind-Value="@EditingEntity.Price" 
                             Label=@L["Price"]
                             Min=0f
                             HideSpinButtons="true"
                             For=@(() => EditingEntity.Price) />
        </MudForm>
    </DialogContent>
    <DialogActions>
        <MudButton Color="MudBlazor.Color.Secondary" 
                   OnClick="CloseEditModalAsync">
            @L["Cancel"]
        </MudButton>
        <MudButton Variant="Variant.Filled" 
                   Color="MudBlazor.Color.Primary" 
                   OnClick="UpdateEntityAsync">
            @L["Save"]
        </MudButton>
    </DialogActions>
</MudDialog>

@code
{
    //ADDED A NEW FIELD
    IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();

    public Books() // Constructor
    {
        LocalizationResource = typeof(BookStoreResource);

        CreatePolicyName = BookStorePermissions.Books.Create;
        UpdatePolicyName = BookStorePermissions.Books.Edit;
        DeletePolicyName = BookStorePermissions.Books.Delete;
    }

    //GET AUTHORS ON INITIALIZATION
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        authorList = (await AppService.GetAuthorLookupAsync()).Items;
    }
}

Next

MudBlazor Theme in ABP Blazor WebAssembly FINAL