The Razor template is a standard C# class, so you can freely use the functions of C#, such as `dependency injection`, using `LINQ`, custom methods, and even using `Repository`.
> The Razor engine compiles template content into a fully-trusted .NET assembly via Roslyn and executes it in the host process, so editing a Razor template at runtime is functionally equivalent to executing arbitrary server-side code. `RazorTemplateRenderingEngine.IsSandboxed` is therefore `false`, and the [Text Template Management](../../../modules/text-template-management.md) module requires the `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` permission (in addition to `EditContents`) before allowing such templates to be edited via its UI. Grant the related permission only to fully trusted developers/operators. If you need a sandboxed engine for content editors, consider [Scriban](scriban.md), which is configured to honor Scriban's [safe runtime boundaries](https://github.com/scriban/scriban/blob/master/site/docs/runtime/safe-runtime.md) by default.
Scriban's [safe runtime](https://github.com/scriban/scriban/blob/master/site/docs/runtime/safe-runtime.md) builds the practical sandbox out of four boundaries: which globals you expose through `ScriptObject`, which .NET members you allow through the member filter, whether you configure `TemplateContext.TemplateLoader` for `include`, and which `TemplateContext` execution limits you enable. ABP's `ScribanTemplateRenderingEngine` is configured to honor these boundaries by default:
| Boundary | ABP default |
|----------|-------------|
| Globals exposed | Only the `globalContext` (`Dictionary<string,object>`) entries, the `model` you pass to `RenderAsync`, and the `L` localization helper. |
| .NET member access | `TemplateContext.MemberFilter` is set to `IsMemberAllowed`, an allowlist that exposes public properties only. Methods, fields, events, and `object`-level members (`GetType`, `ToString`, ...) are not reachable, which closes reflection-based escape paths such as `{{ model.GetType.Assembly.GetType "..." }}`. |
| `TemplateLoader` | Not configured. `include` directives have no template loader and cannot read templates from disk or other sources unless you explicitly wire one up. |
| Execution limits | Scriban's defaults (`LoopLimit = 1000`, `RecursiveLimit = 100`, `LimitToString = 1 MB`, `RegexTimeOut = 10s`). Override `CreateScribanTemplateContext` to tighten these for your own scenarios. |
The recommended way to expose data to a Scriban template is via `ScriptObject` or `IDictionary<string, object>` — the keys you put there are exactly what the template can see. When you pass a .NET object as `model`, the `MemberFilter` ensures only properties are exposed, but the safest pattern is to pre-build a dictionary or `ScriptObject` so the surface is fully under your control:
````csharp
await _templateRenderer.RenderAsync(
"MyTemplate",
model: new Dictionary<string,object>
{
{ "name", user.Name },
{ "email", user.Email }
});
````
If you must pass a .NET object whose methods/fields the template needs to read, override `ScribanTemplateRenderingEngine.IsMemberAllowed` to relax the filter. Only do so when the model objects are trusted and do not carry secrets, since methods and reflection entry points become reachable to whoever can edit the template content.
## Installation
It is suggested to use the [ABP CLI](../../../cli) to install this package.
@ -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:
"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
<SubmitButtonClicked="@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.
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.
@ -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:
> 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.
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.
> 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.
@ -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.
"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,89 @@ _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 accepts a `ValidationAttribute` (e.g. `[Required]`, `[EmailAddress]` from ASP.NET Core's `DataAnnotations`) on the input's `Validation` parameter, plus custom `Func<T, string>` / `Func<T, IEnumerable<string>>` delegates. FluentValidation can be plugged in the same way.
## 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.
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
`AbpMudCrudPageBase.CreateEntityAsync` and `UpdateEntityAsync` validate the form for you (`CreateFormRef.Validate()` / `EditFormRef.Validate()`) and only call the corresponding hook when the form is valid. To inject custom logic before the application service call, override `OnCreatingEntityAsync` / `OnUpdatingEntityAsync` (do **not** re-validate inside the override):
```csharp
protected override Task OnCreatingEntityAsync()
{
// mutate NewEntity here if needed
return base.OnCreatingEntityAsync();
}
```
> 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).
@ -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):
@ -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)`.
> 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;
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
@ -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.
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.
@ -164,6 +164,22 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do
See the `TextTemplateManagementPermissions` class members for all permissions defined for this module.
The module exposes two edit-time permissions with different risk levels:
| Permission | Required to edit | Default grant |
|------------|------------------|---------------|
| `TextTemplateManagement.TextTemplates.EditContents` | Templates rendered by a sandboxed engine (e.g. Scriban). Editing such templates is safe for content editors because the engine cannot execute arbitrary .NET code. | Granted to roles that need to edit template text. |
| `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` | Templates rendered by a **non-sandboxed** engine (e.g. Razor). Editing such templates is functionally equivalent to granting server-side code execution because the engine compiles the content into a .NET assembly that runs with the host process's privileges. | **Not granted to any role by default**, including `admin`. Must be granted explicitly. |
Whether a template is sandboxed is determined by `ITemplateRenderingEngine.IsSandboxed` on the engine that renders it. Editing a non-sandboxed template requires **both**`EditContents` and `EditNonSandboxedContents`.
The Text Template Management UI surfaces this distinction:
- A warning banner is rendered above the editor for non-sandboxed templates.
- The save and restore buttons are disabled when the current user lacks `EditNonSandboxedContents` for a non-sandboxed template.
> Treat `EditNonSandboxedContents` as equivalent to granting shell access to the application server. Only assign it to fully trusted developers or operators.
@ -133,4 +133,28 @@ See the [Add failure retry policy to InboxProcessor](https://github.com/abpframe
Starting from **ABP 10.0**, the [`HideErrors`](../../framework/fundamentals/caching#Available-Options) option of `AbpDistributedCacheOptions` is **disabled by default in the development environment**.
By default, ABP hides and logs cache server errors to keep the application running even when the cache is unavailable.
However, in the **development environment**, errors are no longer hidden so that developers can immediately detect and fix **any cache server issues** (such as connection, configuration, or runtime errors).
However, in the **development environment**, errors are no longer hidden so that developers can immediately detect and fix **any cache server issues** (such as connection, configuration, or runtime errors).
### Angular `LOGO_APP_NAME_TOKEN` May Need Explicit Provider in Module-based Apps
In ABP v10, the logo/app-name binding moved to the `provideLogo(withEnvironmentOptions(...))` API from `@abp/ng.theme.shared`.
For standalone Angular applications, configuring `provideLogo(withEnvironmentOptions(environment))` in `app.config.ts` is enough.
For NgModule-based applications, some setups may still show the literal `ProjectName` text (the default value of `LOGO_APP_NAME_TOKEN`) instead of `environment.application.name`. In that case, provide the token explicitly in your module providers:
```ts
import { LOGO_APP_NAME_TOKEN } from '@abp/ng.theme.shared';
import { environment } from '../environments/environment';
For logo customization/replacement details, see the [Component Replacement](../../framework/ui/angular/component-replacement.md#how-to-replace-logocomponent) documentation.
> See [#25235](https://github.com/abpframework/abp/pull/25235) for details.
### Text Template Rendering Engine — `IsSandboxed` Marker
**Who is affected**
- Custom rendering engines that implement `Volo.Abp.TextTemplating.ITemplateRenderingEngine` directly (not deriving from `TemplateRenderingEngineBase`).
- Modules and applications that surface template editing to non-developer users (e.g. the Text Template Management module).
**What changed**
- `ITemplateRenderingEngine` exposes a new required property:
```csharp
bool IsSandboxed { get; }
```
Sandboxed engines (e.g. Scriban) interpret templates as a restricted DSL without .NET interop. Non-sandboxed engines (e.g. Razor) compile templates into fully-trusted .NET code that runs with the same privileges as the host process.
- `TemplateRenderingEngineBase` provides a virtual default of `false` (secure-by-default): engines that derive from the base class and don't override the property are treated as non-sandboxed.
- `RazorTemplateRenderingEngine` declares `IsSandboxed => false` (compiles to .NET assembly via Roslyn).
- `ScribanTemplateRenderingEngine` declares `IsSandboxed => true` and now sets Scriban's `TemplateContext.MemberFilter` so only public properties on imported objects are exposed; methods, fields, events and reflection entry points (`GetType`, `Assembly`, ...) are no longer reachable from Scriban templates.
**What to do**
- If your application registers a custom engine by implementing `ITemplateRenderingEngine` directly (without deriving from `TemplateRenderingEngineBase`), add the property:
```csharp
public bool IsSandboxed => false; // or true if your engine cannot execute host code
```
- If your engine derives from `TemplateRenderingEngineBase`, no action is required for compilation; however, override `IsSandboxed => true` if your engine is genuinely sandboxed so callers (such as the Text Template Management module) treat its templates as safe to edit by non-developer users.
- Scriban templates that invoke methods on the model (e.g. `{%{{{ model.SomeMethod }}}%}`) or read fields stop working because the engine now whitelists public properties only. Templates that access only properties (the typical Scriban usage) are unaffected. To restore the previous behavior in custom hosts, derive from `ScribanTemplateRenderingEngine` and override `IsMemberAllowed` to allow methods or fields:
Only do this when the model objects are trusted and do not carry secrets, since the previous behavior exposed reflection entry points (`GetType`, `Assembly`, ...) on imported .NET objects.
> See [#25399](https://github.com/abpframework/abp/pull/25399) for details.
### Text Template Management — `EditContents` Permission Split (security)
**Who is affected**
- Applications using the Text Template Management module that have granted the `TextTemplateManagement.TextTemplates.EditContents` permission to roles that are not fully trusted server administrators/developers.
- In particular, applications that use Razor templates (default for solutions referencing `Volo.Abp.TextTemplating.Razor`) and have any non-developer role with `EditContents`.
**What changed**
- A new permission `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` has been added.
- Editing a template whose rendering engine is **non-sandboxed** (i.e. `ITemplateRenderingEngine.IsSandboxed == false`, e.g. Razor) now requires **both** permissions: `EditContents` and `EditNonSandboxedContents`.
- The new permission is **not granted to any role by default**, including the `admin` role. The previously implicit assumption — that `EditContents` was enough to edit Razor templates — has been corrected: editing such templates is functionally equivalent to granting server-side code execution and is now gated by an explicit permission whose name communicates that risk.
- The Text Template Management UI (MVC, Blazor) renders a security warning banner when the current template's engine is non-sandboxed, and disables the save/restore buttons when the current user lacks the new permission.
**What to do**
After upgrading, audit which roles currently hold `EditContents` and decide which of them should also be granted `EditNonSandboxedContents`:
1. Roles that should only edit sandboxed templates (e.g. Scriban, Liquid, plain HTML) need no further action — they keep editing those templates.
2. Roles that need to continue editing Razor templates must be granted `EditNonSandboxedContents` explicitly via the Permission Management page.
3. If — and only if — you have reviewed your role assignments and confirmed that every role currently holding `EditContents` is trusted to execute server-side code through Razor templates, you may seed the new permission for those roles via your `IDataSeedContributor`:
```csharp
var grants = await _permissionGrantRepository.GetListAsync(
> Do not automate this seeding for arbitrary tenants/roles without first reviewing the current grants — auto-restoring the previously-implicit elevated trust would defeat the security improvement. The recommended path is to grant the new permission only to specific developer/operator roles via the Permission Management UI.
If your application's data seeder grants all permissions in the `TextTemplateManagement` group to a role (e.g. the `admin` role), that role will automatically receive `EditNonSandboxedContents` on first run after upgrade. Audit your seeder if you want stricter defaults.
> See the [Razor Integration](../../framework/infrastructure/text-templating/razor.md) document and [#25399](https://github.com/abpframework/abp/pull/25399) for details.
@ -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:
> 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.
* This page uses the standard MudBlazor `MudDataGrid` with `<PropertyColumn>` definitions. ABP also ships an `AbpMudExtensibleDataGrid` that integrates with the [data table column extension system](../../framework/ui/blazor/data-table-column-extensions.md) when you need to extend module pages.
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.
@ -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,57 @@ 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:
* 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.
* `MudDialog.Options` widens the dialog (`MaxWidth.Medium` + `FullWidth`) so the form fields are not cramped.
* `MudStack` with `Spacing="3"` keeps the inputs visually separated; without it MudBlazor inputs render flush against each other.
* `MudDatePicker.@bind-Date` requires a nullable `DateTime?`. If your DTO uses non-nullable `DateTime`, change it to `DateTime?` (`public DateTime? PublishDate { get; set; }`) when using the MudBlazor variant.
{{end}}
That's all. Run the application and try to add a new book:
@ -1194,6 +1271,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 +1291,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 `TemplateColumn` as the first column inside the `<Columns>` section of the `MudDataGrid`:
* `OpenEditDialogAsync` is defined in the base class which takes the entity (book) to edit.
This renders an "Actions" dropdown menu (`MudMenu`) for each row in the data grid. We will add the **Delete** menu item later in the *Deleting a Book* section.
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 +1452,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
<EntityActionTItem="BookDto"
@ -1313,6 +1465,25 @@ Open the `Books.razor` page and add the following `EntityAction` code under the
Add the following `MudMenuItem` after the "Edit" item inside the actions `MudMenu`:
````razor
<MudMenuItemOnClick="@(async()=> {
if (await Message.Confirm(GetDeleteConfirmationMessage(context.Item)))
{
await DeleteEntityAsync(context.Item);
}
})">
@L["Delete"]
</MudMenuItem>
````
{{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 +1498,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 +1694,142 @@ Here's the complete code to create the book management CRUD page, that has been
@ -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,133 @@ 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.
* 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 +1320,197 @@ 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 }}
@ -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
<DataGridColumnTItem="BookDto"
@ -1123,6 +1126,16 @@ It is very easy to show the *Author Name* in the book list. Open the `/Pages/Boo
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).
@ -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
@ -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
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:
@ -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`{{
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:
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.
@ -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:
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:
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:
> * Layout components live under `Volo.Abp.AspNetCore.Components.{Web,Server,WebAssembly}.MudBlazorLeptonXTheme.Components.ApplicationLayout` (with `SideMenu`, `TopMenu`, and `PublicWebsiteLayout` sub-namespaces).