Browse Source

Add MudBlazor documentation for Blazor UI tutorials and framework guides

- Introduce a new "BlazorUI" doc-params dimension (Blazorise / MudBlazor) so
  Blazor docs can show MudBlazor variants of code samples next to the existing
  Blazorise ones
- Cover framework/ui/blazor (overall, theming, basic-theme, forms-validation,
  submit-button, page-layout, page-header, page-toolbar-extensions,
  entity-action-extensions, data-table-column-extensions, error-handling,
  customization-overriding-components)
- Cover ui-themes (index, basic-theme, lepton-x, lepton-x-lite)
- Cover tutorials (book-store part 02/03/09/10, todo single-layer/layered,
  modular-crm part 03/05/06, book-store-with-abp-suite part-05)
- Add an optional "DependsOn" map to DocumentParameterDto so a parameter can
  be hidden when dependencies aren't satisfied (e.g. only show BlazorUI when
  UI is one of Blazor/BlazorServer/BlazorWebApp). Visibility is evaluated on
  the server in the project document index page.
pull/25380/head
maliming 1 week ago
parent
commit
fdcad645dc
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 11
      docs/en/docs-params.json
  2. 15
      docs/en/framework/ui/blazor/basic-theme.md
  3. 63
      docs/en/framework/ui/blazor/components/submit-button.md
  4. 78
      docs/en/framework/ui/blazor/customization-overriding-components.md
  5. 30
      docs/en/framework/ui/blazor/data-table-column-extensions.md
  6. 39
      docs/en/framework/ui/blazor/entity-action-extensions.md
  7. 61
      docs/en/framework/ui/blazor/error-handling.md
  8. 100
      docs/en/framework/ui/blazor/forms-validation.md
  9. 25
      docs/en/framework/ui/blazor/overall.md
  10. 79
      docs/en/framework/ui/blazor/page-header.md
  11. 46
      docs/en/framework/ui/blazor/page-layout.md
  12. 75
      docs/en/framework/ui/blazor/page-toolbar-extensions.md
  13. 50
      docs/en/framework/ui/blazor/theming.md
  14. 3
      docs/en/tutorials/book-store-with-abp-suite/part-05.md
  15. 80
      docs/en/tutorials/book-store/part-02.md
  16. 306
      docs/en/tutorials/book-store/part-03.md
  17. 315
      docs/en/tutorials/book-store/part-09.md
  18. 115
      docs/en/tutorials/book-store/part-10.md
  19. 43
      docs/en/tutorials/modular-crm/part-03.md
  20. 45
      docs/en/tutorials/modular-crm/part-05.md
  21. 45
      docs/en/tutorials/modular-crm/part-06.md
  22. 46
      docs/en/tutorials/todo/layered/index.md
  23. 46
      docs/en/tutorials/todo/single-layer/index.md
  24. 4
      docs/en/ui-themes/basic-theme/index.md
  25. 9
      docs/en/ui-themes/index.md
  26. 16
      docs/en/ui-themes/lepton-x-lite/blazor.md
  27. 16
      docs/en/ui-themes/lepton-x/blazor.md
  28. 8
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs
  29. 3
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml
  30. 20
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs

11
docs/en/docs-params.json

@ -36,6 +36,17 @@
"Tiered": "Tiered",
"Microservice": "Microservice"
}
},
{
"name": "BlazorUI",
"displayName": "Blazor UI Library",
"values": {
"Blazorise": "Blazorise",
"MudBlazor": "MudBlazor"
},
"dependsOn": {
"UI": ["Blazor", "BlazorServer", "BlazorWebApp"]
}
}
]
}

15
docs/en/framework/ui/blazor/basic-theme.md

@ -10,7 +10,8 @@
````json
//[doc-params]
{
"UI": ["Blazor", "BlazorServer"]
"UI": ["Blazor", "BlazorServer"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -20,6 +21,18 @@ The Basic Theme is a theme implementation for the Blazor UI. It is a minimalist
> See the [Theming document](theming.md) to learn about themes.
{{if BlazorUI == "MudBlazor"}}
> **MudBlazor Variant** — When the `--blazor-ui-library mudblazor` option is used, the Basic Theme ships as a MudBlazor variant. Replace `BasicTheme` with `MudBlazorBasicTheme` everywhere in this document (package names, module type names and namespaces). The MudBlazor variant is **not** based on Bootstrap — it uses MudBlazor's Material Design layout components.
>
> Concrete package names you will see when using the MudBlazor variant:
>
> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorBasicTheme`
> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorBasicTheme.Bundling`
> * Module types: `Abp{...}MudBlazorBasicThemeModule`, `Abp{...}MudBlazorBasicThemeBundlingModule`
{{end}}
## Installation
If you need to manually this theme, follow the steps below:

63
docs/en/framework/ui/blazor/components/submit-button.md

@ -1,12 +1,21 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json
//[doc-seo]
{
"Description": "Explore the `SubmitButton` component in Blazor UI, designed for easy form submissions with localization support and loading indicators."
"Description": "Explore the submit button component in Blazor UI, designed for easy form submissions with localization support and loading indicators."
}
```
# Blazor UI: SubmitButton Component
{{if BlazorUI == "Blazorise"}}
`SubmitButton` is a simple wrapper around `Button` component. It is used to be placed inside of page Form or Modal dialogs where it can response to user actions and to be activated as a default button by pressing an ENTER key. Once clicked it will go into the `disabled` state and also it will show a small loading indicator until clicked event is finished.
## Quick Example
@ -29,4 +38,54 @@ Notice that we didn't specify any text, like `Save Changes`. This is because `Su
<SubmitButton Clicked="@YourSaveOperation">
@L["Save"]
</SubmitButton>
```
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
The MudBlazor variant of ABP UI does not ship a dedicated `SubmitButton` wrapper. Use the standard `MudButton` together with the typical `Processing`/`Disabled` pattern to disable the button and show a progress indicator while the click handler is running.
## Quick Example
```razor
<MudButton OnClick="@SaveAsync"
Variant="Variant.Filled"
Color="Color.Primary"
Disabled="@_processing">
@if (_processing)
{
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="me-2" />
}
@L["Save"]
</MudButton>
@code {
private bool _processing;
private async Task SaveAsync()
{
_processing = true;
try
{
// ... your save operation
}
finally
{
_processing = false;
}
}
}
```
## Submit on Enter
When the button is placed inside a `<MudForm>` or a `<MudDialog>`, pressing ENTER inside an input control submits the form. To run validation before saving, call `_form.Validate()` first. See the [Forms & Validation](../forms-validation.md) page for details.
## Use Inside `AbpMudCrudPageBase`
The MudBlazor CRUD page base (`AbpMudCrudPageBase`) already wires up the standard create/update buttons inside its dialogs and shows a progress indicator while the application service call is running. In most cases you don't need to author a save button by hand; override `OnCreatingEntityAsync` / `OnUpdatingEntityAsync` instead.
> Check the [MudBlazor button documentation](https://mudblazor.com/components/button) for all available options.
{{end}}

78
docs/en/framework/ui/blazor/customization-overriding-components.md

@ -10,7 +10,8 @@
````json
//[doc-params]
{
"UI": ["Blazor", "BlazorServer"]
"UI": ["Blazor", "BlazorServer"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -41,6 +42,8 @@ The next step is to create a razor component, like `MyBranding.razor`, in your a
The content of the `MyBranding.razor` is shown below:
{{if BlazorUI == "Blazorise"}}
````html
@using Volo.Abp.DependencyInjection
{{if UI == "BlazorServer"}}
@ -58,6 +61,33 @@ The content of the `MyBranding.razor` is shown below:
</a>
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
The MudBlazor variant uses the LeptonX-based MudBlazor theme by default. The component to override is the `Branding` component shipped by the active MudBlazor theme:
````html
@using Volo.Abp.DependencyInjection
{{if UI == "BlazorServer"}}
@using Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX
{{end}}
{{if UI == "Blazor"}}
@using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX
{{end}}
@inherits Branding
@attribute [ExposeServices(typeof(Branding))]
@attribute [Dependency(ReplaceServices = true)]
<MudLink Href="/" Underline="Underline.None">
<MudImage Src="bookstore-logo.png" Width="250" Height="60" />
</MudLink>
````
> If you are using the MudBlazor BasicTheme or a different MudBlazor theme, replace the namespace with the namespace of that theme's `Themes/<ThemeName>` folder.
{{end}}
Let's explain the code:
* `@inherits Branding` line inherits the Branding component defined by the [Basic Theme](basic-theme.md) (in the {{if UI == "BlazorServer"}}`Volo.Abp.AspNetCore.Components.Server.BasicTheme.Themes.Basic`{{end}} {{if UI == "Blazor"}}`Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic`{{end}} namespace).
@ -75,6 +105,8 @@ Now, you can run the application to see the result:
If you prefer to use code-behind file for the C# code of your component, you can use the attributes in the C# side.
{{if BlazorUI == "Blazorise"}}
**MyBlazor.razor**
````html
@ -113,6 +145,50 @@ namespace MyProject.Blazor.Components
}
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
**MyBlazor.razor**
````html
{{if UI == "BlazorServer"}}
@using Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX
{{end}}
{{if UI == "Blazor"}}
@using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX
{{end}}
@inherits Branding
<MudLink Href="/" Underline="Underline.None">
<MudImage Src="bookstore-logo.png" Width="250" Height="60" />
</MudLink>
````
**MyBlazor.razor.cs**
````csharp
{{if UI == "BlazorServer"}}
using Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX;
{{end}}
{{if UI == "Blazor"}}
using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX;
{{end}}
using Volo.Abp.DependencyInjection;
namespace MyProject.Blazor.Components
{
[ExposeServices(typeof(Branding))]
[Dependency(ReplaceServices = true)]
public partial class MyBranding
{
}
}
````
{{end}}
## Theming
The [Theming](theming.md) system allows you to build your own theme. You can create your theme from scratch or get the [Basic Theme](basic-theme.md) and change however you like.

30
docs/en/framework/ui/blazor/data-table-column-extensions.md

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json
//[doc-seo]
{
@ -102,6 +109,8 @@ public class CustomTableColumn
Navigate to the razor file and paste the following code.
{{if BlazorUI == "Blazorise"}}
```csharp
@using System
@using Volo.Abp.Identity
@ -116,6 +125,27 @@ else
}
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```razor
@using Volo.Abp.Identity
@if (Data.As<IdentityUserDto>().EmailConfirmed)
{
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" />
}
else
{
<MudIcon Icon="@Icons.Material.Filled.Close" Color="Color.Error" />
}
```
> When using MudBlazor, the standard data grid in module pages is `AbpMudExtensibleDataGrid`. You can replace `Component = typeof(CustomTableColumn)` exactly the same way as in Blazorise; the column system is shared across both UI libraries.
{{end}}
Navigate back to the `CustomizedUserManagement` class, and use `Component` property to specify the custom blazor component.
```csharp

39
docs/en/framework/ui/blazor/entity-action-extensions.md

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json
//[doc-seo]
{
@ -95,6 +102,8 @@ Here, the list of the properties that you use in the `EntityAction`.
#### Example
{{if BlazorUI == "Blazorise"}}
```csharp
var clickMeAction = new EntityAction()
{
@ -115,3 +124,33 @@ var clickMeAction = new EntityAction()
}
};
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```csharp
var clickMeAction = new EntityAction()
{
Text = "Click Me!",
Clicked = (data) =>
{
//TODO: Write your custom code
return Task.CompletedTask;
},
Color = MudBlazor.Color.Error,
Icon = MudBlazor.Icons.Material.Filled.PanTool,
ConfirmationMessage = (data) => "Are you sure you want to click to the action?",
Visible = (data) =>
{
//TODO: Write your custom visibility action
//var selectedUser = data.As<IdentityUserDto>();
return true;
}
};
```
> The MudBlazor variant uses `MudBlazor.Color` enum values (e.g. `Color.Primary`, `Color.Error`, `Color.Success`) for `Color`, and Material Icon constants (e.g. `Icons.Material.Filled.Edit`) for `Icon`. The `EntityAction` model itself is shared with Blazorise; only the values you put inside it change.
{{end}}

61
docs/en/framework/ui/blazor/error-handling.md

@ -10,7 +10,8 @@
````json
//[doc-params]
{
"UI": ["Blazor", "BlazorServer"]
"UI": ["Blazor", "BlazorServer"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -36,7 +37,9 @@ There are different type of `Exception` classes handled differently by the ABP.
**Example**
````csharp
{{if BlazorUI == "Blazorise"}}
````razor
@page "/"
@using Volo.Abp
@ -60,11 +63,41 @@ There are different type of `Exception` classes handled differently by the ABP.
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
@page "/"
@using Volo.Abp
<MudButton OnClick="TestException" Variant="Variant.Filled" Color="Color.Primary">Throw test exception</MudButton>
@code
{
private async Task TestException()
{
try
{
throw new UserFriendlyException("A user friendly error message!");
}
catch(UserFriendlyException ex)
{
await HandleErrorAsync(ex);
}
}
}
````
{{end}}
{{end}}
{{if UI == "Blazor"}}
**Example**
````csharp
{{if BlazorUI == "Blazorise"}}
````razor
@page "/"
@using Volo.Abp
@ -78,6 +111,28 @@ There are different type of `Exception` classes handled differently by the ABP.
}
}
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
@page "/"
@using Volo.Abp
<MudButton OnClick="TestException" Variant="Variant.Filled" Color="Color.Primary">Throw test exception</MudButton>
@code
{
private void TestException()
{
throw new UserFriendlyException("A user friendly error message!");
}
}
````
{{end}}
{{end}}
ABP automatically handle the exception and show an error message to the user:

100
docs/en/framework/ui/blazor/forms-validation.md

@ -1,12 +1,21 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json
//[doc-seo]
{
"Description": "Learn how to implement form validation in ABP Blazor UI using Blazorise's validation infrastructure with practical examples."
"Description": "Learn how to implement form validation in ABP Blazor UI using Blazorise or MudBlazor with practical examples."
}
```
# Blazor UI: Forms & Validation
{{if BlazorUI == "Blazorise"}}
ABP Blazor UI is based on the [Blazorise](https://blazorise.com/docs) and does not have a built-in form validation infrastructure. However, you can use the [Blazorise validation infrastructure](https://blazorise.com/docs/components/validation) to validate your forms.
## Sample
@ -44,4 +53,91 @@ _The example is provided by official Blazorise documentation._
}
```
> Check the [Blazorise documentation](https://blazorise.com/docs/components/validation) for more information and examples.
> Check the [Blazorise documentation](https://blazorise.com/docs/components/validation) for more information and examples.
{{end}}
{{if BlazorUI == "MudBlazor"}}
ABP Blazor UI built on top of [MudBlazor](https://mudblazor.com) uses MudBlazor's built-in form components and validation infrastructure. MudBlazor integrates with ASP.NET Core's `DataAnnotations` and supports custom validation through `IValidationRule`, `Func<T, IEnumerable<string>>`, or fluent validators.
## Sample
The most common pattern is wrapping inputs in a `<MudForm>` and binding the form's validation state through `IsValid`:
> Standard MudBlazor and ABP usings (`@using MudBlazor`, `@using Volo.Abp.MudBlazorUI`, etc.) come from the project's `_Imports.razor`. The example below only adds the additional usings needed for validation.
```razor
@using System.ComponentModel.DataAnnotations
<MudForm @ref="_form" @bind-IsValid="@_isValid" Model="@_model">
<MudTextField @bind-Value="_model.Name"
Label="Name"
Required="true"
RequiredError="Please enter the name." />
<MudTextField @bind-Value="_model.Email"
Label="Email"
Required="true"
Validation="@(new EmailAddressAttribute() { ErrorMessage = "Enter a valid email." })" />
<MudButton OnClick="@SubmitAsync"
Disabled="@(!_isValid)"
Variant="Variant.Filled"
Color="Color.Primary">
Submit
</MudButton>
</MudForm>
@code {
private MudForm _form;
private bool _isValid;
private SampleModel _model = new();
private async Task SubmitAsync()
{
await _form.Validate();
if (_isValid)
{
// ...
}
}
public class SampleModel
{
[Required]
public string Name { get; set; }
[Required, EmailAddress]
public string Email { get; set; }
}
}
```
### Inputs Used in CRUD Pages
ABP's MudBlazor CRUD pages (see `AbpMudCrudPageBase`) use a `<MudDialog>` containing a `<MudForm>` and standard MudBlazor inputs:
* `<MudTextField>` / `<MudTextField Lines="N">` for text and multi-line text
* `<MudSelect>` / `<MudSelectItem>` for dropdowns
* `<MudCheckBox>` for booleans
* `<MudDatePicker>` / `<MudTimePicker>` for date and time
* `<MudNumericField>` for numbers
The page base validates the entire form before calling the application service:
```csharp
protected override async Task OnCreatingEntityAsync()
{
await _form.Validate();
if (!_isValid)
{
return;
}
// ... call AppService
}
```
> Check the [MudBlazor documentation](https://mudblazor.com/components/form) for the full list of validation modes and the [MudBlazor inputs reference](https://mudblazor.com/components/textfield).
{{end}}

25
docs/en/framework/ui/blazor/overall.md

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json
//[doc-seo]
{
@ -95,6 +102,8 @@ Currently, three themes are **officially provided**:
There are a set of standard libraries that comes pre-installed and supported by all the themes:
{{if BlazorUI == "Blazorise"}}
* [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework.
* [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree.
* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library.
@ -106,6 +115,22 @@ These libraries are selected as the base libraries and available to the applicat
> Beginning from June, 2021, the Blazorise library has dual licenses; open source & commercial. Based on your yearly revenue, you may need to buy a commercial license. See [this post](https://blazorise.com/news/announcing-2022-blazorise-plans-and-pricing-updates) to learn more. The Blazorise license is bundled with ABP and commercial customers doesn't need to buy an extra Blazorise license.
{{end}}
{{if BlazorUI == "MudBlazor"}}
* [MudBlazor](https://mudblazor.com/) as the component library, providing a complete set of Material Design components built natively for Blazor (form controls, data grid, dialogs, snackbars, dates, etc.).
* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library.
* [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries.
These libraries are selected as the base libraries and available to the applications and modules.
The MudBlazor variant ships its own theming, dialog, snackbar and popover providers (see [Theming](theming.md)). The MudBlazor library is MIT-licensed and is bundled with ABP at no extra cost.
> Bootstrap is **not** required when using MudBlazor; MudBlazor brings its own layout and component styles.
{{end}}
### The Layout
The themes provide the layout. So, you have a responsive layout with the standard features already implemented. The screenshot below has taken from the layout of the [Basic Theme](basic-theme.md):

79
docs/en/framework/ui/blazor/page-header.md

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json
//[doc-seo]
{
@ -30,6 +37,8 @@ Breadcrumbs can be added using the `BreadcrumbItems` property.
**Example: Add Language Management to the breadcrumb items.**
{{if BlazorUI == "Blazorise"}}
Create a collection of `Volo.Abp.BlazoriseUI.BreadcrumbItem` objects and set the collection to the `BreadcrumbItems` parameter.
```csharp
@ -44,6 +53,31 @@ public partial class Index
}
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
Create a collection of `MudBlazor.BreadcrumbItem` objects and set the collection to the `BreadcrumbItems` parameter. The MudBlazor `BreadcrumbItem` constructor takes `(string text, string href, bool disabled = false, string icon = null)`.
```csharp
using MudBlazor;
public partial class Index
{
protected List<BreadcrumbItem> BreadcrumbItems { get; } = new();
protected override void OnInitialized()
{
BreadcrumbItems.Add(new BreadcrumbItem(
text: "Language Management",
href: null,
disabled: true));
}
}
```
{{end}}
Navigate back to the razor page.
```csharp
@ -57,19 +91,38 @@ The theme then renders the breadcrumb. An example render result can be:
* The Home icon is rendered by default. Set `BreadcrumbShowHome` to `false` to hide it.
* Breadcrumb items will be activated based on current navigation. Set `BreadcrumbShowCurrent` to `false` to disable it.
You can add as many items as you need. `BreadcrumbItem` constructor gets three parameters:
You can add as many items as you need.
{{if BlazorUI == "Blazorise"}}
The `Volo.Abp.BlazoriseUI.BreadcrumbItem` constructor gets three parameters:
* `text`: The text to show for the breadcrumb item.
* `url` (optional): A URL to navigate to, if the user clicks to the breadcrumb item.
* `icon` (optional): An icon class (like `fas fa-user-tie` for Font-Awesome) to show with the `text`.
{{end}}
{{if BlazorUI == "MudBlazor"}}
The `MudBlazor.BreadcrumbItem` constructor takes:
* `text`: The text to show for the breadcrumb item.
* `href`: A URL to navigate to (use `null` for the current page).
* `disabled` (optional): When `true`, the item is rendered as the current/non-clickable item.
* `icon` (optional): A Material icon (e.g. `Icons.Material.Filled.Language`).
{{end}}
## Page Toolbar
Page toolbar can be set using the `Toolbar` property.
**Example: Add a "New Item" toolbar item to the page toolbar.**
Create a `PageToolbar` object and define toolbar items using the `AddButton` extension method.
Create a `PageToolbar` object and define toolbar items using the `AddButton` extension method.
{{if BlazorUI == "Blazorise"}}
```csharp
public partial class Index
@ -87,6 +140,28 @@ public partial class Index
}
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```csharp
public partial class Index
{
protected PageToolbar Toolbar { get; } = new();
protected override void OnInitialized()
{
Toolbar.AddButton("New Item", () =>
{
//Write your click action here
return Task.CompletedTask;
}, icon: MudBlazor.Icons.Material.Filled.Add);
}
}
```
{{end}}
Navigate back to the razor page and set the `Toolbar` parameter.
```csharp

46
docs/en/framework/ui/blazor/page-layout.md

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json
//[doc-seo]
{
@ -36,6 +43,8 @@ Indicates current selected menu item name. Menu item name should match a unique
Menu item name can be set on runtime too.
{{if BlazorUI == "Blazorise"}}
```html
@inject PageLayout PageLayout
@ -49,6 +58,25 @@ Menu item name can be set on runtime too.
}
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```razor
@inject PageLayout PageLayout
<MudButton OnClick="SetCategoriesMenuAsSelected" Variant="Variant.Filled" Color="Color.Primary">Change Menu</MudButton>
@code{
protected void SetCategoriesMenuAsSelected()
{
PageLayout.MenuItemName = "MyProjectName.Categories";
}
}
```
{{end}}
![leptonx selected menu item](../../../images/leptonx-selected-menu-item-example.gif)
@ -57,6 +85,9 @@ Menu item name can be set on runtime too.
## BreadCrumbs
BreadCrumbItems are used to render breadcrumbs in the PageHeader.
{{if BlazorUI == "Blazorise"}}
```csharp
@inject PageLayout PageLayout
@ -65,6 +96,21 @@ BreadCrumbItems are used to render breadcrumbs in the PageHeader.
}
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```razor
@using MudBlazor
@inject PageLayout PageLayout
@{
PageLayout.BreadcrumbItems.Add(new BreadcrumbItem("My Page", "/my-page"));
}
```
{{end}}
## Toolbar
ToolbarItems are used to render action toolbar items in the PageHeader.

75
docs/en/framework/ui/blazor/page-toolbar-extensions.md

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json
//[doc-seo]
{
@ -27,6 +34,8 @@ We will use the [component override system](customization-overriding-components.
Here, the content of the overridden `SetToolbarItemsAsync` method.
{{if BlazorUI == "Blazorise"}}
```csharp
protected override async ValueTask SetToolbarItemsAsync()
{
@ -38,10 +47,31 @@ protected override async ValueTask SetToolbarItemsAsync()
}, "file-import", Blazorise.Color.Secondary);
}
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```csharp
protected override async ValueTask SetToolbarItemsAsync()
{
await base.SetToolbarItemsAsync();
Toolbar.AddButton("Import users from excel", () =>
{
//TODO: Write your custom code
return Task.CompletedTask;
}, MudBlazor.Icons.Material.Filled.FileUpload, MudBlazor.Color.Secondary);
}
```
{{end}}
> In order to use the `AddButton` extension method, you need to add a using statement for the `Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars` namespace.
Here, the entire content of the file.
{{if BlazorUI == "Blazorise"}}
```csharp
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars;
@ -67,6 +97,37 @@ namespace MyCompanyName.MyProjectName.Blazor.Pages.Identity
}
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```csharp
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity.Blazor.Pages.Identity;
namespace MyCompanyName.MyProjectName.Blazor.Pages.Identity
{
[ExposeServices(typeof(UserManagement))]
[Dependency(ReplaceServices = true)]
public class CustomizedUserManagement : UserManagement
{
protected override async ValueTask SetToolbarItemsAsync()
{
await base.SetToolbarItemsAsync();
Toolbar.AddButton("Import users from excel", () =>
{
//TODO: Write your custom code
return Task.CompletedTask;
}, MudBlazor.Icons.Material.Filled.FileUpload, MudBlazor.Color.Secondary);
}
}
}
```
{{end}}
When you run the application, you will see the button added next to the current button list. There are some other parameters of the `AddButton` method (for example, use `Order` to set the order of the button component relative to the other components).
## Advanced Use Cases
@ -83,9 +144,21 @@ For this example, we've created a `MyToolbarComponent` component under the `/Pag
`MyToolbarComponent.razor` content:
````csharp
{{if BlazorUI == "Blazorise"}}
````razor
<Button Color="Color.Dark">CLICK ME</Button>
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
<MudButton Variant="Variant.Filled" Color="Color.Dark">CLICK ME</MudButton>
````
{{end}}
We will leave the `MyToolbarComponent.razor.cs` file empty.
Then you can add the `MyToolbarComponent` to the user management page toolbar:

50
docs/en/framework/ui/blazor/theming.md

@ -10,7 +10,8 @@
````json
//[doc-params]
{
"UI": ["Blazor", "BlazorServer"]
"UI": ["Blazor", "BlazorServer"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -52,6 +53,8 @@ All the themes must depend on the [Volo.Abp.AspNetCore.Components.Server.Theming
{{end}}
{{if BlazorUI == "Blazorise"}}
* [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework.
* [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree.
* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library.
@ -61,6 +64,31 @@ These libraries are selected as the base libraries and available to the applicat
> Bootstrap's JavaScript part is not used since the Blazorise library already provides the necessary functionalities to the Bootstrap components in a native way.
{{end}}
{{if BlazorUI == "MudBlazor"}}
* [MudBlazor](https://mudblazor.com/) as the component library, providing a Material Design component set built natively for Blazor (form controls, data grid, dialogs, snackbars, dates, etc.).
* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library.
* [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries.
These libraries are selected as the base libraries and available to the applications and modules.
A theme using the MudBlazor variant must place the four MudBlazor providers in the layout root so dialogs, snackbars and popovers work everywhere:
```razor
<MudThemeProvider />
<MudDialogProvider />
<MudSnackbarProvider />
<MudPopoverProvider />
@Body
```
The provided themes (`Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme`, `Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme`, etc.) ship these providers as part of their layout templates.
{{end}}
### The Layout
All themes must define a layout for the application. The following image shows the user management page in the [Basic Theme](basic-theme.md) application layout:
@ -90,6 +118,8 @@ A theme is simply a Razor Class Library.
The easiest way of creating a new theme is adding [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/modules/basic-theme) module with source codes and customizing it.
{{if BlazorUI == "Blazorise"}}
{{if UI == "Blazor"}}
```bash
abp add-package Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme --with-source-code --add-to-solution-file
@ -102,6 +132,24 @@ abp add-package Volo.Abp.AspNetCore.Components.Server.BasicTheme --with-source-c
```
{{end}}
{{end}}
{{if BlazorUI == "MudBlazor"}}
{{if UI == "Blazor"}}
```bash
abp add-package Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme --with-source-code --add-to-solution-file
```
{{end}}
{{if UI == "BlazorServer"}}
```bash
abp add-package Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme --with-source-code --add-to-solution-file
```
{{end}}
{{end}}
### Global Styles / Scripts
A theme generally needs to add a global style to the page. ABP provides a system to manage the [Global Styles and Scripts](global-scripts-styles.md). A theme can implement the `IBundleContributor` to add global style or script files to the page.

3
docs/en/tutorials/book-store-with-abp-suite/part-05.md

@ -11,7 +11,8 @@
//[doc-params]
{
"UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"],
"DB": ["EF", "Mongo"]
"DB": ["EF", "Mongo"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````

80
docs/en/tutorials/book-store/part-02.md

@ -10,7 +10,8 @@
//[doc-params]
{
"UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp", "NG", "MAUIBlazor"],
"DB": ["EF","Mongo"]
"DB": ["EF","Mongo"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
````json
@ -549,6 +550,8 @@ When you click on the Books menu item under the Book Store parent, you will be r
### Book List
{{if BlazorUI == "Blazorise"}}
We will use the [Blazorise library](https://blazorise.com/) as the UI component kit. It is a very powerful library that supports major HTML/CSS frameworks, including Bootstrap.
ABP provides a generic base class - `AbpCrudPageBase<...>`, to create CRUD style pages. This base class is compatible with the `ICrudAppService` that was used to build the `IBookAppService`. So, we can inherit from the `AbpCrudPageBase` to automate the code behind for the standard CRUD stuff.
@ -624,6 +627,81 @@ Open the `Books.razor` and replace the content as the following:
While the code above is pretty easy to understand, you can check the Blazorise [Card](https://blazorise.com/docs/components/card/) and [DataGrid](https://blazorise.com/docs/extensions/datagrid/) documents to understand them better.
{{end}}
{{if BlazorUI == "MudBlazor"}}
We will use the [MudBlazor library](https://mudblazor.com/) as the UI component kit. It is a Material Design component library built natively for Blazor.
ABP provides a generic base class — `AbpMudCrudPageBase<...>`, to create CRUD style pages. This base class is compatible with the `ICrudAppService` that was used to build the `IBookAppService`. So, we can inherit from the `AbpMudCrudPageBase` to automate the code behind for the standard CRUD stuff.
Open the `Books.razor` and replace the content as the following:
````razor
@page "/books"
@using Volo.Abp.Application.Dtos
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@inherits AbpMudCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h4">@L["Books"]</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<AbpMudExtensibleDataGrid T="BookDto"
ServerData="OnDataGridReadAsync"
RowsPerPage="@PageSize">
<Columns>
<PropertyColumn Property="x => x.Name"
Title="@L["Name"]" />
<PropertyColumn Property="x => x.Type"
Title="@L["Type"]">
<CellTemplate>
@L[$"Enum:BookType.{context.Item.Type}"]
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.PublishDate"
Title="@L["PublishDate"]">
<CellTemplate>
@context.Item.PublishDate.ToShortDateString()
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.Price"
Title="@L["Price"]" />
<PropertyColumn Property="x => x.CreationTime"
Title="@L["CreationTime"]">
<CellTemplate>
@context.Item.CreationTime.ToLongDateString()
</CellTemplate>
</PropertyColumn>
</Columns>
</AbpMudExtensibleDataGrid>
</MudCardContent>
</MudCard>
@code
{
public Books() // Constructor
{
LocalizationResource = typeof(BookStoreResource);
}
}
````
> If you see some syntax errors, you can ignore them if your application is properly built and running. Visual Studio still has some bugs with Blazor.
* Inherited from `AbpMudCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>` which implements all the CRUD details for us.
* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base class.
* `LocalizationResource` is set to the `BookStoreResource` to localize the texts.
* `AbpMudExtensibleDataGrid` is the ABP-extended `MudDataGrid` that supports the [data table column extension system](../../framework/ui/blazor/data-table-column-extensions.md).
While the code above is pretty easy to understand, you can check the MudBlazor [Card](https://mudblazor.com/components/card) and [DataGrid](https://mudblazor.com/components/datagrid) documents to understand them better.
{{end}}
#### About the AbpCrudPageBase
We will continue benefitting from `AbpCrudPageBase` for the books page. You could just inject the `IBookAppService` and perform all the server side calls yourself (thanks to the [Dynamic C# HTTP API Client Proxy](../../framework/api-development/dynamic-csharp-clients.md) system of the ABP). We will do it manually for the authors page to demonstrate how to call the server side HTTP APIs in your Blazor applications.

306
docs/en/tutorials/book-store/part-03.md

@ -10,7 +10,8 @@
//[doc-params]
{
"UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"],
"DB": ["EF","Mongo"]
"DB": ["EF","Mongo"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -1094,6 +1095,8 @@ In this section, you will learn how to create a new modal dialog form to create
### Add a "New Button" Button
{{if BlazorUI == "Blazorise"}}
Open the `Books.razor` and replace the `<CardHeader>` section with the following code:
````xml
@ -1110,6 +1113,27 @@ Open the `Books.razor` and replace the `<CardHeader>` section with the following
</CardHeader>
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
Open the `Books.razor` and replace the `<MudCardHeader>` section with the following code:
````razor
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h4">@L["Books"]</MudText>
</CardHeaderContent>
<CardHeaderActions>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="OpenCreateDialogAsync">@L["NewBook"]</MudButton>
</CardHeaderActions>
</MudCardHeader>
````
{{end}}
This will change the card header by adding a "New book" button to the right side:
![blazor-add-book-button](./images/blazor-add-book-button-2.png)
@ -1118,6 +1142,8 @@ Now, we can add a modal that will be opened when we click the button.
### Book Creation Modal
{{if BlazorUI == "Blazorise"}}
Open the `Books.razor` and add the following code to the end of the page:
````xml
@ -1184,6 +1210,52 @@ This code requires a service; Inject the `AbpBlazorMessageLocalizerHelper<T>` at
* The form implements validation and the `AbpBlazorMessageLocalizerHelper` is used to simply localize the validation messages.
* The `CreateModal` object, `CloseCreateModalAsync` and `CreateEntityAsync` methods are defined by the base class. Check out the [Blazorise documentation](https://blazorise.com/docs/) if you want to understand the `Modal` and the other components.
{{end}}
{{if BlazorUI == "MudBlazor"}}
Open the `Books.razor` and add the following code to the end of the page:
````razor
<MudDialog @ref="_createDialog">
<TitleContent>
<MudText Typo="Typo.h6">@L["NewBook"]</MudText>
</TitleContent>
<DialogContent>
<MudForm @ref="@CreateFormRef" Model="@NewEntity">
<MudTextField @bind-Value="@NewEntity.Name"
Label="@L["Name"]"
For="@(() => NewEntity.Name)"
Required="true" />
<MudSelect T="BookType"
@bind-Value="@NewEntity.Type"
Label="@L["Type"]">
@foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<MudSelectItem Value="@bookTypeValue">@L[$"Enum:BookType.{(int)bookTypeValue}"]</MudSelectItem>
}
</MudSelect>
<MudDatePicker @bind-Date="@NewEntity.PublishDate"
Label="@L["PublishDate"]" />
<MudNumericField T="float"
@bind-Value="@NewEntity.Price"
Label="@L["Price"]" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="CloseCreateDialogAsync">@L["Cancel"]</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="CreateEntityAsync">@L["Save"]</MudButton>
</DialogActions>
</MudDialog>
````
* The form uses `[Required]`/DataAnnotations for validation; messages are localized via the same `AbpResource` localization system.
* The `_createDialog` field, `CloseCreateDialogAsync`, `CreateFormRef` and `CreateEntityAsync` are all defined in `AbpMudCrudPageBase`. Check the [MudBlazor documentation](https://mudblazor.com/components/dialog) if you want to understand the `MudDialog` and other components.
{{end}}
That's all. Run the application and try to add a new book:
![blazor-new-book-modal](./images/blazor-new-book-modal-2.png)
@ -1194,6 +1266,8 @@ Editing a book is similar to creating a new book.
### Actions Dropdown
{{if BlazorUI == "Blazorise"}}
Open the `Books.razor` and add the following `DataGridEntityActionsColumn` section inside the `DataGridColumns` as the first item:
````xml
@ -1212,12 +1286,38 @@ Open the `Books.razor` and add the following `DataGridEntityActionsColumn` secti
The `DataGridEntityActionsColumn` component is used to show an "Actions" dropdown for each row in the `DataGrid`. The `DataGridEntityActionsColumn` shows a **single button** instead of a dropdown if there is only one available action inside it:
{{end}}
{{if BlazorUI == "MudBlazor"}}
Open the `Books.razor` and add the following `MudDataGridEntityActionsColumn` as the first column inside the `<Columns>` section of the `AbpMudExtensibleDataGrid`:
````razor
<MudDataGridEntityActionsColumn T="BookDto" @ref="@EntityActionsColumn">
<CellTemplate>
<MudEntityActions T="BookDto" Context="entityContext">
<MudEntityAction T="BookDto"
Text="@L["Edit"]"
Clicked="() => OpenEditDialogAsync(entityContext)" />
</MudEntityActions>
</CellTemplate>
</MudDataGridEntityActionsColumn>
````
* `OpenEditDialogAsync` is defined in the base class which takes the entity (book) to edit.
The `MudDataGridEntityActionsColumn` component is used to show an "Actions" menu for each row in the data grid. It shows a **single icon button** when there is only one available action and a dropdown menu when there are multiple actions.
{{end}}
![blazor-edit-book-action](./images/blazor-edit-book-action-3.png)
### Edit Modal
We can now define a modal to edit the book. Add the following code to the end of the `Books.razor` page:
{{if BlazorUI == "Blazorise"}}
````xml
<Modal @ref="@EditModal">
<ModalBackdrop />
@ -1273,6 +1373,47 @@ We can now define a modal to edit the book. Add the following code to the end of
</Modal>
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
<MudDialog @ref="_editDialog">
<TitleContent>
<MudText Typo="Typo.h6">@EditingEntity.Name</MudText>
</TitleContent>
<DialogContent>
<MudForm @ref="@EditFormRef" Model="@EditingEntity">
<MudTextField @bind-Value="@EditingEntity.Name"
Label="@L["Name"]"
For="@(() => EditingEntity.Name)"
Required="true" />
<MudSelect T="BookType"
@bind-Value="@EditingEntity.Type"
Label="@L["Type"]">
@foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<MudSelectItem Value="@bookTypeValue">@L[$"Enum:BookType.{(int)bookTypeValue}"]</MudSelectItem>
}
</MudSelect>
<MudDatePicker @bind-Date="@EditingEntity.PublishDate"
Label="@L["PublishDate"]" />
<MudNumericField T="float"
@bind-Value="@EditingEntity.Price"
Label="@L["Price"]" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="CloseEditDialogAsync">@L["Cancel"]</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="UpdateEntityAsync">@L["Save"]</MudButton>
</DialogActions>
</MudDialog>
````
{{end}}
### Mapperly Configuration
The base `AbpCrudPageBase` uses the [object to object mapping](../../framework/infrastructure/object-to-object-mapping.md) system to convert an incoming `BookDto` object to a `CreateUpdateBookDto` object. So, we need to define the mapping.
@ -1304,7 +1445,11 @@ You can now run the application and try to edit a book.
## Deleting a Book
Open the `Books.razor` page and add the following `EntityAction` code under the "Edit" action inside `EntityActions`:
Open the `Books.razor` page and add the following entity action code under the "Edit" action.
{{if BlazorUI == "Blazorise"}}
Add the following `EntityAction` code under the "Edit" action inside `EntityActions`:
````xml
<EntityAction TItem="BookDto"
@ -1313,6 +1458,21 @@ Open the `Books.razor` page and add the following `EntityAction` code under the
ConfirmationMessage="() => GetDeleteConfirmationMessage(context)" />
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
Add the following `MudEntityAction` code under the "Edit" action inside `MudEntityActions`:
````razor
<MudEntityAction T="BookDto"
Text="@L["Delete"]"
Clicked="() => DeleteEntityAsync(entityContext)"
ConfirmationMessage="() => GetDeleteConfirmationMessage(entityContext)" />
````
{{end}}
* `DeleteEntityAsync` is defined in the base class that deletes the entity by performing a call to the server.
* `ConfirmationMessage` is a callback to show a confirmation message before executing the action.
* `GetDeleteConfirmationMessage` is defined in the base class. You can override this method (or pass another value to the `ConfirmationMessage` parameter) to customize the localization message.
@ -1327,6 +1487,8 @@ Run the application and try to delete a book.
Here's the complete code to create the book management CRUD page, that has been developed in the last two parts:
{{if BlazorUI == "Blazorise"}}
````xml
@page "/books"
@using Volo.Abp.Application.Dtos
@ -1521,3 +1683,143 @@ Here's the complete code to create the book management CRUD page, that has been
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
@page "/books"
@using Volo.Abp.Application.Dtos
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@inherits AbpMudCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h4">@L["Books"]</MudText>
</CardHeaderContent>
<CardHeaderActions>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="OpenCreateDialogAsync">@L["NewBook"]</MudButton>
</CardHeaderActions>
</MudCardHeader>
<MudCardContent>
<AbpMudExtensibleDataGrid T="BookDto"
ServerData="OnDataGridReadAsync"
RowsPerPage="@PageSize">
<Columns>
<MudDataGridEntityActionsColumn T="BookDto" @ref="@EntityActionsColumn">
<CellTemplate>
<MudEntityActions T="BookDto" Context="entityContext">
<MudEntityAction T="BookDto"
Text="@L["Edit"]"
Clicked="() => OpenEditDialogAsync(entityContext)" />
<MudEntityAction T="BookDto"
Text="@L["Delete"]"
Clicked="() => DeleteEntityAsync(entityContext)"
ConfirmationMessage="() => GetDeleteConfirmationMessage(entityContext)" />
</MudEntityActions>
</CellTemplate>
</MudDataGridEntityActionsColumn>
<PropertyColumn Property="x => x.Name" Title="@L["Name"]" />
<PropertyColumn Property="x => x.Type" Title="@L["Type"]">
<CellTemplate>
@L[$"Enum:BookType.{(int)context.Item.Type}"]
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.PublishDate" Title="@L["PublishDate"]">
<CellTemplate>
@context.Item.PublishDate.ToShortDateString()
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.Price" Title="@L["Price"]" />
<PropertyColumn Property="x => x.CreationTime" Title="@L["CreationTime"]">
<CellTemplate>
@context.Item.CreationTime.ToLongDateString()
</CellTemplate>
</PropertyColumn>
</Columns>
</AbpMudExtensibleDataGrid>
</MudCardContent>
</MudCard>
<MudDialog @ref="_createDialog">
<TitleContent>
<MudText Typo="Typo.h6">@L["NewBook"]</MudText>
</TitleContent>
<DialogContent>
<MudForm @ref="@CreateFormRef" Model="@NewEntity">
<MudTextField @bind-Value="@NewEntity.Name"
Label="@L["Name"]"
For="@(() => NewEntity.Name)"
Required="true" />
<MudSelect T="BookType"
@bind-Value="@NewEntity.Type"
Label="@L["Type"]">
@foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<MudSelectItem Value="@bookTypeValue">@L[$"Enum:BookType.{(int)bookTypeValue}"]</MudSelectItem>
}
</MudSelect>
<MudDatePicker @bind-Date="@NewEntity.PublishDate"
Label="@L["PublishDate"]" />
<MudNumericField T="float"
@bind-Value="@NewEntity.Price"
Label="@L["Price"]" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="CloseCreateDialogAsync">@L["Cancel"]</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="CreateEntityAsync">@L["Save"]</MudButton>
</DialogActions>
</MudDialog>
<MudDialog @ref="_editDialog">
<TitleContent>
<MudText Typo="Typo.h6">@EditingEntity.Name</MudText>
</TitleContent>
<DialogContent>
<MudForm @ref="@EditFormRef" Model="@EditingEntity">
<MudTextField @bind-Value="@EditingEntity.Name"
Label="@L["Name"]"
For="@(() => EditingEntity.Name)"
Required="true" />
<MudSelect T="BookType"
@bind-Value="@EditingEntity.Type"
Label="@L["Type"]">
@foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<MudSelectItem Value="@bookTypeValue">@L[$"Enum:BookType.{(int)bookTypeValue}"]</MudSelectItem>
}
</MudSelect>
<MudDatePicker @bind-Date="@EditingEntity.PublishDate"
Label="@L["PublishDate"]" />
<MudNumericField T="float"
@bind-Value="@EditingEntity.Price"
Label="@L["Price"]" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="CloseEditDialogAsync">@L["Cancel"]</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="UpdateEntityAsync">@L["Save"]</MudButton>
</DialogActions>
</MudDialog>
@code
{
public Books() // Constructor
{
LocalizationResource = typeof(BookStoreResource);
}
}
````
{{end}}
{{end}}

315
docs/en/tutorials/book-store/part-09.md

@ -10,7 +10,8 @@
//[doc-params]
{
"UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"],
"DB": ["EF","Mongo"]
"DB": ["EF","Mongo"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -837,6 +838,8 @@ That's all! This is a fully working CRUD page, you can create, edit and delete a
Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project with the following content:
{{if BlazorUI == "Blazorise"}}
````xml
@page "/authors"
@using Acme.BookStore.Authors
@ -1017,11 +1020,129 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B
</Modal>
````
* This code is similar to the `Books.razor`, except it doesn't inherit from the `AbpCrudPageBase`, but uses its own implementation.
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
@page "/authors"
@using Acme.BookStore.Authors
@using Acme.BookStore.Localization
@inherits BookStoreComponentBase
@inject IAuthorAppService AuthorAppService
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h4">@L["Authors"]</MudText>
</CardHeaderContent>
<CardHeaderActions>
@if (CanCreateAuthor)
{
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="OpenCreateAuthorDialogAsync">
@L["NewAuthor"]
</MudButton>
}
</CardHeaderActions>
</MudCardHeader>
<MudCardContent>
<MudDataGrid T="AuthorDto"
ServerData="OnDataGridReadAsync"
RowsPerPage="@PageSize">
<Columns>
<TemplateColumn T="AuthorDto" Title="@L["Actions"]" Sortable="false">
<CellTemplate>
<MudMenu Icon="@Icons.Material.Filled.MoreVert"
AriaLabel="@L["Actions"]"
Dense="true">
@if (CanEditAuthor)
{
<MudMenuItem OnClick="@(() => OpenEditAuthorDialogAsync(context.Item))">
@L["Edit"]
</MudMenuItem>
}
@if (CanDeleteAuthor)
{
<MudMenuItem OnClick="@(() => DeleteAuthorAsync(context.Item))">
@L["Delete"]
</MudMenuItem>
}
</MudMenu>
</CellTemplate>
</TemplateColumn>
<PropertyColumn Property="x => x.Name" Title="@L["Name"]" />
<PropertyColumn Property="x => x.BirthDate" Title="@L["BirthDate"]">
<CellTemplate>
@context.Item.BirthDate.ToShortDateString()
</CellTemplate>
</PropertyColumn>
</Columns>
</MudDataGrid>
</MudCardContent>
</MudCard>
<MudDialog @ref="CreateAuthorDialog">
<TitleContent>
<MudText Typo="Typo.h6">@L["NewAuthor"]</MudText>
</TitleContent>
<DialogContent>
<MudForm @ref="@CreateFormRef" Model="@NewAuthor">
<MudTextField @bind-Value="@NewAuthor.Name"
Label="@L["Name"]"
For="@(() => NewAuthor.Name)"
Required="true" />
<MudDatePicker @bind-Date="@NewAuthorBirthDate"
Label="@L["BirthDate"]" />
<MudTextField @bind-Value="@NewAuthor.ShortBio"
Label="@L["ShortBio"]"
Lines="5" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="CloseCreateAuthorDialogAsync">@L["Cancel"]</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="CreateAuthorAsync">
@L["Save"]
</MudButton>
</DialogActions>
</MudDialog>
<MudDialog @ref="EditAuthorDialog">
<TitleContent>
<MudText Typo="Typo.h6">@EditingAuthor.Name</MudText>
</TitleContent>
<DialogContent>
<MudForm @ref="@EditFormRef" Model="@EditingAuthor">
<MudTextField @bind-Value="@EditingAuthor.Name"
Label="@L["Name"]"
For="@(() => EditingAuthor.Name)"
Required="true" />
<MudDatePicker @bind-Date="@EditingAuthorBirthDate"
Label="@L["BirthDate"]" />
<MudTextField @bind-Value="@EditingAuthor.ShortBio"
Label="@L["ShortBio"]"
Lines="5" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="CloseEditAuthorDialogAsync">@L["Cancel"]</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="UpdateAuthorAsync">
@L["Save"]
</MudButton>
</DialogActions>
</MudDialog>
````
{{end}}
* This code is similar to the `Books.razor`, except it doesn't inherit from the `AbpCrudPageBase`/`AbpMudCrudPageBase`, but uses its own implementation.
* Injects the `IAuthorAppService` to consume the server side HTTP APIs from the UI. We can directly inject application service interfaces and use just like regular method calls by the help of [Dynamic C# HTTP API Client Proxy System](../../framework/api-development/dynamic-csharp-clients.md), which performs REST API calls for us. See the `Authors` class below to see the usage.
Create a new code behind file, `Authors.razor.cs`, under the `Pages` folder, with the following content:
{{if BlazorUI == "Blazorise"}}
````csharp
using System;
using System.Collections.Generic;
@ -1195,6 +1316,196 @@ public partial class Authors
}
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Acme.BookStore.Permissions;
using Microsoft.AspNetCore.Authorization;
using MudBlazor;
using Volo.Abp.Application.Dtos;
{{ if UI == "BlazorServer" }}namespace Acme.BookStore.Blazor.Pages;{{ else if UI == "MAUIBlazor" }}namespace Acme.BookStore.MauiBlazor.Pages;{{ else }}namespace Acme.BookStore.Blazor.Client.Pages;{{ end }}
public partial class Authors
{
private IReadOnlyList<AuthorDto> AuthorList { get; set; }
private int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount;
private int CurrentPage { get; set; }
private string CurrentSorting { get; set; }
private int TotalCount { get; set; }
private bool CanCreateAuthor { get; set; }
private bool CanEditAuthor { get; set; }
private bool CanDeleteAuthor { get; set; }
private CreateAuthorDto NewAuthor { get; set; }
private Guid EditingAuthorId { get; set; }
private UpdateAuthorDto EditingAuthor { get; set; }
private MudDialog CreateAuthorDialog { get; set; }
private MudDialog EditAuthorDialog { get; set; }
private MudForm CreateFormRef;
private MudForm EditFormRef;
// MudDatePicker requires nullable DateTime, while AuthorDto.BirthDate is non-nullable.
// Bind to a nullable wrapper and sync back to the DTO before saving.
private DateTime? NewAuthorBirthDate
{
get => NewAuthor?.BirthDate;
set { if (NewAuthor != null && value.HasValue) NewAuthor.BirthDate = value.Value; }
}
private DateTime? EditingAuthorBirthDate
{
get => EditingAuthor?.BirthDate;
set { if (EditingAuthor != null && value.HasValue) EditingAuthor.BirthDate = value.Value; }
}
public Authors()
{
NewAuthor = new CreateAuthorDto();
EditingAuthor = new UpdateAuthorDto();
}
protected override async Task OnInitializedAsync()
{
await SetPermissionsAsync();
await GetAuthorsAsync();
}
private async Task SetPermissionsAsync()
{
CanCreateAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Create);
CanEditAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Edit);
CanDeleteAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Delete);
}
private async Task GetAuthorsAsync()
{
var result = await AuthorAppService.GetListAsync(
new GetAuthorListDto
{
MaxResultCount = PageSize,
SkipCount = CurrentPage * PageSize,
Sorting = CurrentSorting
}
);
AuthorList = result.Items;
TotalCount = (int)result.TotalCount;
}
private async Task<GridData<AuthorDto>> OnDataGridReadAsync(GridState<AuthorDto> state)
{
CurrentSorting = state.SortDefinitions
.Select(s => s.SortBy + (s.Descending ? " DESC" : ""))
.Aggregate("", (a, b) => string.IsNullOrEmpty(a) ? b : a + "," + b);
CurrentPage = state.Page;
await GetAuthorsAsync();
return new GridData<AuthorDto> { Items = AuthorList, TotalItems = TotalCount };
}
private async Task OpenCreateAuthorDialogAsync()
{
NewAuthor = new CreateAuthorDto();
if (CreateFormRef != null) await CreateFormRef.ResetAsync();
await CreateAuthorDialog.ShowAsync();
}
private Task CloseCreateAuthorDialogAsync()
{
return CreateAuthorDialog.CloseAsync();
}
private async Task OpenEditAuthorDialogAsync(AuthorDto author)
{
EditingAuthorId = author.Id;
EditingAuthor = ObjectMapper.Map<AuthorDto, UpdateAuthorDto>(author);
if (EditFormRef != null) await EditFormRef.ResetAsync();
await EditAuthorDialog.ShowAsync();
}
private Task CloseEditAuthorDialogAsync()
{
return EditAuthorDialog.CloseAsync();
}
private async Task DeleteAuthorAsync(AuthorDto author)
{
try
{
var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name];
if (!await Message.Confirm(confirmMessage))
{
return;
}
await AuthorAppService.DeleteAsync(author.Id);
await GetAuthorsAsync();
}
catch(Exception ex)
{
await HandleErrorAsync(ex);
}
}
private async Task CreateAuthorAsync()
{
try
{
await CreateFormRef.Validate();
if (CreateFormRef.IsValid)
{
await AuthorAppService.CreateAsync(NewAuthor);
await GetAuthorsAsync();
await CreateAuthorDialog.CloseAsync();
}
}
catch(Exception ex)
{
await HandleErrorAsync(ex);
}
}
private async Task UpdateAuthorAsync()
{
try
{
await EditFormRef.Validate();
if (EditFormRef.IsValid)
{
await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
await GetAuthorsAsync();
await EditAuthorDialog.CloseAsync();
}
}
catch(Exception ex)
{
await HandleErrorAsync(ex);
}
}
}
````
{{end}}
This class typically defines the properties and methods used by the `Authors.razor` page.
### Object Mapping

115
docs/en/tutorials/book-store/part-10.md

@ -10,7 +10,8 @@
//[doc-params]
{
"UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"],
"DB": ["EF","Mongo"]
"DB": ["EF","Mongo"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -1115,7 +1116,9 @@ That's all. Just run the application and try to create or edit an author.
### The Book List
It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor` {{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor` {{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following `DataGridColumn` definition just after the `Name` (book name) column:
It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor` {{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor` {{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following column definition just after the `Name` (book name) column:
{{if BlazorUI == "Blazorise"}}
````xml
<DataGridColumn TItem="BookDto"
@ -1123,6 +1126,16 @@ It is very easy to show the *Author Name* in the book list. Open the `/Pages/Boo
Caption="@L["Author"]"></DataGridColumn>
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
<PropertyColumn Property="x => x.AuthorName" Title="@L["Author"]" />
````
{{end}}
When you run the application, you can see the *Author* column on the table:
![blazor-bookstore-book-list-with-authors](images/blazor-bookstore-book-list-with-authors-2.png)
@ -1147,6 +1160,8 @@ protected override async Task OnInitializedAsync()
* It is essential to call the `base.OnInitializedAsync()` since `AbpCrudPageBase` has some initialization code to be executed.
{{if BlazorUI == "Blazorise"}}
Override the `OpenCreateModalAsync` method and adding the following code:
````csharp
@ -1199,7 +1214,67 @@ The final `@code` block should be the following:
}
````
Finally, add the following `Field` definition into the `ModalBody` of the *Create* modal, as the first item, before the `Name` field:
{{end}}
{{if BlazorUI == "MudBlazor"}}
Override the `OpenCreateDialogAsync` method and adding the following code:
````csharp
protected override async Task OpenCreateDialogAsync()
{
if (!authorList.Any())
{
throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]);
}
await base.OpenCreateDialogAsync();
NewEntity.AuthorId = authorList.First().Id;
}
````
The final `@code` block should be the following:
````csharp
@code
{
//ADDED A NEW FIELD
IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();
public Books() // Constructor
{
LocalizationResource = typeof(BookStoreResource);
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Delete;
}
//GET AUTHORS ON INITIALIZATION
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
authorList = (await AppService.GetAuthorLookupAsync()).Items;
}
protected override async Task OpenCreateDialogAsync()
{
if (!authorList.Any())
{
throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]);
}
await base.OpenCreateDialogAsync();
NewEntity.AuthorId = authorList.First().Id;
}
}
````
{{end}}
Finally, add the following field definition into the *Create* modal/dialog, as the first item, before the `Name` field:
{{if BlazorUI == "Blazorise"}}
````xml
<Field>
@ -1215,6 +1290,21 @@ Finally, add the following `Field` definition into the `ModalBody` of the *Creat
</Field>
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
<MudSelect T="Guid" @bind-Value="@NewEntity.AuthorId" Label="@L["Author"]">
@foreach (var author in authorList)
{
<MudSelectItem Value="@author.Id">@author.Name</MudSelectItem>
}
</MudSelect>
````
{{end}}
This requires to add a new localization key to the `en.json` file:
````js
@ -1227,7 +1317,9 @@ You can run the application to see the *Author Selection* while creating a new b
### Edit Book Modal
Add the following `Field` definition into the `ModalBody` of the *Edit* modal, as the first item, before the `Name` field:
Add the following field definition into the *Edit* modal/dialog, as the first item, before the `Name` field:
{{if BlazorUI == "Blazorise"}}
````xml
<Field>
@ -1243,6 +1335,21 @@ Add the following `Field` definition into the `ModalBody` of the *Edit* modal, a
</Field>
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
<MudSelect T="Guid" @bind-Value="@EditingEntity.AuthorId" Label="@L["Author"]">
@foreach (var author in authorList)
{
<MudSelectItem Value="@author.Id">@author.Name</MudSelectItem>
}
</MudSelect>
````
{{end}}
That's all. We are reusing the `authorList` defined for the *Create* modal.
{{end}}

43
docs/en/tutorials/modular-crm/part-03.md

@ -10,7 +10,8 @@
````json
//[doc-params]
{
"UI": ["MVC", "BlazorWebApp", "NG"]
"UI": ["MVC", "BlazorWebApp", "NG"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -515,6 +516,8 @@ Open the `ModularCrm.Catalog` .NET solution in your IDE, and find the `Pages/Cat
Replace the `Index.razor` file with the following content:
{{if BlazorUI == "Blazorise"}}
````razor
@page "/catalog"
@using System.Collections.Generic
@ -547,6 +550,44 @@ Replace the `Index.razor` file with the following content:
}
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
@page "/catalog"
@using System.Collections.Generic
@using System.Threading.Tasks
@using ModularCrm.Catalog
@inject IProductAppService ProductAppService
<MudText Typo="Typo.h4">Products</MudText>
<MudCard>
<MudCardContent>
<MudList T="ProductDto">
@foreach (var product in Products)
{
<MudListItem T="ProductDto" Value="@product">
@product.Name <MudText Inline="true" Typo="Typo.caption">(stock: @product.StockCount)</MudText>
</MudListItem>
}
</MudList>
</MudCardContent>
</MudCard>
@code {
private List<ProductDto> Products { get; set; } = new();
protected override async Task OnInitializedAsync()
{
Products = await ProductAppService.GetListAsync();
}
}
````
{{end}}
Here, you inject `IProductAppService`, get all products in `OnInitializedAsync`, and then render the result in a simple list.
{{end}}

45
docs/en/tutorials/modular-crm/part-05.md

@ -10,7 +10,8 @@
````json
//[doc-params]
{
"UI": ["MVC", "BlazorWebApp", "NG"]
"UI": ["MVC", "BlazorWebApp", "NG"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -525,6 +526,8 @@ public class OrderingMenuContributor : IMenuContributor
Replace the `Index.razor` content in the `Pages/Ordering` folder of the `ModularCrm.Ordering.Blazor` project with the following code block:
{{if BlazorUI == "Blazorise"}}
````razor
@page "/ordering"
@using System.Collections.Generic
@ -559,6 +562,46 @@ Replace the `Index.razor` content in the `Pages/Ordering` folder of the `Modular
}
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
@page "/ordering"
@using System.Collections.Generic
@using System.Threading.Tasks
@using ModularCrm.Ordering
@inject IOrderAppService OrderAppService
<MudText Typo="Typo.h4">Orders</MudText>
<MudCard>
<MudCardContent>
<MudList T="OrderDto">
@foreach (var order in Orders)
{
<MudListItem T="OrderDto" Value="@order">
<strong>Customer:</strong> @order.CustomerName <br />
<strong>Product:</strong> @order.ProductId <br />
<strong>State:</strong> @order.State
</MudListItem>
}
</MudList>
</MudCardContent>
</MudCard>
@code {
private List<OrderDto> Orders { get; set; } = new();
protected override async Task OnInitializedAsync()
{
Orders = await OrderAppService.GetListAsync();
}
}
````
{{end}}
This page shows a list of orders on the UI. You haven't created a UI to create new orders, and we will not do it to keep this tutorial simple. If you want to learn how to create advanced UIs with ABP, please follow the [Book Store tutorial](../book-store/index.md).
### Editing the Menu Item

45
docs/en/tutorials/modular-crm/part-06.md

@ -10,7 +10,8 @@
````json
//[doc-params]
{
"UI": ["MVC", "BlazorWebApp", "NG"]
"UI": ["MVC", "BlazorWebApp", "NG"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -296,6 +297,8 @@ As you can see, we can see the product names instead of product IDs.
Open the `Index.razor` file, and change the `@order.ProductId` part to `@order.ProductName` to write the product name instead of the product ID. The final `Index.razor` content should be the following:
{{if BlazorUI == "Blazorise"}}
````razor
@page "/ordering"
@using System.Collections.Generic
@ -330,6 +333,46 @@ Open the `Index.razor` file, and change the `@order.ProductId` part to `@order.P
}
````
{{end}}
{{if BlazorUI == "MudBlazor"}}
````razor
@page "/ordering"
@using System.Collections.Generic
@using System.Threading.Tasks
@using ModularCrm.Ordering
@inject IOrderAppService OrderAppService
<MudText Typo="Typo.h4">Orders</MudText>
<MudCard>
<MudCardContent>
<MudList T="OrderDto">
@foreach (var order in Orders)
{
<MudListItem T="OrderDto" Value="@order">
<strong>Customer:</strong> @order.CustomerName <br />
<strong>Product:</strong> @order.ProductName <br />
<strong>State:</strong> @order.State
</MudListItem>
}
</MudList>
</MudCardContent>
</MudCard>
@code {
private List<OrderDto> Orders { get; set; } = new();
protected override async Task OnInitializedAsync()
{
Orders = await OrderAppService.GetListAsync();
}
}
````
{{end}}
That's all. Now, you can graph build the main application and run it in ABP Studio to see the result:
![abp-studio-browser-list-of-orders-with-product-name](images/abp-studio-browser-list-of-orders-with-product-name.png)

46
docs/en/tutorials/todo/layered/index.md

@ -11,7 +11,8 @@
//[doc-params]
{
"UI": ["MVC", "Blazor", "BlazorServer", "BlazorWebApp" ,"NG", "MAUIBlazor"],
"DB": ["EF", "Mongo"]
"DB": ["EF", "Mongo"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -637,6 +638,8 @@ See the *Dynamic C# Proxies & Auto API Controllers* section below to learn how w
Open the `Index.razor` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}} *TodoApp.Blazor.Client* {{else if UI=="BlazorServer"}} *TodoApp.Blazor* {{else if UI=="MAUIBlazor"}} *TodoApp.MauiBlazor* {{end}} project and replace the content with the following code block:
{{if BlazorUI == "Blazorise"}}
```xml
@page "/"
@inherits TodoAppComponentBase
@ -675,6 +678,47 @@ Open the `Index.razor` file in the `Pages` folder of the {{if UI=="Blazor" || UI
</div>
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```razor
@page "/"
@inherits TodoAppComponentBase
<MudContainer>
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">TODO LIST</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<!-- FORM FOR NEW TODO ITEMS -->
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudTextField @bind-Value="@NewTodoText" Placeholder="enter text..." Variant="Variant.Outlined" />
<MudButton OnClick="Create" Variant="Variant.Filled" Color="Color.Primary">Submit</MudButton>
</MudStack>
<!-- TODO ITEMS LIST -->
<MudList T="TodoItemDto" id="TodoList">
@foreach (var todoItem in TodoItems)
{
<MudListItem T="TodoItemDto" Value="@todoItem">
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="@(() => Delete(todoItem))" />
@todoItem.Text
</MudListItem>
}
</MudList>
</MudCardContent>
</MudCard>
</MudContainer>
```
{{end}}
### Index.razor.css
As the final touch, open the `Index.razor.css` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}}*TodoApp.Blazor.Client*{{else if UI=="BlazorServer"}} *TodoApp.Blazor* {{else if UI=="MAUIBlazor"}} *TodoApp.MauiBlazor* {{end}} project and add the following content:

46
docs/en/tutorials/todo/single-layer/index.md

@ -11,7 +11,8 @@
//[doc-params]
{
"UI": ["MVC", "Blazor", "BlazorServer", "NG"],
"DB": ["EF", "Mongo"]
"DB": ["EF", "Mongo"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -621,6 +622,8 @@ This class uses the {{if UI=="Blazor"}}`ITodoAppService`{{else}}`TodoAppService`
Open the `Index.razor` file in the {{if UI=="BlazorServer"}}`Components/Pages`{{else}}`Pages`{{end}} folder and replace the content with the following code block:
{{if BlazorUI == "Blazorise"}}
```xml
@page "/"
@inherits TodoAppComponentBase
@ -660,6 +663,47 @@ Open the `Index.razor` file in the {{if UI=="BlazorServer"}}`Components/Pages`{{
</div>
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```razor
@page "/"
@inherits TodoAppComponentBase
<MudContainer>
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">TODO LIST</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<!-- FORM FOR NEW TODO ITEMS -->
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudTextField @bind-Value="@NewTodoText" Placeholder="enter text..." Variant="Variant.Outlined" />
<MudButton OnClick="Create" Variant="Variant.Filled" Color="Color.Primary">Submit</MudButton>
</MudStack>
<!-- TODO ITEMS LIST -->
<MudList T="TodoItemDto" id="TodoList">
@foreach (var todoItem in TodoItems)
{
<MudListItem T="TodoItemDto" Value="@todoItem">
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="@(() => Delete(todoItem))" />
@todoItem.Text
</MudListItem>
}
</MudList>
</MudCardContent>
</MudCard>
</MudContainer>
```
{{end}}
### Index.razor.css
As the final touch, open the `Index.razor.css` file in the {{if UI=="BlazorServer"}}`Components/Pages`{{else}}`Pages`{{end}} folder and add the following code block at the end of the file:

4
docs/en/ui-themes/basic-theme/index.md

@ -19,6 +19,8 @@ See the [Theming document](../../framework/ui/mvc-razor-pages/theming.md) to lea
The Basic Theme has implementation for the following UI types:
- [MVC UI](../../framework/ui/mvc-razor-pages/basic-theme.md)
- [Blazor UI](../../framework/ui/blazor/basic-theme.md)
- [Blazor UI](../../framework/ui/blazor/basic-theme.md) — available in two Blazor UI library variants:
- **Blazorise** (default): `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.BasicTheme`
- **MudBlazor**: `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorBasicTheme`
- [Angular UI](../../framework/ui/angular/basic-theme.md)

9
docs/en/ui-themes/index.md

@ -33,6 +33,15 @@ See the following documents based on the UI type you are using:
- [Basic Theme - Blazor UI](../framework/ui/blazor/basic-theme.md)
- [Basic Theme - Angular UI](../framework/ui/angular/basic-theme.md)
## Blazor UI Library
When you create an ABP solution with a Blazor host (Blazor Server, Blazor WebAssembly or Blazor WebApp), you can additionally choose the underlying Blazor component library:
* **Blazorise** — the original ABP default, based on Bootstrap.
* **MudBlazor** — a Material-Design component library, available as an alternative variant. Each official theme has a MudBlazor version (e.g. `MudBlazorLeptonXTheme`, `MudBlazorLeptonXLiteTheme`, `MudBlazorBasicTheme`).
The choice is made at solution creation time via the `--blazor-ui-library` option (`abp new ... -bul mudblazor`) or in the ABP Studio new-solution wizard.
## See Also
* [Theming - MVC UI](../framework/ui/mvc-razor-pages/theming.md)

16
docs/en/ui-themes/lepton-x-lite/blazor.md

@ -10,7 +10,8 @@
````json
//[doc-params]
{
"UI": ["Blazor", "BlazorServer"]
"UI": ["Blazor", "BlazorServer"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
@ -20,6 +21,19 @@ LeptonX Lite has implementation for the ABP Blazor WebAssembly & Blazor Server.
> See the [Theming document](../../framework/ui/mvc-razor-pages/theming.md) to learn about themes.
{{if BlazorUI == "MudBlazor"}}
> **MudBlazor Variant** — When the `--blazor-ui-library mudblazor` option is used, the LeptonX Lite theme ships as a MudBlazor variant. Replace `LeptonXLiteTheme` with `MudBlazorLeptonXLiteTheme` everywhere in this document (package names, module type names and namespaces). The installation steps, layout customization API and override mechanism are the same; only the package and namespace prefix change. The component implementations use MudBlazor primitives (`MudAppBar`, `MudDrawer`, `MudNavLink`, etc.) instead of Blazorise components.
>
> Concrete package names you will see when using the MudBlazor variant:
>
> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXLiteTheme`
> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXLiteTheme.Bundling`
> * Module types: `Abp{...}MudBlazorLeptonXLiteThemeModule`, `Abp{...}MudBlazorLeptonXLiteThemeBundlingModule`
> * Layout namespace: `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXLiteTheme.Themes.MudBlazorLeptonXLite`
{{end}}
## Installation
This theme is **already installed** when you create a new solution using the startup templates. If you are using any other template, you can install this theme by following the steps below:

16
docs/en/ui-themes/lepton-x/blazor.md

@ -10,12 +10,26 @@
````json
//[doc-params]
{
"UI": ["Blazor", "BlazorServer"]
"UI": ["Blazor", "BlazorServer"],
"BlazorUI": ["Blazorise", "MudBlazor"]
}
````
LeptonX theme is implemented and ready to use with ABP. No custom implementation is needed for Blazor Server & WebAssembly.
{{if BlazorUI == "MudBlazor"}}
> **MudBlazor Variant** — When the `--blazor-ui-library mudblazor` option is used, the LeptonX theme ships as a MudBlazor variant. Replace `LeptonXTheme` with `MudBlazorLeptonXTheme` everywhere in this document (package names, module type names and namespaces). The installation steps, layout customization API and override mechanism are the same; only the package and namespace prefix change. Component implementations use MudBlazor primitives (`MudAppBar`, `MudDrawer`, `MudNavLink`, `MudMenu`, etc.) instead of Blazorise components.
>
> Concrete package names you will see when using the MudBlazor variant:
>
> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXTheme`
> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXTheme.Bundling`
> * Module types: `Abp{...}MudBlazorLeptonXThemeModule`, `Abp{...}MudBlazorLeptonXThemeBundlingModule`
> * Layout namespace: `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX`
{{end}}
## Installation
{{if UI == "Blazor"}}

8
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs

@ -9,5 +9,13 @@ namespace Volo.Docs.Documents
public string DisplayName { get; set; }
public Dictionary<string, string> Values { get; set; }
/// <summary>
/// Conditional visibility: this parameter is shown only when the keyed parameter's
/// current value is one of the listed values.
/// Example: "DependsOn": { "UI": [ "Blazor", "BlazorServer", "BlazorWebApp" ] }
/// When null or empty the parameter is always shown.
/// </summary>
public Dictionary<string, List<string>> DependsOn { get; set; }
}
}

3
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml

@ -504,8 +504,9 @@
for (var i = 0; i < count; ++i)
{
var parameter = Model.DocumentPreferences.Parameters[i];
var hiddenStyle = Model.IsParameterVisible(parameter) ? null : "display:none;";
<div class="@BuildParameterDivClass(i)">
<div class="@BuildParameterDivClass(i)" style="@hiddenStyle">
<div class="custom-input-group">
<span class="input-group-text" id="@("Section" + parameter.Name + "ComboboxAddonId")">@(parameter.DisplayName)</span>
<select class="doc-section-combobox form-select"

20
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs

@ -90,6 +90,23 @@ namespace Volo.Docs.Pages.Documents.Project
public DocumentRenderParameters UserPreferences { get; set; } = new DocumentRenderParameters();
public virtual bool IsParameterVisible(DocumentParameterDto parameter)
{
if (parameter.DependsOn == null || parameter.DependsOn.Count == 0)
{
return true;
}
var renderedKeys = DocumentPreferences?.Parameters?.Select(p => p.Name).ToHashSet() ?? new HashSet<string>();
// A rule passes if it's malformed/irrelevant (fail-open) OR the current value matches the allow-list.
return parameter.DependsOn.All(rule =>
rule.Value == null || !renderedKeys.Contains(rule.Key) // skip malformed / unknown-key rules
|| (rule.Value.Count > 0
&& UserPreferences.TryGetValue(rule.Key, out var current)
&& rule.Value.Contains(current)));
}
public List<string> AlternativeOptionLinkQueries { get; set; } = new List<string>();
public bool FullSearchEnabled { get; set; }
@ -827,7 +844,8 @@ namespace Volo.Docs.Pages.Documents.Project
{
Name = parameter.Name,
DisplayName = parameter.DisplayName,
Values = new Dictionary<string, string>()
Values = new Dictionary<string, string>(),
DependsOn = parameter.DependsOn
};
foreach (var value in parameter.Values)

Loading…
Cancel
Save