Demo used in the Exploring the New Fluent UI Blazor Library session at .NET Conf 2024
This project demonstrates how to use the Fluent UI Blazor Library.
note: This code is for step-by-step learning only. It does not respect development rules and/or best practices. This project presents only a small part of the possibilities offered by Fluent UI Blazor library. Some shortcuts have been applied to improve understanding of the project/code.
-
Install the latest version of the FluentUI Blazor Template.
dotnet new install Microsoft.FluentUI.AspNetCore.Templates
-
Open Visual Studio 2022 and create a new Fluent Blazor Web App.
This template contains exactly the same pages and functions as the default Blazor template. A Home page, a Counter page and a Weather page containing a data grid. All these screens have been graphically adapted using these FluentUI Blazor components.
This template automatically includes the following NuGet packages:
We're going to adapt the Weather data grid using a Progress Ring, customising a column with icons and adding a few filtering and data sorting functions.
-
Replace the 'Loading...' label with this component to display a Progress Ring during the loading phase.
<FluentProgressRing>Loading...</FluentProgressRing>
-
Add a TemplateColumn including icons.
<FluentDataGrid Id="weathergrid" Items="@forecasts" GridTemplateColumns="1fr 1fr 1fr 1fr 2fr" TGridItem="WeatherForecast"> <!-- ^^^ --> <PropertyColumn Title="Date" Property="@(c => c!.Date)" Align="Align.Start"/> <PropertyColumn Title="Temp. (C)" Property="@(c => c!.TemperatureC)" Align="Align.Center"/> <PropertyColumn Title="Temp. (F)" Property="@(c => c!.TemperatureF)" Align="Align.Center"/> <!-- ***************************************** --> <TemplateColumn Title="Icon"> @if (context.TemperatureC > 10) { <FluentIcon Value="@(new Icons.Regular.Size24.WeatherHailNight())" Color="@Color.Neutral" /> } else { <FluentIcon Value="@(new Icons.Regular.Size24.TimeAndWeather())" Color="@Color.Neutral" /> } </TemplateColumn> <!-- ***************************************** --> <PropertyColumn Title="Summary" Property="@(c => c!.Summary)" Align="Align.End"/> </FluentDataGrid>
Update the GridTemplateColumns attribute (adding a
1fr
) to define the width of this new column.This attribute respect the syntax used by the CSS grid-template-column attribute.
GridTemplateColumns="1fr 1fr 1fr 1fr 2fr"
-
Allow resizing and sorting behaviors
As descrive on the page: This page is rendered in SSR mode, so the
FluentDataGrid
component does not offer any interactivity (like sorting)To solve that, add this attribute in the top of the page.
@rendermode RenderMode.InteractiveServer
You can find more details on Render Modes on this Blog page.
Next, add the :
- The
ResizableColumns="true"
attribute to the FluentDataGrid element. - The
Sortable="true"
to the Temp. (C) column.
- The
-
Add a new Register.razor page with this content.
@page "/register" @rendermode RenderMode.InteractiveServer <PageTitle>New user</PageTitle> <FluentLabel Typo="Typography.PageTitle">Register</FluentLabel> <FluentLabel Style="margin-bottom: 24px;"> Let's get you all set up so you can verify your personal account and begin setting up your profile. </FluentLabel> <EditForm Model="@Data" OnValidSubmit="ValidHandlerAsync"> <FluentTextField Label="First name:" Placeholder="Enter your first name" Required @bind-Value="@Data.FistName" /> <FluentTextField Label="Last name:" Placeholder="Enter your last name" @bind-Value="@Data.LastName" /> <FluentDatePicker Label="Birth date:" @bind-Value="@Data.BirthDate" /> <FluentTextField Label="EMail:" TextFieldType="TextFieldType.Email" Required Placeholder="Enter your email" @bind-Value="@Data.Email" /> <br /> <FluentCheckbox Label="I agree to all terms, conditions, privacy policy." @bind-Value="@AcceptToCreate" /> <div style="margin: 24px 0px;" /> <FluentButton Appearance="Appearance.Accent" Loading="@Loading" Disabled="@(!AcceptToCreate)" Type="ButtonType.Submit"> Create account </FluentButton> </EditForm> <style> form fluent-text-field { margin-bottom: 24px; } </style>
@code { private RegisterUser Data = new(); private bool AcceptToCreate = false; private bool Loading = false; private async Task ValidHandlerAsync() { Loading = true; // Simulate asynchronous loading await Task.Delay(1000); Loading = false; } public class RegisterUser { public string? LastName { get; set; } public string? FistName { get; set; } public DateTime? BirthDate { get; set; } public string? Email { get; set; } } }
-
Add a new item in the Left Navigation menu.
<FluentNavLink Icon="@(new Icons.Regular.Size20.PlugConnectedSettings())" Href="/Register">Register</FluentNavLink>
- Update the App.razor file, adding
@rendermode="RenderMode.InteractiveServer"
to define the entire application as using InteractiveServer mode.
<body>
<Routes @rendermode="RenderMode.InteractiveServer" />
<script src="_framework/blazor.web.js"></script>
</body>
-
At the end of the MainLayout.razor file, add this line. These Providers define where the HTML code will be generated. By placing it at the end of the file, these components will be rendered 'above' all the other elements of your web page.
<FluentToastProvider /> <FluentDialogProvider /> <FluentTooltipProvider /> <FluentMessageBarProvider /> <FluentMenuProvider />
-
In the Registry.razor page, inject this service and add the Dialog Message.
@inject IDialogService DialogService private async Task ValidHandlerAsync() { Loading = true; // Simulate asynchronous loading await Task.Delay(1000); // Confirmation await DialogService.ShowInfoAsync( message: "Your account has been successfully created", title: "New user"); Loading = false; }
-
Add this new AccountCreatedDialog.razor file , implementing
IDialogContentComponent
, to customize the Dialog content.@implements IDialogContentComponent @* Header *@ <FluentDialogHeader ShowDismiss="true"> <FluentStack VerticalAlignment="VerticalAlignment.Center"> <FluentIcon Value="@(new Icons.Regular.Size24.PersonAdd())" /> <FluentLabel Typo="Typography.PaneHeader"> @Dialog.Instance.Parameters.Title </FluentLabel> </FluentStack> </FluentDialogHeader> @* Footer *@ <FluentDialogFooter> <FluentButton Appearance="Appearance.Accent" OnClick="@SaveAsync"> Close </FluentButton> </FluentDialogFooter> @* Body *@ <FluentDialogBody> <FluentLabel Typo="Typography.Subject" MarginBlock="10px;"> Thanks for being awesome! </FluentLabel> <FluentLabel> Thank you for registering your new request. If your request is urgent, please use our telephone number to speak to one of our customer service representatives. You can also reach us via <a href="https://dotnet.microsoft.com" target="_blank">our documentation page</a>. </FluentLabel> </FluentDialogBody> @code { [CascadingParameter] public required FluentDialog Dialog { get; set; } private async Task SaveAsync() { await Dialog.CloseAsync(); } }
-
Replace the previous
DialogService.ShowInfoAsync
method by this newShowDialogAsync<AccountCreatedDialog>
, to call the customized dialog box.// await DialogService.ShowInfoAsync("Your account has been successfully created", // "New user"); await DialogService.ShowDialogAsync<AccountCreatedDialog>(new DialogParameters() { Title = "New user", });
-
In the Registry.razor page, inject this service and add the Toast Message.
@inject IToastService ToastService
ToastService.ShowSuccess("Your account has been successfully created");
-
Add this class Country.cs to have a list of all countries.
note: Copy the full code from this file.
using Microsoft.AspNetCore.Components; public record Country(string Code, string Name) { public string Flag => $"https://fluentui-blazor.net/_content/FluentUI.Demo.Shared/flags/{Code}.svg"; public MarkupString HtmlFlagAndName => (MarkupString) @$"<div style=""display: flex; gap: 10px;""> <img src=""{Flag}"" width=""24"" /> <div>{Name}</div> </div>"; public static IEnumerable<Country> All { get { // List generated using https://chat.openai.com/ return new Country[] { new Country("af", "Afghanistan"), new Country("al", "Albania"), new Country("dz", "Algeria"), new Country("as", "American Samoa"), new Country("ad", "Andorra"), ... // Copy All Countries from the GitHub source code (see the note above). new Country("ye", "Yemen"), new Country("zm", "Zambia"), new Country("zw", "Zimbabwe"), }; } } }
-
Update the Register.razor file to add the FluentAutocomplete component
<EditForm Model="@Data" OnValidSubmit="ValidHandlerAsync"> ... @* Country *@ <FluentAutocomplete TOption="Country" Label="Select a country" Width="250px" Placeholder="Select countries" OnOptionsSearch="@OnSearchAsync" MaximumSelectedOptions="1" OptionText="@(i => i.Name)" @bind-SelectedOptions="@Data.Countries"> <OptionTemplate Context="country"> @country.HtmlFlagAndName </OptionTemplate> <MaximumSelectedOptionsMessage> Please select only one country. </MaximumSelectedOptionsMessage> </FluentAutocomplete> ... </EditForm>
@code { ... // Add this new method private async Task OnSearchAsync(OptionsSearchEventArgs<Country> e) { e.Items = Country.All.Where(i => i.Name.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase)) .OrderBy(i => i.Name); await Task.CompletedTask; } public class RegisterUser { public string? LastName { get; set; } public string? FistName { get; set; } public DateTime? BirthDate { get; set; } public string? Email { get; set; } // Add this new line public IEnumerable<Country> Countries { get; set; } = Array.Empty<Country>(); } }
You can replace the NavMenu with an AppBar to conform more closely to certain applications.
-
In the NavMenu.razor file, delete the
NavMenu
elements and use this code.<FluentAppBar Style="height: 300px;"> <FluentAppBarItem Href="/" Text="Home" Match="NavLinkMatch.All" IconRest="@(new Icons.Regular.Size20.Home())" IconActive="@(new Icons.Filled.Size20.Home())"/> <FluentAppBarItem Href="counter" Text="Counter" IconRest="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconActive="@(new Icons.Filled.Size20.NumberSymbolSquare())" /> <FluentAppBarItem Href="weather" Text="Weather" IconRest="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconActive="@(new Icons.Filled.Size20.WeatherPartlyCloudyDay())" /> <FluentAppBarItem Href="register" Text="Register" IconRest="@(new Icons.Regular.Size20.PlugConnectedSettings())" IconActive="@(new Icons.Filled.Size20.PlugConnectedSettings())" /> </FluentAppBar>
You may need to adapt the reduced menu for mobile devices.
-
in the MainLayout.razor file, add a button in the page Header to switch between Dark and Light mode.
<FluentHeader> DemoFluentUI <FluentSpacer /> <FluentButton Appearance="Appearance.Accent" IconStart="@(new Icons.Regular.Size20.DarkTheme())" OnClick="@SwitchTheme" Title="Theme" /> </FluentHeader>
-
Add the
FluentDesignTheme
component and the methodSwitchTheme
called by the button.<FluentDesignTheme Mode="@ThemeMode" OfficeColor="OfficeColor.Word" /> @code { private DesignThemeModes ThemeMode = DesignThemeModes.System; private void SwitchTheme() { ThemeMode = ThemeMode != DesignThemeModes.Dark ? DesignThemeModes.Dark: DesignThemeModes.Light; } }