MudBlazor Theme in ABP Blazor WebAssembly PART 1

Introduction

It is quite easy to customize default template and layout of an ABP Blazor application. This sample demonstrates how you can use MudBlazor layouts in your ABP Blazor WebAssembly applications. The source code is available on GitHub

What is MudBlazor?

MudBlazor is a Blazor component library trusted by thousands of users, from hobbyists to enterprises.

1. Create a new ABP Blazor WebAssembly application

Install or update the ABP CLI:

dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli

Create a new ABP Blazor WebAssembly application:

abp new Acme.BookStore -u blazor -d mongodb

2. Add packages to your solution

Copy the source code of Basic Theme to your solution:

abp add-package Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme --with-source-code --add-to-solution-file

Then, navigate to downloaded Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme project directory and run:

abp add-package Volo.Abp.AspNetCore.Components.Web.BasicTheme --with-source-code --add-to-solution-file

To add downloaded projects to source control, open .gitignore file and add this line:

!**/packages/*Theme*

3. Add MudBlazor

Navigate to downloaded Volo.Abp.AspNetCore.Components.Web.BasicTheme project directory and install MudBlazor:

dotnet add package MudBlazor

In Volo.Abp.AspNetCore.Components.Web.BasicTheme project, open AbpAspNetCoreComponentsWebBasicThemeModule.cs file, and replace its content with the following code:

using MudBlazor.Services;
using Volo.Abp.AspNetCore.Components.Web.Theming;
using Volo.Abp.Modularity;

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

[DependsOn(
    typeof(AbpAspNetCoreComponentsWebThemingModule)
)]
public class AbpAspNetCoreComponentsWebBasicThemeModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        base.ConfigureServices(context);
        context.Services.AddMudServices();
    }
}

And in Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme project, open BasicThemeBundleContributor.cs file, and replace its content with the following code:

using Volo.Abp.Bundling;

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

public class BasicThemeBundleContributor : IBundleContributor
{
    public void AddScripts(BundleContext context)
    {
        context.Add("_content/MudBlazor/MudBlazor.min.js");
    }

    public void AddStyles(BundleContext context)
    {
        context.Add("_content/Volo.Abp.AspNetCore.Components.Web.BasicTheme/libs/abp/css/theme.css");

        context.Add("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap");
        context.Add("_content/MudBlazor/MudBlazor.min.css");
    }
}

Add the following to your HTML head section of index.html file in Acme.BookStore.Blazor project:

<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />

In the same file but located in the end of it add the MudBlazor js file, it should be in the same location as the default blazor script:

<script src="_content/MudBlazor/MudBlazor.min.js"></script>

Add this line to Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/_Imports.razor and Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/_Imports.razor files:

@using MudBlazor

4. Customize Basic Theme

Open Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/ folder.

Replace Branding.razor file's content with the following code:

@using Volo.Abp.Ui.Branding
@inject IBrandingProvider BrandingProvider

<MudText Typo="Typo.h5" Class="ml-3">
    @BrandingProvider.AppName
</MudText>

Replace FirstLevelNavMenuItem.razor file's content with the following code:

@using Volo.Abp.UI.Navigation

@{
    var elementId = MenuItem.ElementId ?? "MenuItem_" + MenuItem.Name.Replace(".", "_");
    var cssClass = string.IsNullOrEmpty(MenuItem.CssClass) ? string.Empty : MenuItem.CssClass;
    var disabled = MenuItem.IsDisabled ? "disabled" : string.Empty;
    var url = MenuItem.Url == null ? "#" : MenuItem.Url.TrimStart('/', '~');
}

@if (MenuItem.IsLeaf)
{
    if (MenuItem.Url is not null)
    {
        <MudNavLink Icon="@MenuItem.Icon" Href="@url" Target="@MenuItem.Target" Match="NavLinkMatch.All">
            @MenuItem.DisplayName
        </MudNavLink>
    }
}
else
{
    <MudNavGroup Icon="@MenuItem.Icon" Title="@MenuItem.DisplayName">
        @foreach (var childMenuItem in MenuItem.Items.OrderBy(i => i.Order))
        {
            <SecondLevelNavMenuItem MenuItem="@childMenuItem"/>
        }
    </MudNavGroup>
}

Replace SecondLevelNavMenuItem.razor file's content with the following code:

@using Volo.Abp.UI.Navigation

@{
    var elementId = MenuItem.ElementId ?? "MenuItem_" + MenuItem.Name.Replace(".", "_");
    var cssClass = string.IsNullOrEmpty(MenuItem.CssClass) ? string.Empty : MenuItem.CssClass;
    var disabled = MenuItem.IsDisabled ? "disabled" : string.Empty;
    var url = MenuItem.Url == null ? "#" : MenuItem.Url.TrimStart('/', '~');
}

@if (MenuItem.IsLeaf)
{
    if (MenuItem.Url is not null)
    {
        <MudNavLink Icon="@MenuItem.Icon" Href="@url" Target="@MenuItem.Target">
            @MenuItem.DisplayName
        </MudNavLink>
    }
}
else
{
    <MudNavGroup Icon="@MenuItem.Icon" Title="@MenuItem.DisplayName">
        @foreach (var childMenuItem in MenuItem.Items.OrderBy(i => i.Order))
        {
            <SecondLevelNavMenuItem MenuItem="@childMenuItem"/>
        }
    </MudNavGroup>
}

Replace NavToolbar.razor file's content with the following code:

@foreach (var render in ToolbarItemRenders)
{
    @render
}

Replace MainLayout.razor file's content with the following code:

@inherits LayoutComponentBase

<MudThemeProvider />

<MudLayout>
    <MudAppBar Elevation="8">
        <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="MudBlazor.Color.Inherit" Edge="Edge.Start" 
                       OnClick="@((e) => DrawerToggle())" />
        <Branding />
        <MudSpacer />
        <NavToolbar />
    </MudAppBar>
    <MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="8">
        <NavMenu />
    </MudDrawer>
    <MudMainContent>
        <MudContainer MaxWidth="MaxWidth.False" Class="mt-4">
            <PageAlert />
            @Body
            <UiMessageAlert />
            <UiNotificationAlert />
            <UiPageProgress />
        </MudContainer>
    </MudMainContent>
</MudLayout>

@code 
{
    private bool _drawerOpen = true;

    private void DrawerToggle()
    {
        _drawerOpen = !_drawerOpen;
    }
}

You can use a different layout. See other main layout options.

Now open Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/ folder.

Replace LanguageSwitch.razor file's content with the following code:

@using Volo.Abp.Localization
@using System.Globalization
@using System.Collections.Immutable
@inject ILanguageProvider LanguageProvider
@inject IJSRuntime JsRuntime

@if (_otherLanguages is not null && _otherLanguages.Any())
{
    <MudMenu Color="MudBlazor.Color.Inherit" Direction="MudBlazor.Direction.Left" OffsetX="true" Dense="true">
        <ActivatorContent>
            <MudChip Color="MudBlazor.Color.Primary">
                @_currentLanguage.DisplayName
            </MudChip>
        </ActivatorContent>
        <ChildContent>
            @foreach (var language in _otherLanguages)
            {
                <MudMenuItem OnClick="@(async () => await ChangeLanguageAsync(language))">
                    @language.DisplayName
                </MudMenuItem>
            }
        </ChildContent>
    </MudMenu>
}

@code {
    private IReadOnlyList<LanguageInfo> _otherLanguages;
    private LanguageInfo _currentLanguage;

    protected override async Task OnInitializedAsync()
    {
        var selectedLanguageName = await JsRuntime.InvokeAsync<string>(
            "localStorage.getItem",
            "Abp.SelectedLanguage");

        _otherLanguages = await LanguageProvider.GetLanguagesAsync();

        if (!_otherLanguages.Any()) return;

        if (!selectedLanguageName.IsNullOrWhiteSpace())
            _currentLanguage = _otherLanguages.FirstOrDefault(l => l.UiCultureName == selectedLanguageName);

        _currentLanguage ??= _otherLanguages.FirstOrDefault(l => l.UiCultureName == CultureInfo.CurrentUICulture.Name);
        _currentLanguage ??= _otherLanguages.FirstOrDefault();

        _otherLanguages = _otherLanguages.Where(l => l != _currentLanguage).ToImmutableList();
    }

    private async Task ChangeLanguageAsync(LanguageInfo language)
    {
        await JsRuntime.InvokeVoidAsync(
            "localStorage.setItem",
            "Abp.SelectedLanguage", language.UiCultureName);

        await JsRuntime.InvokeVoidAsync("location.reload");
    }
}

Replace LoginDisplay.razor file's content with the following code:

@using Microsoft.Extensions.Localization
@using Volo.Abp.Users
@using Volo.Abp.MultiTenancy
@using global::Localization.Resources.AbpUi
@inherits AbpComponentBase
@inject ICurrentUser CurrentUser
@inject ICurrentTenant CurrentTenant
@inject IJSRuntime JsRuntime
@inject NavigationManager Navigation
@inject IStringLocalizer<AbpUiResource> UiLocalizer

<AuthorizeView>
    <Authorized>
        <MudMenu Color="MudBlazor.Color.Inherit" Direction="MudBlazor.Direction.Left" OffsetX="true" Dense="true">
            <ActivatorContent>
                <MudChip Color="MudBlazor.Color.Primary">
                    @CurrentUser.Name
                </MudChip>
            </ActivatorContent>
            <ChildContent>
                @if (Menu is not null && Menu.Items.Any())
                {
                    @foreach (var menuItem in Menu.Items)
                    {
                        <MudListItem OnClick="@(async () => await NavigateToAsync(menuItem.Url, menuItem.Target))">
                            @menuItem.DisplayName
                        </MudListItem>
                    }
                    <MudDivider />
                }
                <MudListItem Icon="@Icons.Material.Outlined.Login" OnClick="BeginSignOut">
                    @UiLocalizer["Logout"]
                </MudListItem>
            </ChildContent>
        </MudMenu>
    </Authorized>
    <NotAuthorized>
        <MudLink Color="MudBlazor.Color.Inherit" Href="authentication/login">
            <MudChip Color="MudBlazor.Color.Primary">
                @UiLocalizer["Login"]
            </MudChip>
        </MudLink>
    </NotAuthorized>
</AuthorizeView>

The Result

image

Next

MudBlazor Theme in ABP Blazor WebAssembly PART 2