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>
Comments
Bart Van Hoey 142 weeks ago
Thanks for this great article! I followed all the steps and it works like a charm! Looking forward to the upcoming articles!
xowadi1746 80 weeks ago
That works for me as well, thanks