Links
Components
Pages/MyPage.razor.cs
|
[Inject]
private IDialogService DialogService { get; set; }
private void OnDisplayDialog()
{
var dialogOptions = new DialogOptions() { CloseButton = true };
var parameters = new DialogParameters<MyDialog>
{
{ x => x.MyProperty, propertyValue }
};
DialogService.Show<MyDialog>("Title", parameters, dialogOptions);
|
Dialogs/MyDialog.razor
|
<MudDialog>
<DialogContent>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="Color.Success" Variant="Variant.Filled" OnClick="Submit">Ok</MudButton>
</DialogActions>
</MudDialog>
@code {
[Parameter]
MyDto MyProperty { get; set; }
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
void Submit() => MudDialog.Close(DialogResult.Ok(true));
void Cancel() => MudDialog.Cancel();
}
|
|
<MudIconButton Icon="@Icons.Material.Filled.DeleteForever"
Size="Size.Small"
Color="Color.Error"
aria-label="delete"
OnClick="@(() => OnDeleteAsync(@context.Id))"
Href="@($"items/{context.Id}")" />
|
|
<MudImage Src="images/MyImage.svg" Alt="MyImage" Width="20" />
|
|
<MudLink Href=""
Typo="Typo.h3"
Underline="Underline.None"
Color="Color.Default"
Class="purple-text text-lighten-3">Text</MudLink>
|
|
<MudMenu
Icon="@Icons.Material.Filled.MoreVert"
Size="Size.Small"
AnchorOrigin="Origin.CenterLeft"
TransformOrigin="Origin.CenterRight">
<MudMenuItem>
<MudIconButton
Icon="@Icons.Material.Filled.DeleteForever"
Size="Size.Small"
Color="Color.Error"
aria-label="delete"
OnClick="@(() => OnDeleteAsync(@context.Id))" />
</MudMenuItem>
</MudMenu>
|
Pages/MyPage.razor
|
<MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="OnDeleteAsync">Delete</MudButton>
<MudMessageBox @ref="deleteItemDialog" Title="Item deletion">
<MessageContent>
Are you sure you want to delete the selected item?
</MessageContent>
<YesButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.DeleteForever">Delete</MudButton>
</YesButton>
</MudMessageBox>
|
Pages/MyPage.razor.cs
|
private async Task OnDeleteAsync()
{
var dialogOptions = new DialogOptions
{
CloseButton = true,
DisableBackdropClick = true // modal
};
var result = await deleteItemDialog.Show(dialogOptions);
if (result != true)
{
return;
}
|
|
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true" />
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Value="@progressValue" />
|
|
private double progressValue; // between 0 and 100
|
Pages/MyPage.razor.cs
|
[Inject]
private ISnackbar snackbar { get; set; } = default!;
snackbar.Add("Success!!!", Severity.Success);
snackbar.Add($"Error!!!<br>Error message:<br>{operationStatus.ErrorMessage}", Severity.Error);
|
Program.cs
|
builder.Services.AddMudServices(config =>
{
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight;
config.SnackbarConfiguration.PreventDuplicates = true;
config.SnackbarConfiguration.HideTransitionDuration = 100;
});
|
|
<MudTable
@ref="table"
ServerData="@(new Func<TableState, Task<TableData<MyNamespace.ItemResponse>>>(ServerReloadAsync))"
Hover="true"
Loading="@isSearching"
LoadingProgressColor="Color.Info"
Class="flex-column">
<ToolBarContent>
<MudFab
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
Size="Size.Small"
Href="item/new" />
<MudTextField
T="string"
ValueChanged="@(s => OnSearch(s))"
Placeholder="Search"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Medium"
Class="mt-0 mx-16">
</MudTextField>
</ToolBarContent>
<HeaderContent>
<MudTh Style="text-align:right">Name</MudTh>
<MudTh></MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Name" Style="text-align:right">@context.Name</MudTd>
<MudTd>
<MudMenu
Icon="@Icons.Material.Filled.MoreVert"
Size="Size.Small"
AnchorOrigin="Origin.CenterLeft"
TransformOrigin="Origin.CenterRight">
<MudMenuItem>
<MudIconButton
Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
Color="Color.Primary"
aria-label="edit"
Href="@($"transaction/{context.Id}")" />
</MudMenuItem>
<MudMenuItem>
<MudIconButton
Icon="@Icons.Material.Filled.DeleteForever"
Size="Size.Small"
Color="Color.Error"
aria-label="delete"
OnClick="@(() => OnDeleteAsync(context.Id))" />
</MudMenuItem>
</MudMenu>
</MudTd>
</RowTemplate>
<NoRecordsContent>
<MudText>No matching items found</MudText>
</NoRecordsContent>
<LoadingContent>
<MudText>Loading...</MudText>
</LoadingContent>
<PagerContent>
<MudTablePager
PageSizeOptions="new int[] { 10 }"
HorizontalAlignment="HorizontalAlignment.Left"
HideRowsPerPage="true" />
</PagerContent>
</MudTable>
|
|
private bool isSearching;
private async Task<TableData<ItemResponse>> ServerReloadAsync(TableState state)
{
ArgumentNullException.ThrowIfNull(state);
errors.Clear();
isSearching = true;
var query = itemQueryBuildService.BuildQuery(searchText, state.Page + 1, state.PageSize);
var result = await itemClient.GetPagedAsync(query, CancellationToken.None);
TableData<ItemResponse> data;
if (!result.IsValid)
{
errors.AddRange(result.ErrorMessages);
data = new TableData<ItemResponse> { Items = Enumerable.Empty<ItemResponse>() };
}
else
{
data = new TableData<ItemResponse>
{
TotalItems = result.Result.TotalCount,
Items = result.Result.Response
};
}
isSearching = false;
return data;
}
private void OnSearch(string searchText)
{
this.searchText = searchText;
table.ReloadServerData();
}
private async Task OnDeleteAsync(uint ItemId)
{
var dialogOptions = new DialogOptions { CloseButton = true };
var result = await deleteItemDialog.Show(dialogOptions);
if (result != true)
{
return;
}
var operationStatus = await itemClient.DeleteItemAsync(itemId, CancellationToken.None);
if (operationStatus.Success)
{
snackbar.Add("The item has been well deleted", Severity.Success);
}
else
{
snackbar.Add(
$"The item has not been deleted.<br>Error message:<br>{operationStatus.ErrorMessage}",
Severity.Error);
}
}
|
RowEditCommit and async method
|
<MudTable
RowEditCommit="SaveChangesAsync">
|
|
private async void SaveChangesAsync(object element)
{
var operationStatus = await itemClient
.UpdateItemAsync(item.Id, item, CancellationToken.None);
// because RowEditCommit is an Action<object> it doesn't return a Task and it doesn't wait the task
// if you change the Component you have to call StateHasChanged to update it
StateHasChanged();
|
|
<MudTooltip Text="Delete" Placement="Placement.Right" Arrow="true" Duration="1000">
<MudIconButton Icon="@Icons.Material.Filled.Delete" />
</MudTooltip>
|
Pages/ItemForm.razor
|
@if (Item is not null)
{
<MudCard>
<MudForm @ref="@form" Model="@Item" Validation="@(itemViewModelValidator.ValidateValue)">
<MudCardContent>
<MudTextField
@bind-Value="Item.Name"
For="@(() => Item.Name)"
Label="Name"
DebounceInterval="500"
AutoFocus="true" />
</MudCardContent>
</MudForm>
<MudCardActions>
<MudButton
Variant="Variant.Filled"
Color="Color.Default"
Class="ml-auto mr-2"
OnClick="@CancelRequest">
Cancel
</MudButton>
<MudButton
Variant="Variant.Filled"
Color="Color.Primary"
Class="mr-4"
OnClick="@SubmitAsync">
Save
</MudButton>
</MudCardActions>
</MudCard>
}
|
Pages/ItemForm.razor.cs
|
[Parameter]
public CreateUpdateItemQuery Item { get; set; } = default!;
[Parameter]
public EventCallback<bool> ValidationResult { get; set; }
[Parameter]
public EventCallback CancelRequest { get; set; }
private IForm form = default!;
private readonly CreateUpdateItemQueryValidator itemQueryValidator = new();
private Task SubmitAsync() => ValidationResult.InvokeAsync(form.IsValid);
|
Validators/CreateUpdateItemQueryValidator.cs
|
public class CreateUpdateItemQueryValidator : AbstractValidator<CreateUpdateItemQuery>
{
public CreateUpdateItemQueryValidator()
{
RuleFor(x => x.Name).NotEmpty();
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<CreateUpdateItemQuery>.CreateWithOptions(
(CreateUpdateItemQuery)model, x => x.IncludeProperties(propertyName)));
return result.IsValid
? (IEnumerable<string>)Array.Empty<string>()
: result.Errors.Select(e => e.ErrorMessage);
};
}
|
Pages/CreateItem.razor
|
<ItemForm
Item="@item"
ValidationResult="@(async (success) => await ValidationResultAsync(success))"
CancelRequest="@(() => NavigationManager.NavigateTo("item"))" />
|
Pages/CreateItem.razor.cs
|
private async Task ValidationResultAsync(bool isValid)
{
errors.Clear();
if (!isValid)
{
return;
}
var operationStatus = await ItemClient.CreateAsync(item, CancellationToken.None);
if (operationStatus.Success)
{
Snackbar.Add("The item has been well created", Severity.Success);
NavigationManager.NavigateTo("item");
}
else
{
// server validation errors
errors.AddRange(operationStatus.Errors.SelectMany(x => x.Value));
}
}
|
|
Add the package FluentValidation to use it. |
|
<MudField Label="Type" Class="mt-2" Variant="Variant.Text" InnerPadding="false">
<MudRadioGroup @bind-SelectedOption="item.Type">
<MudRadio Option="@("type1")" Color="Color.Primary">Type1</MudRadio>
<MudRadio Option="@("type2")" Color="Color.Primary">Type2</MudRadio>
</MudRadioGroup>
</MudField>
|
|
<MudNumericField
@bind-Value="item.Quantity"
Label="Quantity"
Variant="Variant.Text"
Min="1"
HideSpinButtons="true" />
|
|
<MudDatePicker Label="Date" @bind-Date="item.Date" DisableToolbar="true" Culture="@frCulture" />
@code {
CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR");
}
|
|
<MudTimePicker Label="Time" @bind-Time="item.Time" Culture="@frCulture" />
|
|
<MudAutocomplete
T="MyNamespace.CurrencyResponse"
Label="Currency"
@bind-Value="item.Currency"
SearchFuncWithCancel="@SearchCurrenciesByCodeAsync"
ToStringFunc="@(x => x == null ? null : x.Code)"
Strict="false"
ShowProgressIndicator="true">
<BeforeItemsTemplate>
<MudText Class="px-4 py-1">3 characters min.</MudText>
</BeforeItemsTemplate>
<NoItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
No matching currencies found
</MudText>
</NoItemsTemplate>
|
|
// call to the web API
private async Task<IEnumerable<CurrencyResponse>> SearchCurrenciesByCodeAsync(string value, CancellationToken cancellationToken)
=> await CurrencyClient.GetCurrenciesByCodeAsync(value, cancellationToken);
// cache all the currencies
private async Task<IEnumerable<CurrencyResponse>> SearchCurrenciesByCodeAsync(string value)
{
var currencies = await CurrencyClient.GetAllAsync();
// if text is null or empty, show complete list
return string.IsNullOrEmpty(value)
? (IEnumerable<CurrencyResponse>)currencies
: currencies.Where(x => x.Code.StartsWith(value, StringComparison.InvariantCultureIgnoreCase));
}
|
|
<MudSelect
T="Namespace.Item"
@bind-Value="@selectedItem"
Label="Item"
AnchorOrigin="Origin.BottomCenter">
@foreach (var item in items)
{
<MudSelectItem Value="@item">@item.Description</MudSelectItem>
}
</MudSelect>
|
|
private readonly List<Item> items = new();
private Item selectedItem = default!;
protected override async Task OnInitializedAsync()
{
// fetch items
var item = items.FirstOrDefault(x => x.Description == "best item");
if (item is not null)
{
selectedItem = item;
}
StateHasChanged();
}
|
|
<MudFileUpload
T="IReadOnlyList<IBrowserFile>"
Accept=".pdf, .csv"
AppendMultipleFiles
MaximumFileCount="@(int.MaxValue)"
FilesChanged="UploadFileAsync"
Class="mt-0">
<ButtonTemplate>
<MudButton
HtmlTag="label"
Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.UploadFile"
for="@context">
Load
</MudButton>
</ButtonTemplate>
</MudFileUpload>
|
|
private async Task UploadFileContentAsync(IReadOnlyList<IBrowserFile> files)
{
var filesTooBig = files.Where(x => x.Size > 512000).ToArray();
errors.AddRange(filesTooBig.Select(GetFileTooBigErrorMessage));
var tasks = files
.Except(filesTooBig)
.Select(
async x => new
{
name = x.Name,
content = await GetFileContentAsync(x)
});
var fileContents = await Task.WhenAll(tasks);
static async Task<byte[]> GetFileContentAsync(IBrowserFile file)
{
using var stream = file.OpenReadStream();
using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
}
|
|
<MudIcon Icon="@Icons.Material.Filled.Sell" Title="Favorite" Color="Color.Primary" Size="Size.Small" />
<MudIconButton Icon="@Icons.Material.Filled.Home" />
<MudIconButton Icon="@myIcon" />
@code
{
string myIcon = MudBlazor.Icons.Material.Filled.Home;
}
|
CSS utilities
Code
|
Description
|
m p |
margin padding
|
t b r l |
top bottom right left
|
x |
left and right
|
y |
top and bottom
|
a |
all 4 sides
|
0 1 n1 auto |
0 4px -4px auto
|
Class
|
Property
|
d-flex |
display: flex;
|
justify-center |
justify-content: center;
|
Code
|
Description
|
border |
border-width: 1px;
|
border-solid |
border-style: solid;
|
mud-border-primary |
|
|
<MudPaper Class="cursor-help">Help</MudPaper>
|
|
<MudPaper Style="@($"color:{theme.Palette.Dark}; background:{theme.Palette.Warning};")">
C# and theme colors
</MudPaper>
<MudPaper Class="blue lighten-4 red-text .text-darken-4">
Material colors background: blue lighten4; color: red darken4
</MudPaper>
<MudPaper Style="@($"color:{Colors.Blue.Lighten4}; background:{Colors.Red.Darken4};")">
C# and material colors
</MudPaper>
@code {
private MudTheme theme = new MudTheme();
}
|
Shared/MainLayout.razor
|
<MudThemeProvider IsDarkMode="true" Theme="customTheme" />
@code {
MudTheme customTheme = new MudTheme
{
PaletteDark = new PaletteDark
{
Background = "#222"
},
LayoutProperties = new LayoutProperties
{
DrawerWidthLeft = "160px" // --mud-drawer-width-left 240px
}
};
}
|
wwwroot/css/theme.css
|
.mud-icon-default {
color: var(--mud-palette-primary);
}
|
CSS isolation
|
- first tag of a component has to be an HTML tag, not a MudBlazor tag.
- use ::deep
|
New project
|
# install MudBlazor templates
dotnet new install MudBlazor.Templates
# help on new project
dotnet new mudblazor --help
# new server project named MudBlazor
dotnet new mudblazor --host server --output MudBlazor
# --no-https is not available
|
No HTTPS
Program.cs
|
// remove
// app.UseHttpsRedirection();
|
Properties/launchSettings.json
|
{
"profiles": {
"MudBlazorThemeManager": {
"applicationUrl": "http://localhost:5000", // remove https url
|
.NET 7
By default the template set the project to .NET 6
MYproject.csproj
|
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ItemGroup>
<PackageReference Include="MudBlazor" Version="6.7.0" />
|
.vscode/launch.json
|
{
"configurations": [
{
"program": "${workspaceFolder}/bin/Debug/net7.0/MyProject.dll",
|
Already existing project
|
dotnet add package MudBlazor
|
_Imports.razor
|
@using MudBlazor
|
Pages/_Host.cshtml
|
<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" />
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
Program.cs
|
builder.Services.AddMudServices();
|
Shared/MainLayout.razor
|
<MudThemeProvider/>
<MudDialogProvider/>
<MudSnackbarProvider/>
|