« MudBlazor » : différence entre les versions

De Banane Atomic
Aller à la navigationAller à la recherche
Aucun résumé des modifications
 
(150 versions intermédiaires par le même utilisateur non affichées)
Ligne 1 : Ligne 1 :
[[Category:Blazor]]
[[Category:Blazor]]
= Links =
= Links =
* [https://mudblazor.com/getting-started Getting started]
* [https://mudblazor.com MudBlazor]
* [https://github.com/MudBlazor/MudBlazor GitHub]
 
= [https://mudblazor.com/getting-started/layouts Layouts] =
<filebox fn='Shared/MainLayout.razor'>
@inherits LayoutComponentBase
 
<PageTitle>My Application</PageTitle>
 
<MudThemeProvider IsDarkMode="true" Theme="customTheme" />
<MudPopoverProvider />
<MudDialogProvider />
<MudSnackbarProvider />
 
<div>
    @*<MudAppBar>
        My Application
    </MudAppBar>*@
    <MudLayout>
        <MudDrawer Open="true">
            <MudDrawerHeader Class="cursor-help mb-1 mx-auto">
                <MudTooltip Text="@($"Version {AssemblyVersion}")"
                            Placement="Placement.Right"
                            Arrow="true"
                            Duration="1000">
                    <MudText Typo="Typo.h4" Color="Color.Primary">My Application</MudText>
                </MudTooltip>
            </MudDrawerHeader>
            <NavMenu />
        </MudDrawer>
 
        <MudMainContent Class="py-2">
            <MudContainer MaxWidth="MaxWidth.Large">
                @Body
            </MudContainer>
        </MudMainContent>
    </MudLayout>
</div>
</filebox>
 
<filebox fn='Shared/MainLayout.razor.css'>
::deep .mud-drawer-content {
    background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
</filebox>
 
<filebox fn='Shared/NavMenu.razor'>
<div>
    <MudNavMenu>
        <MudNavLink Href="/"
                    Match="NavLinkMatch.All"
                    Icon="@Icons.Material.Filled.Home"
                    IconColor="Color.Primary">
            Home
        </MudNavLink>
        <MudNavLink Href="/menu1"
                    Icon="@Icons.Material.Filled.Diversity1"
                    IconColor="Color.Secondary">
            Menu 1
        </MudNavLink>
    </MudNavMenu>
</div>
</filebox>
 
<filebox fn='Shared/NavMenu.razor.css'>
::deep .mud-nav-item {
    font-size: 0.9rem;
    padding: 0 1rem 0.5rem;
}
 
    ::deep .mud-nav-item a {
        height: 3rem;
        border-radius: 4px;
        display: flex;
        align-items: center;
    }
 
::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) {
    background-color: rgba(255,255,255,0.1); /* var(--mud-palette-action-default-hover); */
}
 
::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) {
    background-color: rgba(255,255,255,0.1);
    color: inherit;
}
 
    ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled),
    ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) {
        background-color: rgba(255,255,255,0.1);
    }
 
</filebox>
 
= Components =
== [https://mudblazor.com/components/button Button] ==
<kode lang='razor'>
<MudButton Variant="Variant.Filled"
          StartIcon="@Icons.Material.Filled.Delete"
          Color="Color.Error">Delete</MudButton>
 
<MudButton Href="https://github.com/MudBlazor/MudBlazor"
          Target="_blank"
          Variant="Variant.Filled"
          EndIcon="@Icons.Custom.Brands.GitHub"
          Color="Color.Primary">
    Link
</MudButton>
 
<MudButton Disabled="@processing"
          OnClick="Process"
          Variant="Variant.Filled"
          Color="Color.Primary">
    @if (processing)
    {
        <MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true" />
        <MudText Class="ms-2">Processing</MudText>
    }
    else
    {
        <MudText>Click me</MudText>
    }
</MudButton>
 
@code {
    private bool processing = false;
 
    async Task ProcessAsync()
    {
        processing = true;
        await Task.Delay(2000);
        processing = false;
    }
}
</kode>
 
== [https://mudblazor.com/components/dialog Dialog] ==
<filebox fn='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);
</filebox>
 
<filebox fn='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();
}
</filebox>
 
== [https://mudblazor.com/components/iconbutton IconButton] ==
<kode lang='razor'>
<MudIconButton Icon="@Icons.Material.Filled.DeleteForever"
              Size="Size.Small"
              Color="Color.Error"
              aria-label="delete"
              OnClick="@(() => OnDeleteAsync(@context.Id))"
              Href="@($"items/{context.Id}")" />
</kode>
 
== [https://mudblazor.com/components/image Image] ==
<kode lang='razor'>
<MudImage Src="images/MyImage.svg"
          Alt="MyImage"
          Width="200"
          Elevation="10"
          Class="rounded-lg" />
</kode>
 
== [https://mudblazor.com/components/link Link] ==
<kode lang='razor'>
<MudLink Href=""
        Typo="Typo.h3"
        Underline="Underline.None"
        Color="Color.Default"
        Class="purple-text text-lighten-3">Text</MudLink>
</kode>
 
== [https://mudblazor.com/components/list List] ==
<kode lang='razor'>
<MudPaper MaxHeight="200px" Class="mt-1 overflow-y-auto border-solid border mud-border-error">
    <MudList T="string"
            Dense="true"
            Class="mud-error-text">
        @foreach (var error in Errors)
        {
            <MudListItem Text="@error" Icon="@Icons.Material.Filled.ErrorOutline" IconColor="Color.Error" />
        }
    </MudList>
</MudPaper>
 
@code {
    [Parameter]
    public IReadOnlyCollection<string> Errors { get; set; } = Array.Empty<string>();
}
</kode>
 
== [https://mudblazor.com/components/menu Menu] ==
<kode lang='razor'>
<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>
</kode>
 
== [https://mudblazor.com/components/expansionpanels Expansion Panels] ==
<filebox fn='Pages/MyPage.razor'>
<MudExpansionPanels MultiExpansion="true">
    <MudExpansionPanel Text="Panel One">
        Panel One Content
    </MudExpansionPanel>
    <MudExpansionPanel Text="Panel Two">
        Panel Two Content
    </MudExpansionPanel>
</MudExpansionPanels>
</filebox>
 
== [https://mudblazor.com/components/messagebox MessageBox] ==
<filebox fn='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>
</filebox>
 
<filebox fn='Pages/MyPage.razor.cs'>
private async Task OnDeleteAsync()
{
    var dialogOptions = new DialogOptions
    {
        CloseButton = true,
        DisableBackdropClick = true // modal
    };
    var result = await deleteItemDialog.ShowAsync(dialogOptions);
 
    if (result != true)
    {
        return;
    }
</filebox>
 
== [https://mudblazor.com/components/navmenu Navigation Menu] ==
<kode lang='razor'>
<MudNavMenu>
    <MudNavLink Href=""
                Match="NavLinkMatch.All"
                Icon="@Icons.Material.Filled.Home"
                IconColor="Color.Primary">
        Home
    </MudNavLink>
    <MudNavLink Href="menu1"
                Icon="@Icons.Material.Filled.Diversity1"
                IconColor="Color.Secondary">
        Menu 1
    </MudNavLink>
</MudNavMenu>
</kode>
 
== [https://mudblazor.com/components/progress Progress] ==
<kode lang='razor'>
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true" />
 
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Value="@progressValue" />
</kode>
 
<kode lang='cs'>
private double progressValue; // between 0 and 100
</kode>
 
== [https://mudblazor.com/components/snackbar SnackBar] ==
<filebox fn='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);
</filebox>
 
<filebox fn='Program.cs'>
builder.Services.AddMudServices(config =>
{
    config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight;
    config.SnackbarConfiguration.PreventDuplicates = true;
    config.SnackbarConfiguration.HideTransitionDuration = 100;
});
</filebox>
 
== [https://mudblazor.com/components/typography Text] ==
<kode lang='razor'>
<MudText Typo="Typo.h6"
        Align="Align.Center">
    Title
</MudText>
</kode>
 
== [https://mudblazor.com/components/table Table] ==
* [https://mudblazor.com/api/table API]
{{warn | [https://github.com/MudBlazor/MudBlazor/discussions/4210 No validation in inline editing mode]}}
 
=== [https://mudblazor.com/components/table#sorting Sorting] ===
<kode lang='razor' collapsed>
<MudTable Items="@items"
          SortLabel="Sort By"
          AllowUnsorted="false">
    <HeaderContent>
        <MudTh>Name</MudTh> <!-- no sorting on this column -->
        <MudTh>
            <MudTableSortLabel SortBy="new Func<Item, object>(x => x.Price)"
                              InitialDirection="SortDirection.Descending"> <!-- sorting by default on this column -->
                Price
            </MudTableSortLabel>
        </MudTh>
    </HeaderContent>
</kode>
 
=== [https://mudblazor.com/components/table#server-side-filtering,-sorting-and-pagination Server Side Filtering, Sorting and Pagination] ===
<kode lang='razor' collapsed>
<MudTable @ref="table"
          ServerData="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>
</kode>
 
<kode lang='cs' collapsed>
private bool isSearching;
 
private async Task<TableData<ItemResponse>> ServerReloadAsync(TableState state, CancellationToken cancellationToken)
{
    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);
    }
}
</kode>
 
=== RowEditCommit and async method ===
<kode lang='razor' collapsed>
<MudTable
    RowEditCommit="SaveChangesAsync">
</kode>
 
<kode lang='cs' collapsed>
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();
</kode>
 
== [https://mudblazor.com/components/tabs#simple-tab Tabs] ==
<kode lang='razor'>
<MudTabs Elevation="2" Rounded="true" Centered="true" ApplyEffectsToContainer="true" PanelClass="pa-6">
    <MudTabPanel Text="Tab text 1">...</MudTabPanel>
    <MudTabPanel Text="Tab text 2">...</MudTabPanel>
</MudTabs>
</kode>
 
== [https://mudblazor.com/components/tooltip Tooltip] ==
<kode lang='razor'>
<MudTooltip Text="Delete" Placement="Placement.Right" Arrow="true" Duration="1000">
    <MudIconButton Icon="@Icons.Material.Filled.Delete" />
</MudTooltip>
</kode>
 
== [https://mudblazor.com/components/form Form] ==
<filebox fn='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>
}
</filebox>
 
<filebox fn='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);
</filebox>
 
<filebox fn='Validators/CreateUpdateItemQueryValidator.cs' collapsed>
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);
    };
}
</filebox>
 
<filebox fn='Pages/CreateItem.razor'>
<ItemForm
    Item="@item"
    ValidationResult="@(async (success) => await ValidationResultAsync(success))"
    CancelRequest="@(() => NavigationManager.NavigateTo("item"))" />
</filebox>
 
<filebox fn='Pages/CreateItem.razor.cs' collapsed>
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));
    }
}
</filebox>
 
{{info | Add the package {{boxx|FluentValidation}} to use it. }}
 
=== [https://mudblazor.com/components/textfield Text field] ===
<kode lang='razor'>
<MudTextField @ref="searchTextField"
              T="string"
              ValueChanged="@(s => OnSearch(s))"
              Placeholder="Search"
              Adornment="Adornment.Start"
              AdornmentIcon="@Icons.Material.Filled.Search"
              IconSize="Size.Medium"
              Clearable="true"
              Class="mt-0 mx-16">
</kode>
 
=== [https://mudblazor.com/components/radio Radio buttons] ===
<kode lang='razor'>
<MudField Label="Type" Class="mt-2" Variant="Variant.Text" InnerPadding="false">
    <MudRadioGroup @bind-Value="item.Type">
        <MudRadio Value="@("type1")" Color="Color.Primary">Type1</MudRadio>
        <MudRadio Value="@("type2")" Color="Color.Primary">Type2</MudRadio>
    </MudRadioGroup>
</MudField>
</kode>
 
=== [https://mudblazor.com/components/numericfield Numeric field] ===
<kode lang='razor'>
<MudNumericField
    @bind-Value="item.Quantity"
    Label="Quantity"
    Variant="Variant.Text"
    Min="1"
    HideSpinButtons="true" />
</kode>
 
=== [https://mudblazor.com/components/datepicker Date picker] ===
<kode lang='razor'>
<MudDatePicker Label="Date"
              @bind-Date="item.Date"
              Culture="@frCulture" />
 
@code {
    CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR");
}
</kode>
 
=== [https://mudblazor.com/components/timepicker Time picker] ===
<kode lang='razor'>
<MudTimePicker Label="Time"
              @bind-Time="item.Time"
              Culture="@frCulture" />
 
@code {
    CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR");
}
</kode>
 
=== [https://mudblazor.com/components/autocomplete Autocomplete list] ===
<kode lang='razor'>
<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>
</kode>
 
<kode lang='cs'>
// 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));
}
</kode>
 
=== [https://mudblazor.com/components/select Select] ===
* [https://mudblazor.com/api/select API]
<kode lang='razor'>
@* with an enum *@
<MudSelect @bind-Value="logLevel" Label="Log level">
    @foreach (LogLevel logLevel in Enum.GetValues(typeof(LogLevel)))
    {
        <MudSelectItem Value="@logLevel">@logLevel.ToString()</MudSelectItem>
    }
</MudSelect>
 
@* with a list of items *@
<MudSelect
    T="Namespace.Item"
    @bind-Value="@selectedItem"
    Label="Item"
    AnchorOrigin="Origin.BottomCenter">
@foreach (var item in items)
{
    <MudSelectItem Value="@item">@item.Description</MudSelectItem>
}
</MudSelect>
 
@* with static items *@
<MudSelect @bind-Value="@selectedItem" Label="Item">
    <MudSelectItem Value="@("Item1")">Item1</MudSelectItem>
    <MudSelectItem Value="@("Item2")">Item2</MudSelectItem>
</MudSelect>
</kode>
 
<kode lang='cs'>
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();
}
</kode>
 
=== [https://mudblazor.com/components/fileupload File Upload] ===
* [https://mudblazor.com/api/fileupload API]
<kode lang='razor'>
<MudFileUpload T="IReadOnlyList<IBrowserFile>"
              Accept=".pdf, .csv"
              AppendMultipleFiles
              MaximumFileCount="@(int.MaxValue)"
              FilesChanged="UploadFileAsync"
              Class="mt-0">
    <ActivatorContent>
        <MudButton Variant="Variant.Filled"
                  Color="Color.Primary"
                  StartIcon="@Icons.Material.Filled.UploadFile">
            Load
        </MudButton>
    </ActivatorContent>
</MudFileUpload>
</kode>
 
<kode lang='cs'>
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();
    }
}
</kode>
 
= [https://mudblazor.com/components/icons Icons] =
* [https://mudblazor.com/features/icons Icons list]
<kode lang='razor'>
<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;
}
</kode>
 
= CSS utilities =
== [https://mudblazor.com/utilities/spacing Margin Padding] ==
{| class="wikitable wtp wtmono1 wtmono2"
! 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
|}
 
<kode lang='html'>
<MudPaper Class="pa-4 mr-16">
</kode>
 
== [https://mudblazor.com/utilities/display Display] ==
{| class="wikitable wtp wtmono1 wtmono2"
! Class
! Property
|-
| d-block || display: block;
|-
| d-inline || display: inline;
|-
| d-none || display: none;
|}
 
== [https://mudblazor.com/utilities/enable-flex Flexbox] ==
{| class="wikitable wtp wtmono1 wtmono2"
! Class
! Property
|-
| d-flex  || display: flex;
|-
| justify-center  ||  justify-content: center;
|-
| justify-space-around  ||  justify-content: space-around;
|}
* [https://mudblazor.com/utilities/justify-content justify-content]
 
== [https://mudblazor.com/utilities/border-style Border] ==
{| class="wikitable wtp wtmono1 wtmono2"
! Code
! Description
|-
| border || border-width: 1px;
|-
| border-solid || border-style: solid;
|-
| mud-border-primary ||
|}
 
== [https://mudblazor.com/utilities/cursor Cursor / Mouse] ==
<kode lang='razor'>
<MudPaper Class="cursor-pointer">Hand with finger raised</MudPaper>
<MudPaper Class="cursor-help">Question mark</MudPaper>
</kode>
 
= [https://mudblazor.com/features/colors Colors] =
* [https://stackoverflow.com/questions/72336982/how-to-use-textprimary-textsecondary-color-values-in-mudblazor-theme-palette]
<kode lang='razor'>
<MudPaper Color="Color.Primary">
    Color enum
</MudPaper>
 
<MudPaper Class="mud-info mud-secondary-text">
    CSS and theme colors. background: theme info blue; color: theme secondary pink
</MudPaper>
<!-- mud-theme-primary vs mud-primary -->
 
<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">
    CSS and material colors background: blue lighten4; color: red darken4
</MudPaper>
 
<MudPaper Style="@($"color:{Colors.Blue.Lighten4}; background:{Colors.Red.Darken4};")">
    C# and material colors
</MudPaper>
 
<span style="color: var(--mud-palette-warning);">C# and CSS colors</span>
 
@code {
    private MudTheme theme = new MudTheme();
}
</kode>
 
= [https://mudblazor.com/customization/default-theme Theme] =
* [https://mudblazor.com/customization/default-theme Default theme]
* [https://mudblazor.com/customization/palette Palette]
 
<filebox fn='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
        }
    };
}
</filebox>
 
<filebox fn='wwwroot/css/theme.css'>
.mud-icon-default {
    color: var(--mud-palette-primary);
}
</filebox>
 
== [[Blazor_ASP.NET_Core_7.0#CSS_isolation|CSS isolation]] ==
{{warn |
* first tag of a component has to be an HTML tag ({{boxx|<nowiki><div></nowiki>}}), not a MudBlazor tag.
* use {{boxx|::deep .my-class}} when class is set on a MudBlazor component
* you may need to refresh the browser cache
}}
 
<filebox fn='MyPage.razor'>
<div>
    <MudText Typo="Typo.h1" id="title1">@name</MudText>
    <h2 id="title2">@name</h2>
<div>
</filebox>
 
<filebox fn='MyPage.razor.css'>
::deep title1 { }
title2 { }
</filebox>
 
= Build component in the code-behind =
<filebox fn='MyPage.razor'>
<MudTd DataLabel="Type">
    @GetIconFromType(context.Type)
</MudTd>
</filebox>
 
<filebox fn='MyPage.razor.cs'>
private static RenderFragment GetIconFromType(string type)
    => builder =>
    {
        builder.OpenComponent<MudIcon>(0);
        builder.AddAttribute(1, "Title", type);
        builder.AddAttribute(2, "Icon", @Icons.Material.Filled.Money);
        builder.AddAttribute(3, "Color", type switch
        {
            "deposit" => Color.Success,
            "withdrawal" => Color.Warning,
            "charge" => Color.Error,
            _ => throw new NotSupportedException($"The type '{type}' is not supported.")
        });
        builder.CloseComponent();
    };
</filebox>


= [https://mudblazor.com/getting-started/installation Installation] =
= [https://mudblazor.com/getting-started/installation Installation] =
== New project from template ==
<kode lang='bash'>
<kode lang='bash'>
# install MudBlazor templates
# install MudBlazor templates
Ligne 10 : Ligne 995 :
# help on new project
# help on new project
dotnet new mudblazor --help
dotnet new mudblazor --help
dotnet new mudblazor --host wasm --name MyApplication
 
# new server project named MudBlazor
dotnet new mudblazor --host server --output MudBlazor
# --no-https is not available
</kode>
 
=== Remove HTTPS ===
<filebox fn='Program.cs'>
// remove
// app.UseHttpsRedirection();
</filebox>
 
<filebox fn='Properties/launchSettings.json'>
{
  "iisSettings": {
    "iisExpress": {
      "applicationUrl": "http://localhost:5000",
      // "sslPort": 44393  // remove
    }
  },
  "profiles": {
    "MudBlazorThemeManager": {
      "applicationUrl": "http://localhost:5000", // remove https url
</filebox>
 
=== .NET 7 ===
By default the template set the project to .NET 6
<filebox fn='MYproject.csproj' lang='xml'>
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
</filebox>
 
<filebox fn='.vscode/launch.json'>
{
  "configurations": [
    {
      "program": "${workspaceFolder}/bin/Debug/net7.0/MyProject.dll",
</filebox>
 
=== _Layout.cshtml ===
Remove {{boxx|_Layout.cshtml}}
<filebox fn='_Host.cshtml' lang='razor' collapsed>
@page "/"
@using Microsoft.AspNetCore.Components.Web
@namespace MyApp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
 
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="~/" />
    <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" />
    @* CSS isolation *@
    <link rel="stylesheet" href="MyApp.styles.css" />
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
 
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />
 
    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
 
    <script src="_framework/blazor.server.js"></script>
    <script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>
</html>
</filebox>
 
=== Styled navigation menu ===
<filebox fn='Shared/MainLayout.razor' collapsed>
<MudThemeProvider IsDarkMode="true" Theme="customTheme" />
 
@code {
    MudTheme customTheme = new MudTheme
    {
        LayoutProperties = new LayoutProperties
        {
            DrawerWidthLeft = "160px" // --mud-drawer-width-left 240px
        }
    };
}
</filebox>
 
<filebox fn='Shared/MainLayout.razor.css' collapsed>
::deep .mud-drawer-content {
    background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
</filebox>
 
<filebox fn='Shared/NavMenu.razor.css' collapsed>
::deep .mud-nav-item {
    font-size: 0.9rem;
    padding: 0 1rem 0.5rem;
}
 
    ::deep .mud-nav-item a {
        height: 3rem;
        border-radius: 4px;
        display: flex;
        align-items: center;
    }
 
::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) {
    background-color: rgba(255,255,255,0.1); /* var(--mud-palette-action-default-hover); */
}
 
::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) {
    background-color: rgba(255,255,255,0.1);
    color: inherit;
}
 
    ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled),
    ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) {
        background-color: rgba(255,255,255,0.1);
    }
</filebox>
 
== Already existing project ==
<kode lang='bash'>
dotnet add package MudBlazor
</kode>
</kode>
<filebox fn='_Imports.razor'>
@using MudBlazor
</filebox>
<filebox fn='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>
</filebox>
<filebox fn='Program.cs'>
builder.Services.AddMudServices();
</filebox>
<filebox fn='Shared/MainLayout.razor'>
<MudThemeProvider />
<MudPopoverProvider />
<MudDialogProvider />
<MudSnackbarProvider />
</filebox>

Dernière version du 16 novembre 2024 à 22:57

Links

Layouts

Shared/MainLayout.razor
@inherits LayoutComponentBase

<PageTitle>My Application</PageTitle>

<MudThemeProvider IsDarkMode="true" Theme="customTheme" />
<MudPopoverProvider />
<MudDialogProvider />
<MudSnackbarProvider />

<div>
    @*<MudAppBar>
        My Application
    </MudAppBar>*@
    <MudLayout>
        <MudDrawer Open="true">
            <MudDrawerHeader Class="cursor-help mb-1 mx-auto">
                <MudTooltip Text="@($"Version {AssemblyVersion}")"
                            Placement="Placement.Right"
                            Arrow="true"
                            Duration="1000">
                    <MudText Typo="Typo.h4" Color="Color.Primary">My Application</MudText>
                </MudTooltip>
            </MudDrawerHeader>
            <NavMenu />
        </MudDrawer>

        <MudMainContent Class="py-2">
            <MudContainer MaxWidth="MaxWidth.Large">
                @Body
            </MudContainer>
        </MudMainContent>
    </MudLayout>
</div>
Shared/MainLayout.razor.css
::deep .mud-drawer-content {
    background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
Shared/NavMenu.razor
<div>
    <MudNavMenu>
        <MudNavLink Href="/"
                    Match="NavLinkMatch.All"
                    Icon="@Icons.Material.Filled.Home"
                    IconColor="Color.Primary">
            Home
        </MudNavLink>
        <MudNavLink Href="/menu1"
                    Icon="@Icons.Material.Filled.Diversity1"
                    IconColor="Color.Secondary">
            Menu 1
        </MudNavLink>
    </MudNavMenu>
</div>
Shared/NavMenu.razor.css
::deep .mud-nav-item {
    font-size: 0.9rem;
    padding: 0 1rem 0.5rem;
}

    ::deep .mud-nav-item a {
        height: 3rem;
        border-radius: 4px;
        display: flex;
        align-items: center;
    }

::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) {
    background-color: rgba(255,255,255,0.1); /* var(--mud-palette-action-default-hover); */
}

::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) {
    background-color: rgba(255,255,255,0.1);
    color: inherit;
}

    ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled),
    ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) {
        background-color: rgba(255,255,255,0.1);
    }

Components

Button

Razor.svg
<MudButton Variant="Variant.Filled"
           StartIcon="@Icons.Material.Filled.Delete"
           Color="Color.Error">Delete</MudButton>

<MudButton Href="https://github.com/MudBlazor/MudBlazor"
           Target="_blank"
           Variant="Variant.Filled"
           EndIcon="@Icons.Custom.Brands.GitHub"
           Color="Color.Primary">
    Link
</MudButton>

<MudButton Disabled="@processing"
           OnClick="Process"
           Variant="Variant.Filled"
           Color="Color.Primary">
    @if (processing)
    {
        <MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true" />
        <MudText Class="ms-2">Processing</MudText>
    }
    else
    {
        <MudText>Click me</MudText>
    }
</MudButton>

@code {
    private bool processing = false;

    async Task ProcessAsync()
    {
        processing = true;
        await Task.Delay(2000);
        processing = false;
    }
}

Dialog

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();
}

IconButton

Razor.svg
<MudIconButton Icon="@Icons.Material.Filled.DeleteForever"
               Size="Size.Small"
               Color="Color.Error"
               aria-label="delete"
               OnClick="@(() => OnDeleteAsync(@context.Id))"
               Href="@($"items/{context.Id}")" />

Image

Razor.svg
<MudImage Src="images/MyImage.svg"
          Alt="MyImage"
          Width="200"
          Elevation="10"
          Class="rounded-lg" />

Link

Razor.svg
<MudLink Href=""
         Typo="Typo.h3"
         Underline="Underline.None"
         Color="Color.Default"
         Class="purple-text text-lighten-3">Text</MudLink>

List

Razor.svg
<MudPaper MaxHeight="200px" Class="mt-1 overflow-y-auto border-solid border mud-border-error">
    <MudList T="string"
             Dense="true"
             Class="mud-error-text">
        @foreach (var error in Errors)
        {
            <MudListItem Text="@error" Icon="@Icons.Material.Filled.ErrorOutline" IconColor="Color.Error" />
        }
    </MudList>
</MudPaper>

@code {
    [Parameter]
    public IReadOnlyCollection<string> Errors { get; set; } = Array.Empty<string>();
}

Menu

Razor.svg
<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>

Expansion Panels

Pages/MyPage.razor
<MudExpansionPanels MultiExpansion="true">
    <MudExpansionPanel Text="Panel One">
        Panel One Content
    </MudExpansionPanel>
    <MudExpansionPanel Text="Panel Two">
        Panel Two Content
    </MudExpansionPanel>
</MudExpansionPanels>

MessageBox

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.ShowAsync(dialogOptions);

    if (result != true)
    {
        return;
    }

Navigation Menu

Razor.svg
<MudNavMenu>
    <MudNavLink Href=""
                Match="NavLinkMatch.All"
                Icon="@Icons.Material.Filled.Home"
                IconColor="Color.Primary">
        Home
    </MudNavLink>
    <MudNavLink Href="menu1"
                Icon="@Icons.Material.Filled.Diversity1"
                IconColor="Color.Secondary">
        Menu 1
    </MudNavLink>
</MudNavMenu>

Progress

Razor.svg
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true" />

<MudProgressCircular Color="Color.Primary" Size="Size.Small" Value="@progressValue" />
Cs.svg
private double progressValue; // between 0 and 100

SnackBar

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;
});

Text

Razor.svg
<MudText Typo="Typo.h6"
         Align="Align.Center">
    Title
</MudText>

Table

No validation in inline editing mode

Sorting

Razor.svg
<MudTable Items="@items"
          SortLabel="Sort By"
          AllowUnsorted="false">
    <HeaderContent>
        <MudTh>Name</MudTh> <!-- no sorting on this column -->
        <MudTh>
            <MudTableSortLabel SortBy="new Func<Item, object>(x => x.Price)"
                               InitialDirection="SortDirection.Descending"> <!-- sorting by default on this column -->
                Price
            </MudTableSortLabel>
        </MudTh>
    </HeaderContent>

Server Side Filtering, Sorting and Pagination

Razor.svg
<MudTable @ref="table"
          ServerData="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>
Cs.svg
private bool isSearching;

private async Task<TableData<ItemResponse>> ServerReloadAsync(TableState state, CancellationToken cancellationToken)
{
    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

Razor.svg
<MudTable
    RowEditCommit="SaveChangesAsync">
Cs.svg
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();

Tabs

Razor.svg
<MudTabs Elevation="2" Rounded="true" Centered="true" ApplyEffectsToContainer="true" PanelClass="pa-6">
    <MudTabPanel Text="Tab text 1">...</MudTabPanel>
    <MudTabPanel Text="Tab text 2">...</MudTabPanel>
</MudTabs>

Tooltip

Razor.svg
<MudTooltip Text="Delete" Placement="Placement.Right" Arrow="true" Duration="1000">
    <MudIconButton Icon="@Icons.Material.Filled.Delete" />
</MudTooltip>

Form

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.

Text field

Razor.svg
<MudTextField @ref="searchTextField"
              T="string"
              ValueChanged="@(s => OnSearch(s))"
              Placeholder="Search"
              Adornment="Adornment.Start"
              AdornmentIcon="@Icons.Material.Filled.Search"
              IconSize="Size.Medium"
              Clearable="true"
              Class="mt-0 mx-16">

Radio buttons

Razor.svg
<MudField Label="Type" Class="mt-2" Variant="Variant.Text" InnerPadding="false">
    <MudRadioGroup @bind-Value="item.Type">
        <MudRadio Value="@("type1")" Color="Color.Primary">Type1</MudRadio>
        <MudRadio Value="@("type2")" Color="Color.Primary">Type2</MudRadio>
    </MudRadioGroup>
</MudField>

Numeric field

Razor.svg
<MudNumericField 
    @bind-Value="item.Quantity"
    Label="Quantity"
    Variant="Variant.Text"
    Min="1"
    HideSpinButtons="true" />

Date picker

Razor.svg
<MudDatePicker Label="Date"
               @bind-Date="item.Date"
               Culture="@frCulture" />

@code {
    CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR");
}

Time picker

Razor.svg
<MudTimePicker Label="Time"
               @bind-Time="item.Time"
               Culture="@frCulture" />

@code {
    CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR");
}

Autocomplete list

Razor.svg
<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>
Cs.svg
// 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));
}

Select

Razor.svg
@* with an enum *@
<MudSelect @bind-Value="logLevel" Label="Log level">
    @foreach (LogLevel logLevel in Enum.GetValues(typeof(LogLevel)))
    {
        <MudSelectItem Value="@logLevel">@logLevel.ToString()</MudSelectItem>
    }
</MudSelect>

@* with a list of items *@
<MudSelect
    T="Namespace.Item"
    @bind-Value="@selectedItem"
    Label="Item"
    AnchorOrigin="Origin.BottomCenter">
@foreach (var item in items)
{
    <MudSelectItem Value="@item">@item.Description</MudSelectItem>
}
</MudSelect>

@* with static items *@
<MudSelect @bind-Value="@selectedItem" Label="Item">
    <MudSelectItem Value="@("Item1")">Item1</MudSelectItem>
    <MudSelectItem Value="@("Item2")">Item2</MudSelectItem>
</MudSelect>
Cs.svg
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();
}

File Upload

Razor.svg
<MudFileUpload T="IReadOnlyList<IBrowserFile>"
               Accept=".pdf, .csv"
               AppendMultipleFiles
               MaximumFileCount="@(int.MaxValue)"
               FilesChanged="UploadFileAsync"
               Class="mt-0">
    <ActivatorContent>
        <MudButton Variant="Variant.Filled"
                   Color="Color.Primary"
                   StartIcon="@Icons.Material.Filled.UploadFile">
            Load
        </MudButton>
    </ActivatorContent>
</MudFileUpload>
Cs.svg
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();
    }
}

Icons

Razor.svg
<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

Margin Padding

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
Html.svg
<MudPaper Class="pa-4 mr-16">

Display

Class Property
d-block display: block;
d-inline display: inline;
d-none display: none;

Flexbox

Class Property
d-flex display: flex;
justify-center justify-content: center;
justify-space-around justify-content: space-around;

Border

Code Description
border border-width: 1px;
border-solid border-style: solid;
mud-border-primary

Cursor / Mouse

Razor.svg
<MudPaper Class="cursor-pointer">Hand with finger raised</MudPaper>
<MudPaper Class="cursor-help">Question mark</MudPaper>

Colors

Razor.svg
<MudPaper Color="Color.Primary">
    Color enum
</MudPaper>

<MudPaper Class="mud-info mud-secondary-text">
    CSS and theme colors. background: theme info blue; color: theme secondary pink
</MudPaper>
<!-- mud-theme-primary vs mud-primary -->

<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">
    CSS and material colors background: blue lighten4; color: red darken4
</MudPaper>

<MudPaper Style="@($"color:{Colors.Blue.Lighten4}; background:{Colors.Red.Darken4};")">
    C# and material colors
</MudPaper>

<span style="color: var(--mud-palette-warning);">C# and CSS colors</span>

@code {
    private MudTheme theme = new MudTheme();
}

Theme

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 (<div>), not a MudBlazor tag.
  • use ::deep .my-class when class is set on a MudBlazor component
  • you may need to refresh the browser cache
MyPage.razor
<div>
    <MudText Typo="Typo.h1" id="title1">@name</MudText>
    <h2 id="title2">@name</h2>
<div>
MyPage.razor.css
::deep title1 { }
title2 { }

Build component in the code-behind

MyPage.razor
<MudTd DataLabel="Type">
    @GetIconFromType(context.Type)
</MudTd>
MyPage.razor.cs
private static RenderFragment GetIconFromType(string type)
    => builder =>
    {
        builder.OpenComponent<MudIcon>(0);
        builder.AddAttribute(1, "Title", type);
        builder.AddAttribute(2, "Icon", @Icons.Material.Filled.Money);
        builder.AddAttribute(3, "Color", type switch
        {
            "deposit" => Color.Success,
            "withdrawal" => Color.Warning,
            "charge" => Color.Error,
            _ => throw new NotSupportedException($"The type '{type}' is not supported.")
        });
        builder.CloseComponent();
    };

Installation

New project from template

Bash.svg
# 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

Remove HTTPS

Program.cs
// remove
// app.UseHttpsRedirection();
Properties/launchSettings.json
{
  "iisSettings": {
    "iisExpress": {
      "applicationUrl": "http://localhost:5000",
      // "sslPort": 44393  // remove
    }
  },
  "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>
.vscode/launch.json
{
  "configurations": [
    {
      "program": "${workspaceFolder}/bin/Debug/net7.0/MyProject.dll",

_Layout.cshtml

Remove _Layout.cshtml

_Host.cshtml
@page "/"
@using Microsoft.AspNetCore.Components.Web
@namespace MyApp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="~/" />
    <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" />
    @* CSS isolation *@
    <link rel="stylesheet" href="MyApp.styles.css" />
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>

<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
    <script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>
</html>

Styled navigation menu

Shared/MainLayout.razor
<MudThemeProvider IsDarkMode="true" Theme="customTheme" />

@code {
    MudTheme customTheme = new MudTheme
    {
        LayoutProperties = new LayoutProperties
        {
            DrawerWidthLeft = "160px" // --mud-drawer-width-left 240px
        }
    };
}
Shared/MainLayout.razor.css
::deep .mud-drawer-content {
    background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
Shared/NavMenu.razor.css
::deep .mud-nav-item {
    font-size: 0.9rem;
    padding: 0 1rem 0.5rem;
}

    ::deep .mud-nav-item a {
        height: 3rem;
        border-radius: 4px;
        display: flex;
        align-items: center;
    }

::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) {
    background-color: rgba(255,255,255,0.1); /* var(--mud-palette-action-default-hover); */
}

::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) {
    background-color: rgba(255,255,255,0.1);
    color: inherit;
}

    ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled),
    ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) {
        background-color: rgba(255,255,255,0.1);
    }

Already existing project

Bash.svg
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 />
<MudPopoverProvider />
<MudDialogProvider />
<MudSnackbarProvider />