Browse Source

Merge branch 'dev' into issue-codemirror

pull/25358/head
Zeynep Gizem Fırat 5 months ago
parent
commit
60665f65bc
  1. 2
      .claude/settings.local.json
  2. 16
      .github/scripts/CheckDocsSyntax/CheckDocsSyntax.csproj
  3. 347
      .github/scripts/CheckDocsSyntax/Program.cs
  4. 5
      .github/workflows/angular.yml
  5. 20
      .github/workflows/auto-pr.yml
  6. 33
      .github/workflows/build-and-test.yml
  7. 22
      .github/workflows/cancel-workflow.yml
  8. 221
      .github/workflows/check-docs-syntax.yml
  9. 4
      .github/workflows/codeql-analysis.yml
  10. 7
      .github/workflows/image-compression.yml
  11. 129
      Directory.Packages.props
  12. 43
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  13. 4
      common.props
  14. 72
      docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/POST.md
  15. BIN
      docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/cover-image.png
  16. BIN
      docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/upgrade-abp-packages.png
  17. 214
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/POST.md
  18. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/account-settings.png
  19. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/cover-image.png
  20. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email.png
  21. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email2.png
  22. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-identity.png
  23. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-index.png
  24. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-studio.png
  25. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/studio-switch-to-preview.png
  26. 89
      docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/POST.md
  27. BIN
      docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/cover-image.png
  28. BIN
      docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/upgrade-abp-packages.png
  29. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/abp-studio.png
  30. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/discovery.png
  31. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/job-feed.png
  32. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/negotiation.png
  33. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-dark.png
  34. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-light.png
  35. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-bottom-tab-menu.png
  36. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-drawer-menu.png
  37. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-old-menu.png
  38. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-1.png
  39. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-2.png
  40. BIN
      docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-3.png
  41. 165
      docs/en/Community-Articles/13-05-2026-new-react-native/post.md
  42. BIN
      docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-app-screenshot.png
  43. BIN
      docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-ui-modern-template-demo.gif
  44. BIN
      docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-studio-project-creation-react.png
  45. BIN
      docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/cover.png
  46. BIN
      docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/react-ui-and-admin-console.png
  47. 116
      docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/post.md
  48. 254
      docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/Post.md
  49. BIN
      docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/cover.png
  50. BIN
      docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic1.jpg
  51. BIN
      docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic2.png
  52. BIN
      docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-ai-review.gif
  53. BIN
      docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-analyze-engine.png
  54. BIN
      docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-code-generation.gif
  55. 244
      docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-ai-announcement.md
  56. BIN
      docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-new-design.png
  57. BIN
      docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/community-talk-cover-image.png
  58. BIN
      docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/cover.png
  59. BIN
      docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-ai-coding-assistant.jpg
  60. BIN
      docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-solution-runner-and-monitor.png
  61. BIN
      docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-ai-review.gif
  62. BIN
      docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-analyze-engine.png
  63. BIN
      docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-code-generation.gif
  64. 117
      docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-vibe-architecting-en.md
  65. 4
      docs/en/cli/differences-between-old-and-new-cli.md
  66. 981
      docs/en/cli/index.md
  67. 66
      docs/en/docs-nav.json
  68. 11
      docs/en/docs-params.json
  69. 4
      docs/en/framework/infrastructure/background-jobs/index.md
  70. 4
      docs/en/framework/infrastructure/background-workers/index.md
  71. 68
      docs/en/framework/infrastructure/image-manipulation.md
  72. 2
      docs/en/framework/infrastructure/text-templating/razor.md
  73. 25
      docs/en/framework/infrastructure/text-templating/scriban.md
  74. 15
      docs/en/framework/ui/blazor/basic-theme.md
  75. 63
      docs/en/framework/ui/blazor/components/submit-button.md
  76. 78
      docs/en/framework/ui/blazor/customization-overriding-components.md
  77. 30
      docs/en/framework/ui/blazor/data-table-column-extensions.md
  78. 39
      docs/en/framework/ui/blazor/entity-action-extensions.md
  79. 61
      docs/en/framework/ui/blazor/error-handling.md
  80. 98
      docs/en/framework/ui/blazor/forms-validation.md
  81. 25
      docs/en/framework/ui/blazor/overall.md
  82. 79
      docs/en/framework/ui/blazor/page-header.md
  83. 46
      docs/en/framework/ui/blazor/page-layout.md
  84. 75
      docs/en/framework/ui/blazor/page-toolbar-extensions.md
  85. 50
      docs/en/framework/ui/blazor/theming.md
  86. 3
      docs/en/framework/ui/index.md
  87. 46
      docs/en/framework/ui/mvc-razor-pages/overall.md
  88. 140
      docs/en/framework/ui/react-native/index.md
  89. 159
      docs/en/framework/ui/react-native/styling-with-nativewind.md
  90. 191
      docs/en/framework/ui/react/admin-console.md
  91. 183
      docs/en/framework/ui/react/authorization.md
  92. 184
      docs/en/framework/ui/react/components/index.md
  93. 208
      docs/en/framework/ui/react/customization.md
  94. 121
      docs/en/framework/ui/react/environment-variables.md
  95. 213
      docs/en/framework/ui/react/http-requests.md
  96. 142
      docs/en/framework/ui/react/index.md
  97. 158
      docs/en/framework/ui/react/localization.md
  98. 171
      docs/en/framework/ui/react/permission-management.md
  99. 150
      docs/en/framework/ui/react/unit-testing.md
  100. BIN
      docs/en/get-started/images/abp-studio-mobile-sample.gif

2
.claude/settings.local.json

@ -3,6 +3,8 @@
"allow": [
"Bash(yarn nx g:*)",
"Bash(npx vitest:*)",
"Bash(xargs:*)",
"Bash(dotnet build:*)",
"Bash(git show:*)"
]
}

16
.github/scripts/CheckDocsSyntax/CheckDocsSyntax.csproj

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Volo.Abp.Docs.SyntaxCheck</RootNamespace>
<AssemblyName>CheckDocsSyntax</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Scriban" />
</ItemGroup>
</Project>

347
.github/scripts/CheckDocsSyntax/Program.cs

@ -0,0 +1,347 @@
using System.Text.Json;
using Scriban;
using Scriban.Runtime;
using Scriban.Syntax;
// Validates the Scriban template syntax embedded in `docs/en/` Markdown files.
//
// For each input file we run `Template.Parse` and a strict-mode render with the
// same parameter set the docs renderer injects at runtime (each docs-params.json
// `<name>` and its `<name>_Value` companion, plus Document_Language_Code,
// Document_Version and Release_Status). StrictVariables is enabled on purpose so
// references that would otherwise be silently rendered as empty strings surface
// as build failures here.
//
// Known limitations:
// - Partial template inlining is not executed: partial bodies are loaded from
// external storage at render time, so they cannot be resolved in CI. Files
// under `docs/en/` currently have no `//[doc-template]` references; if one is
// added later, errors inside the partial body must be reviewed manually.
// - Cookie- and query-string-driven parameter overrides are not injected, but
// their keys still resolve to empty strings because they layer on top of the
// same `<name>` / `<name>_Value` entries that are already injected.
namespace Volo.Abp.Docs.SyntaxCheck;
internal static class Program
{
private const string DefaultDocsRoot = "docs/en";
private const string DocsParamsFileName = "docs-params.json";
private static readonly string[] BuiltInVariables =
{
"Document_Language_Code",
"Document_Version",
"Release_Status"
};
public static int Main(string[] args)
{
var useGitHubAnnotations = Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true";
var inputPaths = args.Length == 0
? new[] { DefaultDocsRoot }
: args;
var files = new List<string>();
foreach (var path in inputPaths)
{
if (File.Exists(path))
{
if (path.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
{
files.Add(Path.GetFullPath(path));
}
}
else if (Directory.Exists(path))
{
foreach (var file in Directory.EnumerateFiles(path, "*.md", SearchOption.AllDirectories))
{
files.Add(Path.GetFullPath(file));
}
}
else
{
Console.Error.WriteLine($"WARN: path does not exist: {path}");
}
}
if (files.Count == 0)
{
Console.WriteLine("No markdown files to check.");
return 0;
}
Dictionary<string, string> renderParameters;
try
{
renderParameters = BuildRenderParameters(files);
}
catch (Exception ex)
{
Console.Error.WriteLine($"ERROR: failed to load docs-params: {ex.Message}");
if (useGitHubAnnotations)
{
Console.WriteLine($"::error::docs-params: {EscapeAnnotation(ex.Message)}");
}
return 1;
}
var errorCount = 0;
var warningCount = 0;
var fileIssueCount = 0;
var repoRoot = TryFindRepoRoot(Directory.GetCurrentDirectory());
foreach (var file in files)
{
var fileIssues = CheckFile(file, renderParameters);
if (fileIssues.Count == 0)
{
continue;
}
fileIssueCount++;
foreach (var issue in fileIssues)
{
if (issue.Severity == IssueSeverity.Error)
{
errorCount++;
}
else
{
warningCount++;
}
var displayPath = repoRoot != null
? Path.GetRelativePath(repoRoot, file)
: file;
var severityLabel = issue.Severity == IssueSeverity.Error ? "error" : "warning";
Console.WriteLine(
$"{displayPath}:{issue.Line}:{issue.Column}: {severityLabel}: [{issue.Kind}] {issue.Message}");
if (useGitHubAnnotations)
{
var command = issue.Severity == IssueSeverity.Error ? "error" : "warning";
Console.WriteLine(
$"::{command} file={displayPath},line={issue.Line},col={issue.Column}::" +
$"{issue.Kind}: {EscapeAnnotation(issue.Message)}");
}
}
}
Console.WriteLine();
Console.WriteLine($"Checked {files.Count} markdown file(s). " +
$"{fileIssueCount} file(s) with issues, " +
$"{errorCount} error(s), {warningCount} warning(s).");
if (errorCount > 0 || warningCount > 0)
{
Console.WriteLine();
Console.WriteLine("Tip: wrap inline Scriban-looking text with `{%{{{ ... }}}%}` " +
"or wrap whole code blocks with `{%{` ... `}%}` to escape Scriban parsing.");
}
return errorCount > 0 ? 1 : 0;
}
private static List<Issue> CheckFile(string file, IReadOnlyDictionary<string, string> renderParameters)
{
var issues = new List<Issue>();
string content;
try
{
content = File.ReadAllText(file);
}
catch (Exception ex)
{
issues.Add(new Issue("Read", 1, 1, ex.Message, IssueSeverity.Error));
return issues;
}
var template = Template.Parse(content, file);
foreach (var message in template.Messages)
{
var severity = message.Type switch
{
Scriban.Parsing.ParserMessageType.Error => IssueSeverity.Error,
Scriban.Parsing.ParserMessageType.Warning => IssueSeverity.Warning,
_ => (IssueSeverity?)null
};
if (severity is null)
{
continue;
}
var kind = severity == IssueSeverity.Error ? "ScribanParseError" : "ScribanParseWarning";
issues.Add(new Issue(
kind,
message.Span.Start.Line + 1,
message.Span.Start.Column + 1,
message.Message,
severity.Value));
}
if (template.HasErrors)
{
return issues;
}
try
{
var context = new TemplateContext
{
StrictVariables = true
};
var scriptObject = new ScriptObject();
foreach (var entry in renderParameters)
{
scriptObject[entry.Key] = entry.Value;
}
context.PushGlobal(scriptObject);
template.Render(context);
}
catch (ScriptRuntimeException ex)
{
issues.Add(new Issue(
"ScribanRenderError",
ex.Span.Start.Line + 1,
ex.Span.Start.Column + 1,
ex.OriginalMessage,
IssueSeverity.Error));
}
catch (Exception ex)
{
issues.Add(new Issue("ScribanRenderError", 1, 1, ex.Message, IssueSeverity.Error));
}
return issues;
}
private static Dictionary<string, string> BuildRenderParameters(IEnumerable<string> files)
{
// Reproduces the keys the docs renderer places into its parameter
// dictionary before rendering a documentation page.
var parameters = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var name in BuiltInVariables)
{
parameters[name] = string.Empty;
}
foreach (var paramName in DiscoverParameterNames(files))
{
parameters[paramName] = string.Empty;
parameters[paramName + "_Value"] = string.Empty;
}
return parameters;
}
private static HashSet<string> DiscoverParameterNames(IEnumerable<string> files)
{
var names = new HashSet<string>(StringComparer.Ordinal);
var visitedDirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var file in files)
{
var dir = Path.GetDirectoryName(file);
while (!string.IsNullOrEmpty(dir) && visitedDirs.Add(dir))
{
var candidate = Path.Combine(dir, DocsParamsFileName);
if (File.Exists(candidate))
{
AddNamesFromDocsParamsFile(candidate, names);
}
var parent = Path.GetDirectoryName(dir);
if (string.IsNullOrEmpty(parent) || parent == dir)
{
break;
}
dir = parent;
}
}
return names;
}
private static void AddNamesFromDocsParamsFile(string path, HashSet<string> sink)
{
// A malformed docs-params.json would silently shrink the injected
// variable set and turn parameter file regressions into hard-to-debug
// "variable not found" errors on otherwise-fine markdown. Surface the
// failure directly so the contributor fixes the JSON instead.
using var doc = JsonDocument.Parse(File.ReadAllText(path));
if (!doc.RootElement.TryGetProperty("parameters", out var parameters) ||
parameters.ValueKind != JsonValueKind.Array)
{
throw new InvalidDataException(
$"{path}: expected a top-level `parameters` array.");
}
foreach (var parameter in parameters.EnumerateArray())
{
if (parameter.TryGetProperty("name", out var nameElement) &&
nameElement.ValueKind == JsonValueKind.String)
{
var name = nameElement.GetString();
if (!string.IsNullOrWhiteSpace(name))
{
sink.Add(name);
}
}
}
}
private static string? TryFindRepoRoot(string startDir)
{
var current = new DirectoryInfo(startDir);
while (current != null)
{
var gitPath = Path.Combine(current.FullName, ".git");
if (Directory.Exists(gitPath) || File.Exists(gitPath))
{
return current.FullName;
}
current = current.Parent;
}
return null;
}
private static string EscapeAnnotation(string text)
{
// GitHub workflow command escaping. `%` must be encoded first so the
// sequences we introduce below are not double-escaped.
return text
.Replace("%", "%25")
.Replace("\r", "%0D")
.Replace("\n", "%0A")
.Replace(":", "%3A")
.Replace(",", "%2C");
}
private enum IssueSeverity
{
Warning,
Error
}
private readonly record struct Issue(
string Kind,
int Line,
int Column,
string Message,
IssueSeverity Severity);
}

5
.github/workflows/angular.yml

@ -15,6 +15,11 @@ on:
- synchronize
- reopened
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read

20
.github/workflows/auto-pr.yml

@ -1,13 +1,13 @@
name: Merge branch dev with rel-10.3
name: Merge branch dev with rel-10.4
on:
push:
branches:
- rel-10.3
- rel-10.4
permissions:
contents: read
jobs:
merge-dev-with-rel-10-3:
merge-dev-with-rel-10-4:
permissions:
contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR
@ -18,14 +18,14 @@ jobs:
ref: dev
- name: Reset promotion branch
run: |
git fetch origin rel-10.3:rel-10.3
git reset --hard rel-10.3
git fetch origin rel-10.4:rel-10.4
git reset --hard rel-10.4
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
branch: auto-merge/rel-10-3/${{github.run_number}}
title: Merge branch dev with rel-10.3
body: This PR generated automatically to merge dev with rel-10.3. Please review the changed files before merging to prevent any errors that may occur.
branch: auto-merge/rel-10-4/${{github.run_number}}
title: Merge branch dev with rel-10.4
body: This PR generated automatically to merge dev with rel-10.4. Please review the changed files before merging to prevent any errors that may occur.
draft: true
token: ${{ github.token }}
- name: Merge Pull Request
@ -33,5 +33,5 @@ jobs:
GH_TOKEN: ${{ secrets.BOT_SECRET }}
run: |
gh pr ready
gh pr review auto-merge/rel-10-3/${{github.run_number}} --approve
gh pr merge auto-merge/rel-10-3/${{github.run_number}} --merge --auto --delete-branch
gh pr review auto-merge/rel-10-4/${{github.run_number}} --approve
gh pr merge auto-merge/rel-10-4/${{github.run_number}} --merge --auto --delete-branch

33
.github/workflows/build-and-test.yml

@ -21,6 +21,9 @@ on:
- '.github/workflows/build-and-test.yml'
pull_request:
branches:
- dev
- 'rel-*'
paths:
- 'framework/**/*.cs'
- 'framework/**/*.cshtml'
@ -42,8 +45,14 @@ on:
- synchronize
- reopened
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
id-token: write
jobs:
build-test:
@ -51,14 +60,20 @@ jobs:
timeout-minutes: 50
if: ${{ !github.event.pull_request.draft }}
steps:
- uses: jlumbroso/free-disk-space@main
- uses: PSModule/install-powershell@v1
with:
Version: latest
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@master
- uses: jlumbroso/free-disk-space@v1.3.1
- name: Install PowerShell
run: sudo apt-get install -y powershell
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', 'Directory.Packages.props') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Build All
run: ./build-all.ps1
working-directory: ./build
@ -70,4 +85,8 @@ jobs:
shell: pwsh
- name: Codecov
uses: codecov/codecov-action@v2
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: codecov/codecov-action@v5
with:
use_oidc: true
fail_ci_if_error: true

22
.github/workflows/cancel-workflow.yml

@ -1,17 +1,21 @@
# This workflow is intentionally disabled.
# The workflows that previously depended on this file now handle cancellation
# natively via per-workflow `concurrency` blocks.
# The styfle/cancel-workflow-action has been archived upstream and is no longer maintained.
#
# To re-enable manual cancellation, change `on: workflow_dispatch` back to `on: [push]`
# and restore the styfle step, but the native `concurrency` approach is preferred.
name: cancel-workflow
on: [push]
on:
workflow_dispatch:
permissions:
contents: read
jobs:
cancel:
permissions:
actions: write # for styfle/cancel-workflow-action to cancel/stop running workflows
name: 'Cancel Previous Runs'
name: 'Disabled - See file header comment'
runs-on: ubuntu-latest
timeout-minutes: 3
timeout-minutes: 1
steps:
- uses: styfle/cancel-workflow-action@0.6.0
with:
workflow_id: 10629,1299107,2792859,8268314
access_token: ${{ github.token }}
- name: No-op
run: echo "Cancellation is handled via concurrency groups in each workflow."

221
.github/workflows/check-docs-syntax.yml

@ -0,0 +1,221 @@
# Validates Scriban template syntax in PR-changed Markdown files under docs/en/,
# so escape issues are caught before they reach the published documentation.
name: Check Docs Syntax
on:
pull_request:
paths:
- 'docs/en/**/*.md'
- 'docs/en/docs-params.json'
- '.github/scripts/CheckDocsSyntax/**'
- '.github/workflows/check-docs-syntax.yml'
permissions:
contents: read
pull-requests: write
jobs:
check-scriban-syntax:
name: Validate Scriban syntax in docs/en
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Build syntax checker
run: dotnet build .github/scripts/CheckDocsSyntax/CheckDocsSyntax.csproj -c Release --nologo -v minimal
- name: Get changed markdown files
id: changed
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.pull_request.number;
const changed = [];
let paramsChanged = false;
let page = 1;
while (true) {
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
per_page: 100,
page,
});
const PARAMS_PATH = 'docs/en/docs-params.json';
for (const f of files) {
const isMutation =
f.status === 'added' || f.status === 'modified' || f.status === 'renamed';
if (!isMutation) continue;
// For renames, GitHub puts the new path in `filename` and the
// old one in `previous_filename`. Detect docs-params.json on
// either side so renames into / out of that path still trigger
// the parameter-file validation path.
if (f.filename === PARAMS_PATH || f.previous_filename === PARAMS_PATH) {
paramsChanged = true;
}
if (f.filename.startsWith('docs/en/') && f.filename.endsWith('.md')) {
changed.push(f.filename);
}
}
if (files.length < 100) break;
page++;
}
core.setOutput('files', changed.join('\n'));
core.setOutput('count', changed.length.toString());
core.setOutput('paramsChanged', paramsChanged ? 'true' : 'false');
core.info(`Markdown files to check: ${changed.length}`);
core.info(`docs-params.json changed: ${paramsChanged}`);
for (const f of changed) {
core.info(` - ${f}`);
}
- name: Run syntax checker
id: checker
if: steps.changed.outputs.count != '0' || steps.changed.outputs.paramsChanged == 'true'
env:
CHANGED_FILES: ${{ steps.changed.outputs.files }}
PARAMS_CHANGED: ${{ steps.changed.outputs.paramsChanged }}
run: |
mapfile -t files <<< "$CHANGED_FILES"
args=()
for f in "${files[@]}"; do
if [ -n "$f" ] && [ -f "$f" ]; then
args+=("$f")
fi
done
if [ ${#args[@]} -eq 0 ]; then
if [ "$PARAMS_CHANGED" = "true" ] && [ -f "docs/en/index.md" ]; then
# No markdown changed, but docs-params.json did. Run the checker
# against a single known-clean page so BuildRenderParameters /
# docs-params.json parsing actually executes and fails fast on a
# malformed parameter file.
echo "docs-params.json changed but no markdown changed; validating params via docs/en/index.md."
args+=("docs/en/index.md")
else
echo "No existing markdown files to check (all changes are deletions)."
exit 0
fi
fi
# Capture the checker's stdout so a follow-up step can post it as a PR
# comment when the run fails, while still streaming it to the job log.
set -o pipefail
dotnet run --project .github/scripts/CheckDocsSyntax/CheckDocsSyntax.csproj \
-c Release --no-build -- "${args[@]}" 2>&1 | tee checker-output.txt
- name: Upsert PR comment on failure
if: failure() && steps.checker.conclusion == 'failure'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const MARKER = '<!-- check-docs-syntax-bot -->';
const prNumber = context.payload.pull_request.number;
let report = '';
try {
report = fs.readFileSync('checker-output.txt', 'utf8').trim();
} catch (e) {
report = '(checker output was not captured)';
}
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = [
MARKER,
'### Docs syntax check failed',
'',
'The Scriban syntax checker reported issues in the Markdown files this PR changes. Wrap inline Scriban-looking text with `{%{{{ ... }}}%}` or wrap whole code blocks with `{%{` ... `}%}` to keep it from being parsed as a template.',
'',
'<details><summary>Checker output</summary>',
'',
'```',
report,
'```',
'',
'</details>',
'',
`[Full run log](${runUrl})`,
].join('\n');
// Find an existing bot comment to update (idempotent across re-runs).
let existing = null;
for (let page = 1; ; page++) {
const { data } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
page,
});
existing = data.find(c => c.body && c.body.startsWith(MARKER));
if (existing || data.length < 100) break;
}
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
core.info(`Updated existing bot comment (#${existing.id}).`);
} else {
const { data: created } = await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
core.info(`Created bot comment (#${created.id}).`);
}
- name: Resolve previous failure comment on success
# Clear any stale failure comment whenever this workflow run is green,
# even if the syntax checker step was skipped (e.g. when a later
# commit reverts the earlier failure so no markdown files appear in
# the PR's net diff).
if: success()
uses: actions/github-script@v7
with:
script: |
const MARKER = '<!-- check-docs-syntax-bot -->';
const prNumber = context.payload.pull_request.number;
for (let page = 1; ; page++) {
const { data } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
page,
});
const existing = data.find(c => c.body && c.body.startsWith(MARKER));
if (existing) {
const body = [
MARKER,
'### Docs syntax check passed',
'',
'The previously reported issues are no longer present in this PR.',
].join('\n');
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
core.info(`Cleared bot comment (#${existing.id}).`);
break;
}
if (data.length < 100) break;
}

4
.github/workflows/codeql-analysis.yml

@ -29,6 +29,10 @@ on:
- reopened
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read

7
.github/workflows/image-compression.yml

@ -12,6 +12,11 @@ on:
- synchronize
- reopened
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
if: github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft
@ -23,5 +28,3 @@ jobs:
- name: Compress Images
uses: calibreapp/image-actions@main
with:
githubToken: ${{ secrets.GITHUB_TOKEN }}

129
Directory.Packages.props

@ -23,6 +23,7 @@
<PackageVersion Include="Blazorise.Components" Version="2.0.4" />
<PackageVersion Include="Blazorise.DataGrid" Version="2.0.4" />
<PackageVersion Include="Blazorise.Snackbar" Version="2.0.4" />
<PackageVersion Include="MudBlazor" Version="8.0.0" />
<PackageVersion Include="Castle.Core" Version="5.2.1" />
<PackageVersion Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
<PackageVersion Include="CommonMark.NET" Version="0.15.1" />
@ -54,66 +55,66 @@
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.2" />
<PackageVersion Include="LdapForNet" Version="2.7.15" />
<PackageVersion Include="LibGit2Sharp" Version="0.31.0" />
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="14.9.1" />
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="14.13.0" />
<PackageVersion Include="MailKit" Version="4.13.0" />
<PackageVersion Include="Markdig.Signed" Version="0.42.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="10.0.20" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="10.0.20" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="10.0.51" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="10.0.51" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.2" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.7" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.7" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.7" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.7" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.71.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Abstractions" Version="1.71.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.9.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Composite" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Composite" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.7" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
@ -122,7 +123,7 @@
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.16.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.16.0" />
<PackageVersion Include="Minio" Version="6.0.5" />
<PackageVersion Include="MongoDB.Driver" Version="3.7.1" />
<PackageVersion Include="MongoDB.Driver" Version="3.8.1" />
<PackageVersion Include="NEST" Version="7.17.5" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Nito.AsyncEx.Context" Version="5.1.2" />
@ -132,11 +133,11 @@
<PackageVersion Include="NUglify" Version="1.21.17" />
<PackageVersion Include="Nullable" Version="1.3.1" />
<PackageVersion Include="Octokit" Version="14.0.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="7.3.0" />
<PackageVersion Include="OpenIddict.Core" Version="7.3.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="7.3.0" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="7.3.0" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="7.3.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="7.5.0" />
<PackageVersion Include="OpenIddict.Core" Version="7.5.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="7.5.0" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="7.5.0" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="7.5.0" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="10.23.26000" />
<PackageVersion Include="Polly" Version="8.6.3" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
@ -169,17 +170,17 @@
<PackageVersion Include="Spectre.Console" Version="0.51.1" />
<PackageVersion Include="StackExchange.Redis" Version="2.9.17" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.0.1" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.2" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.7" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.6.7" />
<PackageVersion Include="System.Linq.Queryable" Version="4.3.0" />
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.6" />
<PackageVersion Include="System.Security.Permissions" Version="10.0.2" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.7" />
<PackageVersion Include="System.Security.Permissions" Version="10.0.7" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.2" />
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.2" />
<PackageVersion Include="System.Text.Json" Version="10.0.2" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.7" />
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.7" />
<PackageVersion Include="System.Text.Json" Version="10.0.7" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="TencentCloudSDK.Sms" Version="3.0.1273" />
<PackageVersion Include="TimeZoneConverter" Version="7.2.0" />
@ -194,6 +195,6 @@
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="Fody" Version="6.9.3" />
<PackageVersion Include="System.Management" Version="10.0.2"/>
<PackageVersion Include="System.Management" Version="10.0.7" />
</ItemGroup>
</Project>

43
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -348,15 +348,58 @@
"CompanySize": "Company size",
"DetailTrialLicense": "Details",
"Requested": "Requested",
"Pending": "Pending",
"Running": "Running",
"Activated": "Activated",
"PurchasedToNormalLicense": "Purchased",
"Expired": "Expired",
"TrialLicenseDeletionWarningMessage": "Are you sure you want to delete the trial license? Trial license, organization, support accounts will be deleted!",
"LicenseCategoryFilter": "License category",
"Permission:SendWelcomeEmail": "Send Welcome Email",
"Permission:ProvisionExistingOrganizationsAi": "Provision Existing Organizations AI",
"Permission:AiProviderKeyLimit": "Manage AI Provider Key Limit",
"SendWelcomeEmail": "Send Welcome Email",
"SendWelcomeEmailWarningMessage": "Are you sure you want to send welcome email to the organization members?",
"SendWelcomeEmailSuccessMessage": "Welcome email sent successfully!",
"ProvisionExistingOrganizationsAi": "Provision Existing Organizations AI",
"ProvisionExistingOrganizationsAiConfirmation": "This will enable AI assisted development for all active organizations, grant included AI credits, and provision provider keys in the background. Do you want to continue?",
"DeleteExistingOrganizationsAiCredentials": "Delete Existing Organizations AI Keys",
"DeleteExistingOrganizationsAiCredentialsConfirmation": "This will revoke existing OpenRouter keys referenced by organizations and remove stored AI credentials from the database so provisioning can be retried. Do you want to continue?",
"ExistingOrganizationsAiOperationAlreadyRunning": "Another existing organizations AI operation is already running.",
"ExistingOrganizationsAiBackfillAlreadyRunning": "An existing organizations AI provisioning job is already running.",
"ExistingOrganizationsAiBackfillMissingManagementApiKey": "OpenRouter management API key is not configured for the admin application. Configure AiAssistedDevelopment:Providers:OpenRouter:ManagementApiKey before starting this operation.",
"NoActiveOrganizationsFoundForAiBackfill": "No active organizations were found for AI provisioning.",
"NoOrganizationsFoundForAiCredentialCleanup": "No organizations with AI credentials were found for cleanup.",
"ExistingOrganizationsAiBackfillNotFound": "The existing organizations AI provisioning operation was not found.",
"ExistingOrganizationsAiBackfillCompleted": "Existing organizations AI provisioning completed successfully.",
"ExistingOrganizationsAiBackfillFailed": "Existing organizations AI provisioning failed.",
"ExistingOrganizationsAiCredentialCleanupCompleted": "Existing organizations AI credential cleanup completed successfully.",
"ExistingOrganizationsAiCredentialCleanupFailed": "Existing organizations AI credential cleanup failed.",
"CurrentOrganization": "Current organization",
"AiProviderKeyLimit": "ABP AI Agent Provider Key Limit",
"AiProviderKeyMissing": "No provider key exists for this organization.",
"AiProviderKeyMissingDescription": "Create a provider key before setting the ABP AI Agent usable limit.",
"CreateAiProviderKey": "Create provider key",
"ProviderUsableLimit": "Provider usable limit",
"CustomerVisibleCredits": "Customer visible credits",
"ProviderRemaining": "Provider remaining",
"ProviderUsed": "Provider used",
"GrossToUsableRatio": "Gross to usable ratio",
"LastSync": "Last sync",
"NeverSynced": "Never synced",
"NotSynced": "Not synced",
"NewProviderUsableLimit": "New provider usable limit",
"NewProviderUsableLimitDescription": "This is the absolute OpenRouter usable USD limit. Customer-visible credits are calculated from the configured gross-to-usable ratio.",
"CustomerVisibleGrossPreview": "Customer visible gross preview",
"AiProviderLimitMustBeNonNegative": "AI provider limit must be greater than or equal to 0.",
"AiProviderLimitCannotBeLowerThanUsage": "AI provider limit cannot be lower than current provider usage.",
"AiProviderKeyLimitUpdateConfirmation": "Set OpenRouter usable limit to ${0}? Customer-visible credits will become ${1}.",
"Processed": "Processed",
"Succeeded": "Succeeded",
"Failed": "Failed",
"Cancelled": "Cancelled",
"CompletedAt": "Completed at",
"LastError": "Last error",
"Activate": "Activate",
"ActivateTrialLicenseWarningMessage": " When you activate a trial license, a welcome e-mail will be sent to the user. Do you want to activate it?",
"ActivateTrialLicenseSuccessMessage": "Activated successfully and the welcome e-mail sent to the organization members.",

4
common.props

@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>10.4.0-preview</Version>
<LeptonXVersion>5.4.0-preview</LeptonXVersion>
<Version>10.5.0-preview</Version>
<LeptonXVersion>5.5.0-preview</LeptonXVersion>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>

72
docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/POST.md

@ -0,0 +1,72 @@
# ABP.IO Platform 10.3 Final Has Been Released!
We are glad to announce that [ABP](https://abp.io/) 10.3 stable version has been released.
## What's New With Version 10.3?
All the new features were explained in detail in the [10.3 RC Announcement Post](https://abp.io/community/announcements/announcing-abp-10-3-release-candidate-hgnpr9jq), so there is no need to review them again. You can check it out for more details.
## Getting Started with 10.3
### How to Upgrade an Existing Solution
You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained:
### Upgrading via ABP Studio
If you are already using the ABP Studio, you can upgrade it to the latest version. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info.
After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Upgrade ABP Packages** action button to instantly upgrade your solution:
![](upgrade-abp-packages.png)
### Upgrading via ABP CLI
Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version.
If you haven't installed it yet, you can run the following command:
```bash
dotnet tool install -g Volo.Abp.Studio.Cli
```
Or to update the existing CLI, you can run the following command:
```bash
dotnet tool update -g Volo.Abp.Studio.Cli
```
After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows:
```bash
abp update
```
You can run this command in the root folder of your solution to update all ABP related packages.
## Migration Guides
There are some important changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v10.2 or earlier versions: [ABP Version 10.3 Migration Guide](https://abp.io/docs/10.3/release-info/migration-guides/abp-10-3)
## Community News
### New ABP Community Articles
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here:
- [Liming Ma](https://abp.io/community/members/maliming) has published 6 new posts:
- [Dynamic Events in ABP](https://abp.io/community/articles/dynamic-events-in-abp-dukq95m1)
- [Dynamic Background Jobs and Workers in ABP](https://abp.io/community/articles/dynamic-background-jobs-and-workers-in-abp-wfdkdsq9)
- [Shared User Accounts in ABP Multi-Tenancy](https://abp.io/community/articles/shared-user-accounts-in-abp-multitenancy-mf3bkg79)
- [Secure Client Authentication with private_key_jwt in ABP 10.3](https://abp.io/community/articles/secure-client-authentication-with-privatekeyjwt-in-abp-b2rf18bc)
- [Operation Rate Limiting in ABP Framework](https://abp.io/community/articles/operation-rate-limiting-in-abp-framework-f4jtd6sn)
- [Resource-Based Authorization in ABP Framework](https://abp.io/community/articles/resourcebased-authorization-in-abp-framework-choku1sn)
- [One Endpoint, Many AI Clients: Turning ABP Workspaces into OpenAI-Compatible Models](https://abp.io/community/articles/turning-abp-workspaces-into-openai-compatible-endpoints-u3ls1gp4) by [Engincan Veske](https://abp.io/community/members/EngincanV)
- [Automatically Validate Your Documentation: How We Built a Tutorial Validator](https://abp.io/community/articles/automatically-validate-your-documentation-m3ozgkhv) by [Mansur Besleney](https://abp.io/community/members/mansur.besleney)
- [Automate Localhost Access for Expo: A Guide to Dynamic Cloudflare Tunnels & Dev Builds](https://abp.io/community/articles/automate-localhost-access-for-expo-a-guide-to-dynamic-7cblqtj3) by [Sumeyye Kurtulus](https://abp.io/community/members/sumeyye.kurtulus)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## About the Next Version
The next feature version will be 10.4. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version.

BIN
docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

BIN
docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/upgrade-abp-packages.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

214
docs/en/Blog-Posts/2026-04-29 v10_4_Preview/POST.md

@ -0,0 +1,214 @@
# ABP Platform 10.4 RC Has Been Released
We are happy to release [ABP](https://abp.io) version **10.4 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version.
Try this version and provide feedback for a more stable version of ABP v10.4! Thanks to you in advance.
## Get Started with the 10.4 RC
You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli).
> The v10.4 RC versions of ABP Studio and the ABP CLI are still being tested and will be released shortly.
By default, ABP Studio uses stable versions to create solutions. Therefore, if you want to create a solution with a preview version, first you need to create a solution and then switch your solution to the preview version from the ABP Studio UI:
![studio-switch-to-preview](studio-switch-to-preview.png)
## Migration Guide
There are no explicitly marked breaking changes in this version. However, there are still some important migration notes for specific scenarios. Please check the migration guide if you are upgrading from v10.3 or earlier: [ABP Version 10.4 Migration Guide](https://abp.io/docs/10.4/release-info/migration-guides/abp-10-4).
## What's New with ABP v10.4?
In this section, I will introduce some major features released in this version.
Here is a brief list of titles explained in the next sections:
- URL-Based Localization
- Localization File Splitting
- Blazor UI: MudBlazor Support
- Identity: Single-Use Email/SMS 2FA Token Providers
- Account Pro: Passwordless Email Login
- AI Management: MCP Server Enhancements
- LeptonX: URL-Based Localization and Theme Improvements
- Dependency and Security Updates
### URL-Based Localization
ABP v10.4 introduces URL-based localization support. You can now embed the culture directly in the URL path, such as `/tr/products` or `/en/about`.
This is especially useful for public websites, documentation sites, e-commerce applications, and any application that needs SEO-friendly and shareable localized URLs. Instead of relying only on query string, cookie, or browser language detection, the selected culture can be part of the URL itself.
You can enable it with a single configuration:
```csharp
Configure<AbpRequestLocalizationOptions>(options =>
{
options.UseRouteBasedCulture = true;
});
```
When enabled, ABP automatically handles route registration, URL generation, menu links, and language switching for MVC/Razor Pages, Blazor, and Angular UIs.
For Angular applications, route trees can be wrapped with `withOptionalRouteCulturePrefix` so the same route configuration can handle both `/identity/users` and `/en/identity/users`:
```typescript
import { Routes } from '@angular/router';
import { withOptionalRouteCulturePrefix } from '@abp/ng.core';
const appRoutesCore: Routes = [
// ... your routes
];
export const appRoutes = withOptionalRouteCulturePrefix(appRoutesCore);
```
For Blazor applications, ABP built-in module pages already include culture-aware route variants. If you have your own Blazor pages, add culture route variants manually:
```razor
@page "/Products"
@page "/{culture}/Products"
```
> See the [URL-Based Localization](https://abp.io/docs/10.4/framework/fundamentals/url-based-localization) documentation and [#25174](https://github.com/abpframework/abp/pull/25174) for details.
### Localization File Splitting
ABP localization resources can now use multiple JSON files for the same culture. This is useful for large modules or applications where keeping all localization texts in a single `en.json` file becomes difficult to maintain.
For example, you can split a resource by feature:
```text
Localization/
+-- MyResource/
+-- en.json
+-- en_Authors.json
+-- en_Books.json
+-- en_Users.json
```
ABP merges these files into the same localization dictionary. Files are sorted by name before merging, and if the same key exists in multiple files, the value from the last file wins.
> See the [Localization](https://abp.io/docs/10.4/framework/fundamentals/localization) documentation and [#25227](https://github.com/abpframework/abp/pull/25227) for details.
### Blazor UI: MudBlazor Support
ABP v10.4 starts the [MudBlazor](https://mudblazor.com/) integration work for the Blazor UI stack.
This release adds MudBlazor-based package infrastructure, template integration, and module/theme support needed to build ABP Blazor applications with MudBlazor. Blazorise and MudBlazor are now supported side by side, the LeptonX theme works with both UI libraries, and when creating a new Blazor project you can pick which UI library to use.
This is a major UI foundation change, so we especially encourage Blazor users to try the RC and share feedback before the stable release.
***Selecting the UI library when creating a new Blazor project in ABP Studio:***
![mud-studio](mud-studio.png)
***MudBlazor-based application home page:***
![mud-index](mud-index.png)
***MudBlazor-based Identity management page:***
![mud-identity](mud-identity.png)
> See [#25235](https://github.com/abpframework/abp/pull/25235) for details.
### Identity: Single-Use Email/SMS 2FA Token Providers
ABP v10.4 improves the security model for email and SMS two-factor authentication codes.
Email and phone verification codes now use ABP's single-use token providers. Generated codes are encrypted, stored with an absolute expiration time, and consumed after successful validation. Generating a new code invalidates the previous one.
You can configure token lifetime and code length:
```csharp
Configure<AbpEmailTwoFactorTokenProviderOptions>(options =>
{
options.TokenLifespan = TimeSpan.FromMinutes(5);
options.CodeLength = 8;
});
Configure<AbpPhoneNumberTwoFactorTokenProviderOptions>(options =>
{
options.TokenLifespan = TimeSpan.FromMinutes(2);
});
```
The authenticator app provider is not affected and continues to use the standard TOTP approach.
> See the [Two Factor Authentication](https://abp.io/docs/10.4/modules/identity/two-factor-authentication) documentation and [#25316](https://github.com/abpframework/abp/pull/25316) for details.
### Account Pro: Passwordless Email Login
ABP Commercial v10.4 RC introduces passwordless email login for the Account Pro module.
Users can sign in by receiving an email login link and/or a one-time password (OTP), depending on the configured login type. Administrators can enable the feature, choose the login mode, and configure token lifetime from the account settings.
![account-settings](account-settings.png)
The feature is designed with security in mind:
- Login links and OTPs are single-use.
- Resending a login email invalidates previous tokens.
- Token operations respect the current tenant context.
- Rate limiting helps protect against brute-force and email spam scenarios.
- Email enumeration behavior follows the existing account security setting.
This feature is especially useful for applications that want a smoother sign-in experience without removing the tenant-aware and security-focused account flow of ABP.
***"Login via email":***
![login-via-email](login-via-email.png)
***Type the One-time Password (OTP) to login:***
![login-via-email2](login-via-email2.png)
### AI Management: MCP Server Enhancements
The [AI Management module](https://abp.io/docs/latest/modules/ai-management) continues to improve its MCP (Model Context Protocol) support.
In this release, MCP server configuration has been enhanced for `stdio` transport scenarios and workspace relationships. This makes it easier to connect local or process-based MCP servers to AI workspaces and use their tools from the chat playground.
### LeptonX: URL-Based Localization and Theme Improvements
LeptonX has been updated to work with the new URL-based localization flow across UI types, including Angular language switching and culture-aware navigation.
This release also includes several theme improvements and fixes, such as PathBase-safe menu links, improved custom select synchronization, sidebar menu re-binding after async rendering, and MudBlazor-related theme support.
### Dependency and Security Updates
ABP v10.4 RC includes several dependency updates and security-related package bumps:
- OpenIddict upgraded to **7.5.0**
- MongoDB.Driver upgraded to **3.8.0**
- Microsoft/System package updates for CVE-2026-40372
- `System.Security.Cryptography.Xml` upgraded to **10.0.6**
- `@abp/lodash` lodash dependency updated
> Check [Package Version Changes](https://abp.io/docs/10.4/package-version-changes) document for all updates.
### Other Improvements and Enhancements
- **Virtual File System**: `ReplaceEmbeddedByPhysical` can now receive exclusion filters, which gives developers more control over included/excluded physical files during development ([#25284](https://github.com/abpframework/abp/pull/25284)).
- **Exception logging**: Complex objects in exception data are now serialized more clearly in logs ([#25267](https://github.com/abpframework/abp/pull/25267)).
- **Feature management**: Improved batch state checker performance and added `RequireFeaturesSimpleBatchStateChecker` ([#25276](https://github.com/abpframework/abp/pull/25276)).
- **RabbitMQ**: Fixed a potential hang while acquiring a closed channel after RabbitMQ restart ([#25311](https://github.com/abpframework/abp/pull/25311)).
- **Shared user accounts**: Improved shared-user lookup and two-factor authentication flows for shared user scenarios.
- **Account and SaaS modules**: Improved shared-user invitation and account-page flows in tenant user sharing scenarios.
## Community News
### New ABP Community Articles
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here:
- [Stop Sprinkling [RequiresFeature] Everywhere — A Centralized Feature Gate for ABP.IO](https://abp.io/community/articles/stop-sprinkling-requiresfeature-everywhere-a-centralized-7znie818) by [Mohammad AlMohammad AlMahmoud](https://abp.io/community/members/Mohammad97Dev)
- [Top AI Coding Models in 2026: Which One Should Developers Actually Use?](https://abp.io/community/articles/top-ai-coding-models-in-2026-which-one-should-developers-use-rivh8x15) by [Alper Ebiçoğlu](https://abp.io/community/members/alper)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## Conclusion
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://abp.io/docs/10.4/release-info/road-map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v10.4 RC and provide feedback to help us release a more stable version.
Thanks for being a part of this community!

BIN
docs/en/Blog-Posts/2026-04-29 v10_4_Preview/account-settings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/en/Blog-Posts/2026-04-29 v10_4_Preview/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

BIN
docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-identity.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-index.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

BIN
docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-studio.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en/Blog-Posts/2026-04-29 v10_4_Preview/studio-switch-to-preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

89
docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/POST.md

@ -0,0 +1,89 @@
# ABP.IO Platform 10.4 Final Has Been Released!
We are glad to announce that [ABP](https://abp.io/) 10.4 stable version has been released.
## What's New With Version 10.4?
All the new features were explained in detail in the [10.4 RC Announcement Post](https://abp.io/community/announcements/announcing-abp-10-4-release-candidate-7ukyudm0), so there is no need to review them again. You can check it out for more details.
## Getting Started with 10.4
### How to Upgrade an Existing Solution
You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained:
### Upgrading via ABP Studio
If you are already using the ABP Studio, you can upgrade it to the latest version. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info.
After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Upgrade ABP Packages** action button to instantly upgrade your solution:
![](upgrade-abp-packages.png)
### Upgrading via ABP CLI
Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version.
If you haven't installed it yet, you can run the following command:
```bash
dotnet tool install -g Volo.Abp.Studio.Cli
```
Or to update the existing CLI, you can run the following command:
```bash
dotnet tool update -g Volo.Abp.Studio.Cli
```
After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows:
```bash
abp update
```
You can run this command in the root folder of your solution to update all ABP related packages.
## Migration Guides
There are no explicitly marked breaking changes in this version. However, there are still some important migration notes for specific scenarios. Please read the migration guide carefully, if you are upgrading from v10.3 or earlier versions: [ABP Version 10.4 Migration Guide](https://abp.io/docs/10.4/release-info/migration-guides/abp-10-4)
## Community News
### Highlights from the ABP Community
There have been some important announcements for the ABP community recently. Here are two highlights you may want to check out:
#### React UI for ABP Framework Is Finally Here
![React UI for ABP Framework Is Finally Here](https://abp.io/api/posts/cover-picture-source/3a2114d0-5518-38a8-d9b3-ab5100b587a4?v=20260508112328)
React support has been one of the most requested topics in the ABP community, and with ABP 10.4, it becomes a first-class UI option in the modern template system. The new React UI is designed for teams that want ABP on the backend and React on the frontend while keeping ABP's built-in application features such as authentication, authorization, localization, multi-tenancy, modularity, runtime configuration, and deployment.
Modern React solutions include your own React application as real source code in the solution, plus the ABP Admin Console for standard module administration screens. This means your product UI stays fully under your control, while ABP still provides a consistent and upgradeable administration experience.
The React stack is built with familiar modern tools, including Vite, TypeScript, TanStack Router, TanStack Query, Axios, Zod, React Hook Form, Tailwind CSS, shadcn/ui, and Vitest. You can create a React UI solution with the `--modern` flag or by selecting the modern template flow in ABP Studio. You can read the announcement here: [React UI for ABP Framework Is Finally Here](https://abp.io/community/announcements/react-ui-for-abp-framework-is-finally-here-7rfmgb2v).
#### Introducing ABP Studio AI Agent
![Introducing ABP Studio AI Agent](https://abp.io/api/posts/cover-picture-source/3a212ebc-06c1-e10f-f83c-a90079f988c1?v=20260508112328)
ABP Studio now introduces ABP Agent, a deeply integrated AI coding assistant that understands ABP solutions as complete systems, not just as files in folders. It is aware of ABP concepts such as modules, layers, aggregate roots, repositories, application services, DTOs, permissions, localization, event bus, distributed cache, background jobs, and module dependencies.
ABP Agent works in three modes: Agent mode for implementation, Plan mode for read-only investigation and planning, and Ask mode for Q&A and explanations. It can use ABP Studio's analysis engine to understand the solution structure, build affected projects, start or restart applications, run tasks, generate proxies, add migrations, and inspect runtime feedback such as exceptions, logs, HTTP requests, and distributed events.
The announcement also highlights the broader development loop around ABP Agent: solution runner integration, custom workflows, task runner support, Git and GitHub integration, AI-generated commit messages, and ABP-aware AI code review. You can read the announcement here: [Introducing ABP Studio AI Agent](https://abp.io/community/announcements/introducing-abp-studio-ai-agent-o1ni0toc).
### New ABP Community Articles
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here:
- [ABP in the AI Era: Surviving, Evolving, and Staying Relevant](https://abp.io/community/articles/abp-in-the-ai-era-surviving-evolving-and-staying-relevant-6gyfjfpe) by [Engincan Veske](https://abp.io/community/members/EngincanV)
- [Stop Sprinkling [RequiresFeature] Everywhere — A Centralized Feature Gate for ABP.IO](https://abp.io/community/articles/stop-sprinkling-requiresfeature-everywhere-a-centralized-7znie818) by [Mohammad AlMohammad AlMahmoud](https://abp.io/community/members/Mohammad97Dev)
- [Top AI Coding Models in 2026: Which One Should Developers Actually Use?](https://abp.io/community/articles/top-ai-coding-models-in-2026-which-one-should-developers-use-rivh8x15) by [Alper Ebiçoğlu](https://abp.io/community/members/alper)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## About the Next Version
The next feature version will be 10.5. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version.

BIN
docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

BIN
docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/upgrade-abp-packages.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/abp-studio.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/discovery.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/job-feed.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/negotiation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-dark.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-light.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-bottom-tab-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-drawer-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-old-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

165
docs/en/Community-Articles/13-05-2026-new-react-native/post.md

@ -0,0 +1,165 @@
# A New Look for ABP React Native: NativeWind, Modernization & Two Sample Apps
## Introduction
Mobile is increasingly the first surface users meet a product on, and the bar for what a mobile UI is supposed to look and feel like has moved a lot in the last few years. ABP has had a solid React Native template for a long time, but "solid" and "modern" are not the same thing — and the gap was starting to show.
If you've been following ABP's mobile story, you know the React Native template has been useful but a bit stuck in time: a wall of `StyleSheet.create()` blocks per screen, inline color literals, a mixed `.js` / `.tsx` codebase, and a navigator tree that carried features most teams never used. This post is about what changed, why we changed it, and what's next.
We approached the refresh in two passes: first a thorough cleanup of legacy screens, dead components, and unused locales, and then a full styling-layer rewrite around NativeWind v4 with a shadcn-style design token system. Along the way we also built two sample apps on top of the new template, so the changes aren't just theoretical — they've been exercised against real screens and real flows.
The changes ship to both the **`react-native`** template and the **`microservice/apps/mobile/react-native`** template, so layered, single-layer (no-layers), and microservice solutions all get the same modernized mobile experience.
## Why we modernized
The previous template used React Native Paper plus hand-written `StyleSheet.create()` objects under every component. That works, but it has a real cost over time:
- **No design tokens.** Spacing, radii, and colors lived as raw numbers and hex strings scattered across screens. Changing one accent color meant touching dozens of files.
- **Dark mode was a manual switch.** Every screen had to read theme colors and pass them down through `style` props — easy to forget, easy to drift.
- **Dead code accumulated.** Older tenant management, dashboard widgets, and SVG illustrations had built up — useful in 2021, but mostly noise in 2026.
- **Mixed JS/TS.** Some screens were still plain `.js`, which got in the way of type safety and consistent tooling.
We wanted the same outcome a modern web template gives you: open a screen, see structured layout, see semantic class names, change one token in a config file and watch it ripple everywhere. NativeWind v4 lets us bring that exact feel to React Native — Tailwind CSS class names compile at build time, so the runtime stays small and predictable.
## Part 1 — Template cleanup
Before the styling rewrite, the template needed a serious trim.
**Removed (legacy / unused):**
- `Dashboard/` (HostDashboard, TenantDashboard, EditionUsageWidget, ErrorRateWidget) — these widgets were demo-only and rarely fit real apps.
- `CreateUpdateTenant/`, `CreateUpdateUser/`, `TenantsNavigator`, `UsersNavigator` — full administrative CRUD belongs in the admin UI, not on mobile.
- Long tail of one-off components: `DataList`, `DateRangePicker`, `Select`, `ListMenu`, `LoadingButton`, `TenantBox`, `AddIcon`, `CancelButton`, `NoRecordSvg`, `AnalysisSvg`.
- Hooks: `UsePermission`, `UseAuthAndTokenExchange`, `UseLocalizedTitle`, `PermissionHOC`.
- 18 of 20 locale files. Only **`en`** and **`tr`** ship by default — the rest are easy to add back per project, but bundling 20 locales no one ships was waste.
**Added / upgraded:**
- New **`LoginScreen.tsx`** (replacing the legacy `LoginScreen.js`), plus brand-new `RegisterScreen`, `ForgotPasswordScreen`, and `ResetPasswordScreen` — the full account flow is now first-class.
- **`AppContainer.tsx` / `AppContent.tsx`** split, so app-level providers and bootstrapping are cleanly separated from navigation.
- **`app.config.js`** replacing the static `app.json`, enabling environment-driven Expo config.
- **`scripts/tunnel.js`** — automates the Cloudflare tunnel flow we covered in [Automate Localhost Access for Expo](https://abp.io/community/articles/automate-localhost-access-for-expo-a-guide-to-dynamic-7cblqtj3).
- New docs: `docs/UPGRADE.md` and `docs/permission-guide.md`.
The result: a smaller, sharper template that fits how teams actually use ABP on mobile today.
## Part 2 — NativeWind v4 and a new visual system
With the template trimmed, the styling layer got a full modernization.
**The stack:**
- **NativeWind v4** — Tailwind CSS for React Native. Class names compile at build time, so the runtime cost is minimal.
- **Tailwind CSS 3.4** — single source of truth for design tokens via `tailwind.config.js`.
- **shadcn-style neutral palette** — zinc-based color system with semantic tokens (`background`, `foreground`, `card`, `muted`, `accent`, `border`, `destructive`). Same vocabulary you already know from the web template.
- **React Native Paper** is still in the box, but **only for `TextInput`** (outlined mode, error states, icons). Everything else moved to NativeWind.
- **Ionicons** (`@expo/vector-icons`) replaces Paper icons across the UI.
**What this looks like in practice:**
```tsx
// Before — separate styles object, inline colors
<View style={styles.card}>
<Text style={styles.title}>{title}</Text>
</View>
const styles = StyleSheet.create({
card: { padding: 16, backgroundColor: '#fff', borderRadius: 12, /* ... */ },
title: { fontSize: 18, fontWeight: '600', color: '#18181b' },
});
// After — class names, dark mode included
<View className="p-4 bg-card rounded-xl border border-border">
<Text className="text-lg font-semibold text-foreground">{title}</Text>
</View>
```
That `dark:` variant is the big quality-of-life win. Dark mode is no longer something each screen has to handle by hand — it's a config-level concern, applied through semantic tokens, and consistent across every surface. Here's the new `LoginScreen` in both modes — same code, same components, just the theme switch flipped:
<table>
<tr>
<td align="center" width="50%"><img src="images/rn-light.png" alt="LoginScreen — light mode" /><br/><sub><b>Light</b></sub></td>
<td align="center" width="50%"><img src="images/rn-dark.png" alt="LoginScreen — dark mode" /><br/><sub><b>Dark</b></sub></td>
</tr>
</table>
**Visual touches that come along for the ride:**
- **Hero `HomeScreen`** with the app logo and feature cards.
- **iOS-style grouped settings cards** in `SettingsScreen`.
- **Centered card containers + logo headers** on login / register / password screens.
- `src/theme/spacing.ts` and `src/theme/shape.ts` are gone — those tokens now live in `tailwind.config.js` where they belong.
### Navigation, rethought
Navigation is where the template change is felt the most. The old template gave you a single drawer menu and that was it — everything lived behind a hamburger. The new template keeps the drawer but adds a proper **`BottomTabNavigator`** alongside it, plus an **`AccountNavigator`** for the user/account area. That brings the navigation in line with what users actually expect from a modern mobile app: primary destinations one tap away on the bottom tab bar, secondary destinations and global actions tucked into the drawer.
<table>
<tr>
<td align="center" width="33%"><img src="images/rn-old-menu.png" alt="Old drawer menu" /><br/><sub><b>Before</b> — single drawer menu</sub></td>
<td align="center" width="33%"><img src="images/rn-new-drawer-menu.png" alt="New drawer menu" /><br/><sub><b>After</b> — revamped drawer</sub></td>
<td align="center" width="33%"><img src="images/rn-new-bottom-tab-menu.png" alt="New bottom tab menu" /><br/><sub><b>After</b> — new bottom tab bar</sub></td>
</tr>
</table>
## Two sample apps we built
To stress-test the new template and to give the community something concrete to learn from, we built **two sample apps** on top of it:
### Habit Tracker — a minimal demo
I built **Habit Tracker** — a single-feature demo where you keep a list of daily habits and tick them off as you go through your day. Build a habit, mark it done, watch the streak grow. That's the whole loop.
The point here wasn't to ship a feature-rich productivity tool, it was to show the smallest meaningful surface you can build on top of the new template without losing the auth flow, theming, or navigation defaults. Everything around your one feature — login/register, drawer + bottom tab navigation, light/dark mode, localized strings — comes from the template. You add the feature, and the template carries everything else.
<table>
<tr>
<td align="center" width="33%"><img src="images/rn-small-1.png" alt="Habit Tracker — screen 1" /></td>
<td align="center" width="33%"><img src="images/rn-small-2.png" alt="Habit Tracker — screen 2" /></td>
<td align="center" width="33%"><img src="images/rn-small-3.png" alt="Habit Tracker — screen 3" /></td>
</tr>
</table>
### Hanova — a home-services demo
We have also built **Hanova** that is a two-sided home-services sample where customers find local providers, request a job, negotiate the price, and chat through to confirmation. Pick a role, log in, browse open work or open requests, accept a booking, message the other party. That's basically the loop.
The point here wasn't about shipping a full Uber-for-plumbers product, but it was to show a **realistic but focused** domain you can grow on top of the ABP single-layer template without rebuilding authentication, navigation, or theming from scratch. Everything around the marketplace — login/register, role selection, bottom-tab navigation, light/dark mode, localized strings, OAuth, and API wiring — comes from the template. You add the booking flow (discovery, job feed, negotiation, messaging), and the template carries everything else. Two pre-seeded personas (`ayse.kaya` / `mehmet.yilmaz`, password `Demo@1234`) populate every screen on first launch so you can explore both sides immediately.
<table>
<tr>
<td align="center" width="33%"><img src="images/discovery.png" alt="Hanova — Discovery / browse providers" /></td>
<td align="center" width="33%"><img src="images/job-feed.png" alt="Hanova — Bookings / job feed" /></td>
<td align="center" width="33%"><img src="images/negotiation.png" alt="Hanova — Messages / negotiation" /></td>
</tr>
</table>
Both apps will be available shortly — they're useful as reference implementations, and they're also useful as a kind of regression check on the template itself.
## Try it yourself
If you're starting fresh, you'll pick up the new template automatically. The easiest way is through **ABP Studio** — just enable the mobile platform and pick **React Native** from the wizard:
![Selecting React Native as the mobile platform in ABP Studio](images/abp-studio.png)
Or via CLI:
```bash
abp new Acme.BookStore --template app-pro --mobile react-native
```
For the microservice solution template:
```bash
abp new Acme.BookStore --template microservice-pro --mobile react-native
```
Open the generated `react-native/` (or `apps/mobile/react-native/`) folder, run `npm install`, then `npx expo start`, and you'll be looking at the new UI within seconds.
If you're **upgrading an existing project**, check the new `docs/UPGRADE.md` in the template — it walks through the moving pieces (Babel/Metro config, the new `global.css` import, the `nativewind-env.d.ts` ambient types, and the locale trim).
## Summary
The ABP React Native template is in a much better place than it was a few months ago. The legacy screens and a long tail of dead components are gone, the styling layer is now a proper token-based system powered by **NativeWind v4** and a shadcn-style neutral palette, and **dark mode** is no longer something each screen has to think about — it's just there. Navigation got a real refresh too: the old single-drawer pattern is now a revamped drawer plus a `BottomTabNavigator` and `AccountNavigator`, which is the structure most modern mobile apps actually use.
We also built **two sample apps** on top of the new template — **Habit Tracker**, a focused single-feature one, and **Hanova**, a more substantial demo — so the changes are exercised against real screens and real flows, not just template snapshots. If you create a new ABP solution with a React Native mobile app, all of this is what you get out of the box; if you're upgrading, the new `docs/UPGRADE.md` walks you through it. Try the template, build something on top of it, and share what you make — the whole point of moving to a token-based system is that it should be easy for everyone to extend, not just us.

BIN
docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-app-screenshot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-ui-modern-template-demo.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-studio-project-creation-react.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/react-ui-and-admin-console.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

116
docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/post.md

@ -0,0 +1,116 @@
# React UI for ABP Framework Is Finally Here
If you have followed ABP for a while, you probably know that React support has been one of the most requested topics in the community.
With **ABP 10.4.0-rc.1**, that wait ends. React in ABP is no longer just something people ask about, hope for, or imagine as the next step. You can now create it, run it, and explore it today as a beta/preview experience in the modern template system.
As part of the ABP Framework team, and as one of the developers working on this React effort, I am genuinely happy to finally share it. This RC gives the community an early chance to try it, share feedback, and help us polish the final details before **ABP 10.4 stable**, where we plan to make the React UI generally available.
![abp-studio-project-creation-react](images/abp-studio-project-creation-react.png)
## Why this matters
ABP Framework has always been about helping teams build modern, maintainable, production-ready applications faster. With the new React UI, we are extending that same vision to teams who want ABP on the backend and React on the frontend without losing the built-in application features that make ABP productive from day one.
This is not another empty starter. The goal is a **first-class UI option** that fits into the ABP application startup experience and works naturally with familiar ABP concepts such as authentication, authorization, localization, multi-tenancy, modularity, runtime configuration, and deployment.
There is one important detail: the React UI belongs to ABP's **modern template system**. You create it with the `--modern` flag in the ABP CLI or by selecting the modern template flow in ABP Studio. You can find the technical documentation here: [React UI documentation](https://abp.io/docs/10.4/framework/ui/react).
## A quick look at the architecture
The final shape is clearer now: a modern React solution gives you a real React application in the solution, plus the ABP administration experience.
First, there is **your React application**. In the modern templates, this lives directly in the solution as a real app under `react/` or `apps/react/`. It contains the frontend code you work with every day, including pages, components, routing, API integration, runtime configuration, and authentication setup.
Second, there is the **ABP Admin Console**. The Admin Console is a pre-built React application that provides the standard ABP module management pages. It is delivered through the `Volo.Abp.AdminConsole` NuGet package, so it can evolve with ABP package updates while your own React application stays focused on your product's business features.
For layered and single-layer modern applications, the Admin Console is hosted by the backend and served under `/admin-console/*`. For microservice solutions, it runs as a separate React app under `apps/react-admin-console/`, with its own runtime configuration and the same `/admin-console/` base path. In both cases, the main React app can link users into the Admin Console when they need full administrative screens.
This split is a practical design choice. Your business UI stays yours, while administration capabilities remain available, consistent, and upgradeable.
![react-ui-and-admin-console](images/react-ui-and-admin-console.png)
## A different frontend philosophy
One of the most important things to understand is that this React UI is **not** being shaped with exactly the same architecture as some previous UI options.
We are not trying to ship the whole frontend experience as a closed set of page implementations coming from npm packages. Instead, the generated solution includes the actual page code inside the app itself. You can open it, understand it, refactor it, redesign it, and adapt it without fighting against a packaged black box.
The Admin Console covers ABP's standard module administration pages. Your own React application remains intentionally open and direct. That gives teams a good balance: built-in administrative power from ABP, and full ownership of the product-facing frontend.
## Built for AI-driven development
The new React UI is also shaped for the era of **AI-assisted development**.
React, TypeScript, Vite, TanStack Router, TanStack Query, Axios, Zod, React Hook Form, and shadcn/ui are technologies that modern coding assistants understand very well. Just as importantly, the generated application contains real frontend code in the solution. That gives AI tools and coding agents concrete project context to read, extend, and refactor.
This direction also fits the broader ABP AI story. ABP Studio already includes an AI assistant experience, and the new **ABP AI Agent** is being introduced to bring code generation, project understanding, issue fixing, and natural-language application evolution directly into the ABP workflow. You can follow that work here: [The Future of ABP Studio: AI Agent + Code Generation](https://abp.io/community/events/community-talks/the-future-of-abp-studio-ai-agent-code-generation-live-fekeoyjr). For the wider toolset, see the [ABP AI Toolkit](https://abp.io/ai/toolkit).
## What the React experience looks like
The current template already points to the kind of experience React developers expect from a modern application:
- A Vite-powered React + TypeScript frontend
- TanStack Router for client-side routing
- TanStack Query for server state and data fetching
- OIDC authentication against the ABP Auth Server
- Axios-based HTTP client integration
- Runtime configuration through `dynamic-env.json`
- Localization and permission-aware behavior integrated with ABP application configuration
- Tailwind CSS and shadcn/ui components that live in your project and can be customized directly
- Zod and React Hook Form for form handling and validation
- Vitest for frontend tests
- A dedicated Admin Console for ABP module administration
Even in its current form, the React UI already feels like a real ABP solution experience, not just a login page plus a few demo screens.
![abp-react-app-screenshot.png](images/abp-react-app-screenshot.png)
## More than a hello world
The generated React app is intentionally small enough to understand, but it is not empty.
Out of the box, you already get the kind of foundation most teams expect: login, registration, forgot-password and reset-password flows, runtime configuration, localization, permission-aware routing, API proxy generation, and a simple users page that can deep-link into the Admin Console when full user management is needed.
Depending on the selected options, it can also include a sample Books CRUD page that demonstrates how to build a full create/read/update/delete flow against an ABP backend.
The Admin Console provides the standard management experience for ABP modules, including identity management, roles, organization units, settings, audit logs, OpenIddict administration, language management, text templates, GDPR, SaaS and tenant management, and other module pages depending on your solution configuration.
That is the core value: developers get a clean React application to build their product, while ABP continues to provide the administrative capabilities expected from a production-ready application platform.
## Try it with ABP 10.4 RC
During the RC period, you can create a modern React solution with ABP 10.4.0-rc.1:
```bash
abp new Acme.BookStore --template app --modern
```
The React UI is the default UI option when `--modern` is used, but you can also pass it explicitly:
```bash
abp new Acme.BookStore --template app --modern --ui-framework react
```
For a single-layer application:
```bash
abp new Acme.BookStore --template app-nolayers --modern
```
For a microservice solution:
```bash
abp new Acme.BookStore --template microservice --modern
```
Once ABP 10.4 stable is released, the same modern React experience is planned to become generally available without needing to target the RC version explicitly.
![ABP Framework React UI Modern Template Demo](images/abp-react-ui-modern-template-demo.gif)
## What's next
The React UI is now real in ABP 10.4 RC, and the final polishing work continues toward the stable release. If you have been waiting for a real React path in ABP, this is the point where it stops being a wish and starts becoming something you can actually build with.
For me, one of the nicest parts of this RC is that we can finally stop talking about React support in ABP as a future idea and start improving something real together.
Try it, explore it, and share feedback with us while we keep polishing it for **ABP 10.4 stable**.

254
docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/Post.md

@ -0,0 +1,254 @@
## Introduction
AI coding tools went from “cool autocomplete” to “basically your junior dev (who never sleeps)” in just a couple of years.
In 2026, the landscape is **crowded, competitive, and honestly a bit confusing**. Every model claims to be the best at coding—but depending on what you actually *do* (APIs, frontend, DevOps, debugging), the “best” can change fast.
So instead of hype, let’s break down the **top AI coding models in 2026**, ranked by:
* Real-world dev usefulness
* Code quality & correctness
* Context handling
* Tooling ecosystem
We'll check the AI models against these topics:
![](pic1.jpg)
---
## 🏆 1. GPT-5.4 (OpenAI) — The All-Round Beast
Let’s not dance around it—**GPT-5.4 is still the most versatile coding model right now.**
### Why it’s #1
* Extremely strong across **all languages**
* Handles **large codebases** without losing context
* Excellent at:
* Refactoring
* Architecture suggestions
* Debugging complex issues
### Where it shines
* Full-stack development
* API design
* Writing clean, production-ready code
### Where it struggles
* Occasionally over-engineers solutions
* Can be slower than lightweight models
### As a result;
If you want a **default “just works” coding AI**, this is it.
---
## 🥈 2. Claude 4.7 (Anthropic) — The Clean Code Specialist
Claude 4.7 has built a reputation for writing code that feels like it came from a senior engineer who drinks too much coffee but cares deeply about readability.
### Strengths
* Beautiful, readable code
* Strong reasoning for:
* Refactoring
* Code reviews
* Documentation
### Killer feature
* Massive context window → great for:
* Large repositories
* Long discussions
* System design
### Weak spots
* Slightly less aggressive in solving edge-case bugs
* Sometimes too “safe” in decisions
### As a result;
Perfect if you care about **maintainability over raw speed**.
---
## 🥉 3. Gemini 3.1 (Google) — The Multimodal Powerhouse
Gemini 3.1 is where things get interesting.
This isn’t just a coding model—it’s a **multi-input problem solver**.
### What makes it different
* Understands:
* Code
* Screenshots
* Diagrams
* Logs
### Where it dominates
* Debugging UI issues from screenshots
* DevOps + cloud workflows
* Cross-referencing documentation
### Downsides
* Code style can be inconsistent
* Sometimes less deterministic than GPT-5
### As a result;
If your workflow includes **visual debugging or cloud-heavy systems**, this is insanely useful.
---
## ⚡ 4. Mistral Code (Open Models) — The Speed King
Mistral AI’s coding models are gaining serious attention.
### Why devs love it
* Fast
* Cheap (or free if self-hosted)
* Great for:
* Autocomplete
* Small functions
* Local development
### Trade-offs
* Not as strong in deep reasoning
* Limited compared to closed models
### As a result;
Best choice for:
* Privacy-sensitive environments
* Offline/local setups
* Lightweight coding tasks
---
## 🧠 5. Code Llama 4 — The Open-Source Veteran
Code Llama 4 is still very relevant, especially in enterprise setups.
### Strengths
* Fully open-source
* Customizable & fine-tunable
* Good baseline performance
### Weaknesses
* Behind top-tier models in reasoning
* Needs tuning for best results
### As a result;
If your company says “no cloud AI,” this is your friend.
---
## 📊 Comparison Table Between AI Models
| Model | Best For | Weakness |
| ------------ | ------------------------ | --------------------- |
| GPT-5.4 | Everything | Slightly slower |
| Claude 4.7 | Clean, maintainable code | Less aggressive fixes |
| Gemini 3.1 | Multimodal workflows | Inconsistent style |
| Mistral Code | Speed & local usage | Shallow reasoning |
| Code Llama 4 | Open-source flexibility | Needs tuning |
Image Prompt:
A sleek table-style infographic comparing AI models with icons, performance bars, and labels like “Best for speed”, “Best for reasoning”.
---
## 🤔 When to Use What (Real Scenarios)
### Use GPT-5.4 if:
* You’re building a full product
* You need architecture + implementation
* You want fewer “AI mistakes”
---
### Use Claude 4.7 if:
* You’re reviewing code
* You care about readability
* You’re working in a team
---
### Use Gemini 3.1 if:
* You debug using screenshots/logs
* You work with cloud infrastructure
* You want multimodal workflows
---
### Use Mistral / Code Llama if:
* You need local/private AI
* You want low cost
* You’re okay trading power for control
---
## 🔌 Where ABP Framework Fits In
If you're working with **ASP.NET Core and the ABP Framework**, these models can seriously boost productivity:
* GPT-5.4 → Generate **application services, DTOs, and modules**
* Claude → Clean up **domain layer logic**
* Gemini → Help debug **UI + backend integration issues**
The sweet spot?
👉 Use AI to scaffold ABP layers, then refine manually.
That keeps your architecture clean while still saving hours.
---
## 🚨 Reality Check
AI coding models in 2026 are powerful—but:
* They still hallucinate edge cases
* They don’t fully understand your business logic
* They can fix somewhere, break another
* They can not fix a bug even after you write 10 different prompts
So yeah—**don’t ship blind**.
Treat them like:
> A fast junior dev… who needs code review.
---
## TL;DR
👉 There’s no single “winner”—just the best tool for your workflow.
![](pic2.png)
---
If you're experimenting with these models in real projects (especially with ABP), it's worth trying **multiple models side-by-side**. The differences become obvious *fast*.

BIN
docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

BIN
docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

BIN
docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-ai-review.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

BIN
docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-analyze-engine.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

BIN
docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-code-generation.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 KiB

244
docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-ai-announcement.md

@ -0,0 +1,244 @@
# Introducing ABP Studio AI Agent
The new ABP Studio release introduces a deeply integrated set of features designed around one idea: an AI coding agent that truly understands ABP solutions, sitting inside an IDE that already knows how to build, run, monitor, and iterate on them.
At the center is **ABP Agent**, our AI coding assistant. Around it are long-standing ABP Studio capabilities that have been brought together into a single development loop. Together they turn ABP Studio into a single place where you architect and code your ABP solutions.
![abp-studio-ui](studio-ai-coding-assistant.jpg)
---
## Meet ABP Agent
![agent-working](abp-agent-code-generation.gif)
ABP Agent is the AI coding assistant built into ABP Studio. It operates in three modes, each tuned for a different stage of work:
- **Agent mode**: the implementation mode. The agent reads the solution, writes and edits files, builds the affected projects, runs your apps, watches the runtime, and iterates until the change works end-to-end.
- **Plan mode**: read-only planning. The agent investigates the codebase, researches the official ABP documentation, and produces a structured implementation plan (Problem, Solution, Workflow Diagram, Files Affected, Expected Result). When you are happy with the plan, a single click promotes it into Agent mode and the implementation starts from the plan.
- **Ask mode**: read-only Q&A. The agent explains how something works, draws diagrams, and answers questions about your code without touching any files.
The agent is **ABP-aware by default**. It is instructed to prefer ABP base classes over plain POCOs, repositories over direct `DbContext` injection, `ApplicationService` over plain services, the ABP permission system over `[Authorize(Roles=…)]`, localized strings over hardcoded text, `BusinessException`/`UserFriendlyException` over plain `Exception`, the distributed cache abstraction over raw memory cache, and background job abstractions over hand-rolled hosted services. When it is unsure about an ABP feature, it consults the official ABP documentation as a primary source of truth, not random blog posts on the web.
---
## Why It Was Needed
General-purpose AI coding tools (Cursor, Claude Code, Windsurf, opencode and similar) are excellent for horizontal, file-shaped work. They read source files, edit them, and run shell commands. That works well for small scripts or front-end apps.
ABP solutions are different. A typical ABP solution is **system-shaped**, not just file-shaped: the important context is not only where files are located, but how modules, layers, permissions, contracts, localization, persistence, and UI pieces work together:
- It is split across multiple modules and layers (Domain, Application, EntityFrameworkCore, HttpApi, Web, etc.) with strict dependency rules.
- It is composed of many runnable units: HTTP services, gateways, identity servers, background workers, SPAs, mobile apps, plus the Docker containers they depend on.
- It follows a strong set of conventions: aggregate roots, repositories, application services, DTOs, permissions, localization, event bus, distributed cache, background jobs.
- It is a *living* system at design time. You don't just read code, you run it, watch logs, follow distributed events, hit HTTP endpoints, and iterate.
A generic agent has none of that vocabulary. It does not know what a module is, which project is the Domain layer, or that an `ApplicationService` should not depend on a `DbContext` directly. It cannot start "the gateway, the auth server, and the two microservices my React app talks to". It just runs `dotnet run` in some folder and hopes. It cannot tell you that the agent's latest edit caused a runtime exception in the Identity service or made the `OrderPlacedEto` event handler silently fail, because it has no concept of "running app".
ABP Agent and the surrounding ABP Studio features were built to close exactly that gap. The agent is born inside an IDE that already understands modules, run profiles, builds, migrations, proxies, Docker containers, Kubernetes services, and distributed runtime telemetry, and it uses every one of them.
---
## How ABP Agent Sees Your Application: The Analyze Engine
![abp-agent-analyze-engine](abp-agent-analyze-engine.png)
Before ABP Agent answers anything, it needs to *understand* your solution. This is where the **Analyze** feature does the heavy lifting.
When you open a solution, ABP Studio analyzes every package and builds a structural map: the application's **skeleton**. It identifies what each type actually is in ABP terms: an aggregate root, an entity, a value object, a repository interface or implementation, a domain service, an application service or interface, a DTO, an integration service, a controller, an HTTP API, a background job or worker, an event handler, an ETO, a SignalR hub, a DbContext (EF Core or MongoDB), a permission/feature/setting provider, a global feature, a Mapperly or AutoMapper profile, a fluent validator, a menu contributor, a data seed contributor, an options class, an ABP module with its `[DependsOn]` chain, and many more.
For each of these, the analyzer captures the structural information that actually matters: properties, method signatures with their line ranges, injected dependencies, base classes, the permission groups and items, the feature tree, the settings catalog, the database tables and collections, without parsing C# at runtime.
ABP Agent receives this analyzed skeleton at the start of every session. That means:
- The agent already knows what types exist in each project and what role each one plays, before you ask anything.
- When you say *"add a Category to my Catalog module"*, the agent already knows where the Domain project is, what the existing aggregate roots look like, which DbContext should get the new entity, where the permissions provider lives, and which application service to extend.
- When the agent needs to change an existing type, it can fetch a precise outline (base classes, properties, method signatures with exact line ranges) instead of reading thousands of lines of source. That is dramatically faster, dramatically cheaper, and dramatically more accurate.
- When you modify code, ABP Studio re-analyzes only the affected packages and refreshes the agent's understanding.
This is what we mean by **solution-aware AI**. The agent does not "look at folders and guess"; it works from a typed, ABP-shaped index of your application.
---
## Native .NET Awareness: No Shell Gymnastics
A common pattern in generic agentic IDEs is to do everything through the terminal. Building? Run `dotnet build` in a shell. Restarting an app? Spawn a process. Adding a migration? More shell. Checking whether the build succeeded? Parse terminal output and pray.
ABP Agent does not work through a terminal for these things. Building, running, restarting, adding migrations, generating proxies, installing client-side libraries: all of these are **first-class operations** the agent invokes directly. Build calls return structured results with errors and warnings the agent can act on immediately. Starting an application returns a structured outcome: which apps started, which failed, and a summarized error log for each failure. There is no string-scraping, no "did the spinner stop yet?" guessing.
The agent also scopes builds intelligently. It can build a single project, a single module, or the entire solution depending on what changed. After editing a few files in your Application layer, it builds just that module (not the whole solution), and only escalates the scope when needed.
---
## Solution Runner & Live Runtime Monitor
![solution-runner-and-monitor](studio-solution-runner-and-monitor.png)
This is the half of ABP Agent that no general-purpose AI IDE can replicate, because no general-purpose AI IDE has a first-class runner for distributed .NET solutions.
**Solution Runner** is the ABP Studio feature that knows about every runnable thing in your solution: web apps, microservices, gateways, identity servers, background workers, CLI applications, mobile and SPA front-ends, plus the Docker containers your stack depends on (databases, caches, message brokers). Apps are grouped into folders and can be launched as a coherent set under a named *run profile*. You start everything with one click; ABP Studio handles ports, dependencies, restart-on-failure, and embedded browser previews.
ABP Agent talks to the Solution Runner directly. It can:
- Start, stop, or restart a specific application, every application inside a folder ("Backend/API"), or all applications.
- Start or stop Docker containers separately from applications.
- Run tasks defined in your run profile (database migrations, npm scripts, custom scripts).
When the agent starts an application that crashes on startup, it does not loop forever restarting it. It captures the recent logs from that application, summarizes the failure, returns a structured report to itself, and uses that to **fix the underlying bug in the code** before trying again.
But starting apps is only half of it. The **runtime monitor** is the other half. When your applications run under ABP Studio, the IDE collects, in real time:
- **Exceptions**: type, message, full stack trace, inner exceptions, source application.
- **Logs**: timestamps, log level, application, message.
- **HTTP requests**: method, URL, status code, response time.
- **Distributed events**: name, source, direction (published/received), and payload.
ABP Agent can query all of this. The development loop becomes:
> Generate the code → Build the affected module → Restart the affected application → Hit the endpoint or perform the action → Ask the agent to inspect the last exceptions, the failing HTTP requests, and the distributed events → Fix the bug → Repeat.
That is the loop a generic IDE cannot do, because it cannot see your application after it starts. ABP Agent can.
---
## Custom Workflows & Task Runner Integration: Determinism When You Need It
LLMs are powerful but non-deterministic. In real teams, some steps must happen **every time** (before or after the agent works) and they must happen in a known order. That is what **Custom Workflows** are for.
A workflow is a named sequence of deterministic steps with a *Before* phase and an *After* phase. You can configure steps such as:
- Build (whole solution, specific modules, or specific packages)
- Start, stop, or restart applications (specific apps, an entire folder, or all)
- Start or stop containers
- Run a Task (any custom task defined in your run profile: `npm install`, custom scripts, code generators, database resets, anything your team already wired into ABP Studio's Task Runner)
- Add a database migration
- Install client-side libraries
- Generate C# or Angular client proxies for HTTP APIs
For example, you can configure a workflow that:
- **Before** the agent works: starts the required containers and runs a code-generation Task.
- **After** the agent works: builds the affected modules, regenerates client proxies, restarts the gateway and the SPA, and adds a database migration.
Workflows can be **personal** to you, or **shared with your team** through the solution's run profile file, so the deterministic pre/post pipeline travels with the repository and every developer gets the same behavior.
The result is a clean separation: the LLM handles the creative, ambiguous middle (designing the change and writing the code), while your workflow guarantees the boring, must-happen steps around it. The agent becomes more deterministic exactly where determinism matters, without losing flexibility where it doesn't.
The Task Runner integration is what makes the *After* phase especially valuable. You can run absolutely anything as a post-step: npm scripts, custom executables, code generators, integration test runners, lint passes, database refresh scripts. If your team already runs it as part of "I just changed some code, now do X", you can run it automatically after every agent turn.
---
## Git & GitHub: Reviewing and Committing Without Leaving the IDE
![ai-review](abp-agent-ai-review.gif)
ABP Studio now ships a full Git client with deep GitHub integration. The goal is simple: once the agent finishes a change, you should be able to review, commit, push, branch, and respond to review feedback **without ever leaving ABP Studio**.
The Git side covers everything you'd expect:
- Stage and commit changes with rich, package-grouped change views.
- Create, switch, and merge branches.
- Stash and restore work, including a clear flow for stashing before switching branches.
- View commit history and create branches from any commit.
- Resolve merge conflicts with a built-in conflict editor.
- Push, pull, fetch, with seamless OAuth-based GitHub authentication.
On the GitHub side:
- Browse and filter Issues, view their comments, and send an issue (or just its comments) directly to ABP Agent to start working on it.
- View existing Pull Requests, see their requested changes and comments inline, and send the requested-changes feedback straight into ABP Agent so it can address the reviewer's comments. You can send the feedback into the same session you used to write the change, or start a fresh agent session.
- A one-click "Create Pull Request" action opens the new-PR page on GitHub directly from the IDE, pre-targeted at the current branch.
- Jump to any file on GitHub directly from the IDE.
Two AI-assisted touches make this loop especially smooth:
- **AI-generated commit messages.** Click "Generate with AI" and ABP Agent writes a Conventional Commits-style message from the staged diff. Edit it if you want, then commit.
- **AI Code Review on the diff.** Select the files you want reviewed, run AI review, and inline suggestions stream into the IDE as the analysis runs. Crucially, this is not a generic code review; it is an **ABP-aware** review. The reviewer looks for ABP-specific pattern violations: plain POCOs where ABP base classes belong, direct `DbContext` injection where a repository should be used, hardcoded strings where localization should be used, plain exceptions where `BusinessException` belongs, role-based authorization where ABP Permissions are the right answer, and so on. When the reviewer is unsure, it consults the official ABP documentation before flagging an issue.
The result is a *closed loop*: the agent writes the change, you (or the AI reviewer agent) review the diff, you let the agent fix the comments, you commit with an AI-suggested message, you push, and you head to GitHub for the pull request, all from inside ABP Studio.
---
## The End-to-End Development Loop
Put the pieces together and ABP Studio becomes the single place where the whole development cycle happens:
1. Open an issue from GitHub, send it to ABP Agent.
2. Switch to Plan mode; ABP Agent investigates the analyzed solution, consults the ABP docs, produces a structured plan.
3. Promote the plan to Agent mode. Implementation starts from the plan.
4. The *Before* workflow runs (start containers, run preparation tasks).
5. ABP Agent writes the code, batching edits across the affected modules, and fixes any compile errors directly.
6. The *After* workflow runs (build the affected modules, install client-side libraries, generate proxies, add a database migration, restart the impacted applications).
7. ABP Agent inspects the runtime monitor (exceptions, logs, HTTP requests, distributed events) and fixes any bug it sees.
8. You (or the AI reviewer) review the diff. Comments are sent back into the same agent session, or into a new one.
9. ABP Agent generates a commit message; you commit and push.
10. Open the new-pull-request page on GitHub with one click from ABP Studio. When reviewers leave requested changes, send them into the same agent session or start a fresh one, and iterate.
You do not need to switch to a terminal to build. You do not need a separate tool to start your microservices. You do not need a different IDE to watch logs. You do not need a separate window for Git and GitHub. **ABP Studio is the only program you use during development, all in one.**
---
## Learning Over Time
ABP Agent also has a small but powerful feedback feature: when it makes a mistake and you (or the build, or the ABP docs) correct it, it can save that correction as a **lesson**. Lessons are short, verified notes that the agent carries forward into future turns and future sessions, so the same mistake does not happen twice. Over time, the agent gets better at the specific conventions and quirks of *your* solution, not just generic ABP.
---
## Honest Comparison With Generic AI IDEs
To be fair: Cursor, Claude Code, Windsurf and opencode are excellent products. They have semantic search, plan/agent modes, sub-agents, file editing, and shell access, and we wouldn't dispute any of that. But for ABP development specifically, here is where ABP Agent is genuinely different:
| Capability | ABP Agent | Generic AI IDEs |
| --- | --- | --- |
| Aware of ABP roles (aggregate root, app service, repository, DTO, ETO, event handler, etc.) | Yes, structurally indexed | No (flat file/text view) |
| Knows your solution's modules, layers and dependency rules | Yes | No |
| Builds with module/package scope as a first-class operation | Yes, structured build result | No, runs `dotnet build` in a shell and parses output |
| First-class Solution Runner for distributed .NET apps + containers | Yes | No, generic shell processes |
| Restarting an application, opening its URL, waiting for it to be ready | Yes, built in | No |
| Live runtime telemetry as a tool: exceptions, logs, HTTP requests, distributed events | Yes | No |
| ABP-specific patterns checked during AI code review | Yes | Generic patterns only |
| Pre/post workflows with deterministic build / start / migrate / generate-proxies steps | Yes | No first-class concept |
| Task Runner integration for arbitrary pre/post steps owned by your team | Yes | No |
| Adding EF Core migrations as a first-class agent action | Yes | Shell only |
| Generating C#/Angular client proxies as a first-class agent action | Yes | Shell only |
| Native ABP documentation as authoritative source for the agent's decisions | Yes | Generic web search |
| Integrated Git + GitHub (issues, PR viewing, AI review) inside the same window | Yes | Partial / external |
The pattern is consistent: **anything ABP-specific or runtime-specific belongs to ABP Agent and ABP Studio. Anything general-purpose is something everyone has.** That is the line we drew on purpose.
---
## What This Means For You
If you build with ABP Framework, this release changes how you work in three concrete ways:
1. **You stop describing your solution to the AI.** ABP Agent already knows it.
2. **You stop bouncing between tools.** Editor, runner, runtime monitor, Git, GitHub, AI review, they all live in one window with one feedback loop.
3. **You stop accepting non-deterministic side effects.** Custom workflows + Task Runner integration make the boring steps boring again, while the agent focuses on the creative ones.
This is the first release that brings AI assistance to ABP Studio, and we built it so that it is designed *around* the agent rather than bolted on. We think this is the natural shape of AI-assisted enterprise .NET development.
---
## Short-Term Roadmap
This release is the first step. The features below are already on our short-term roadmap and will land in upcoming releases:
- **Debug Mode**: a dedicated mode where you and ABP Agent co-operate when debugging the solution. The agent can follow breakpoints, inspect state, propose fixes mid-session, and re-run after each change.
- **Browser control for the agent**: ABP Agent will be able to drive the embedded browser, browse pages, fill forms, and click buttons, so it can verify a UI flow end-to-end on its own and report what it observed.
- **Custom Workflows improvements**: more step types, richer conditions, finer-grained targets, and better visibility into which workflow ran for which agent turn.
- **GitHub integration improvements**: in-IDE pull request creation (no more jumping to the GitHub website), richer review handling, and more first-class issue and PR actions for the agent.
- **Git integration improvements**: more advanced day-to-day Git operations available without leaving the IDE.
- **Design helper**: ABP Agent will generate images.
- **Create a new project with the agent**: an AI-driven solution creation flow where you describe the application you want and ABP Agent helps choose the right template, modules, and configuration.
- **ABP Suite integration**: ABP Agent will be able to invoke ABP Suite for CRUD-page generation. Instead of asking the LLM to write the full set of layers for a CRUD page (which spends a lot of tokens and time), the agent will hand the task to ABP Suite, get a deterministic, production-quality result back, and continue with the parts that actually need the AI.
## Live Demo
We have previewed the ABP Agent in our latest community talk. Click to watch it:
[![ABP Agent community talk demo video cover image](community-talk-cover-image.png)](https://www.youtube.com/watch?v=GYVFn2lRuWw)
### Also see
* https://abp.io/studio/ai-agent

BIN
docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-new-design.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/community-talk-cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

BIN
docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

BIN
docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-ai-coding-assistant.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

BIN
docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-solution-runner-and-monitor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-ai-review.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

BIN
docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-analyze-engine.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-code-generation.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 KiB

117
docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-vibe-architecting-en.md

@ -0,0 +1,117 @@
# The Antidote to Vibe Architecting: ABP Studio AI Agent
Recent discussions in software engineering have started pointing at a quiet but critical side effect of AI-assisted development. The uncomfortable truth is simple: **AI agents no longer just write code, they make architectural decisions, yet almost no one reviews those decisions as architecture.**
Here is a striking observation: for the *same task*, changing nothing but the wording of your prompt can produce a system whose line count and file count grow several times over. In other words, the words in your prompt shape the architecture of the system. The phenomenon even has a name: **vibe architecting**, architecture that emerges from prompts rather than from deliberate, recorded design.
In this article I will first lay out the problem, and then show why **ABP Studio AI Agent** is designed precisely to mitigate it.
![abp-agent-code-generation](abp-agent-code-generation.gif)
---
## What Is "Vibe Architecting"?
The **vibe coding** popularized by Andrej Karpathy (describing what you want in plain language and letting the AI write the code) has moved far beyond single-line autocomplete. Today's agents spin up entire systems from a single sentence of description. And there is a further step: while writing code, the agent is also **choosing the architecture.**
We can identify five main **mechanisms** through which agents make hidden architectural decisions:
1. **Model selection:** Different LLMs produce structurally different code; switching the model selector is itself an architectural choice.
2. **Task decomposition:** How the agent splits work into subtasks determines the module boundaries of the system.
3. **Default configuration:** Without explicit rules, the agent drifts toward defaults inherited from its training data.
4. **Scaffolding and autonomous generation:** A single prompt folds every framework, database, auth, and deployment choice into one interaction, with no visible rationale.
5. **Integration protocols:** How the system connects to the outside world is chosen by the agent or the tool, not by the team.
Three properties set these decisions apart from human ones:
- **Scale:** Framework, database, authentication, and deployment are selected in a single interaction, bundled together rather than as separately reviewable choices.
- **Speed:** Decisions a team would debate for days happen in **seconds**, faster than any review process can keep up with.
- **Opacity:** The decisions are buried inside the generated code: no ADR, no design document, no recorded rationale.
This has two concrete consequences. The first is the **speed-review gap**: the agent builds a system in minutes, while the team needs hours or days to audit it. The second is **convergence onto narrow stacks**: agent-based tools default to the same stack again and again (e.g. React/TypeScript/Tailwind), which concentrates the security attack surface. In short, these decisions take seconds, arrive bundled, and leave no record behind.
---
## The Bridge: The Risk Is Far Greater in Enterprise .NET
The examples behind these discussions usually revolve around small chatbots. But carry the same mechanisms over to an **enterprise, modular, distributed** solution and the risk multiplies. A hidden architectural decision now looks like this:
- An `ApplicationService` depending directly on `DbContext` (a layering violation),
- Raw data access instead of a repository,
- A hand-written `[Authorize(Roles=…)]` instead of the ABP permission system,
- Hardcoded text instead of localized strings,
- A wrong module dependency, or a flawed event/flow design.
None of these stand out in a small prototype; but in an enterprise system with dozens of modules and many services, they turn into **technical debt that piles up unseen.** And this debt starts accumulating before the code even runs, right at the moment of production.
---
## The Prescription: A Three-Layer Governance Framework
A three-layer framework that maps existing tool mechanisms onto classic software-architecture concepts is a sensible answer to this problem:
- **Layer 1, Constraints:** Defines what the agent may and may not do. Today, instruction files (AGENTS.md, .cursorrules) and MCP configurations play this role informally; in architecture terms, the equivalent is ADLs and Attribute-Driven Design.
- **Layer 2, Conformance:** Checks the generated code against those constraints. Plan-build flows (the agent proposes before acting) and post-generation hooks are the counterpart of *fitness functions* in evolutionary architecture.
- **Layer 3, Knowledge:** Feeds architectural context back to the agent. Today, repository maps and context files; in architecture terms, ADRs and Architectural Knowledge Management.
One caveat worth noting: tools that deliver all three layers together, proactively, are still **largely missing** today. And that is exactly where it gets interesting.
---
## ABP Studio AI Agent: The Prescription, Implemented
ABP Studio AI Agent is not a general-purpose code agent; it is an in-IDE agent that understands ABP solutions *as systems*. Look at its design and you will see it covers all three layers above with surprising clarity.
### Layer 1, Constraints: ABP-Aware by Default
The first layer calls for "a constraint layer that tells the agent what it may do." ABP Agent brings this as default behavior. By instruction, the agent prefers: **ABP base classes** over plain POCOs, **repositories** over direct `DbContext`, **`ApplicationService`** over plain services, the **ABP permission system** over `[Authorize(Roles=…)]`, **localized strings** over hardcoded text, **`BusinessException`/`UserFriendlyException`** over plain `Exception`, and the **distributed cache** abstraction over raw in-memory cache. When it is unsure about an ABP feature, it consults the **official ABP documentation** as the authoritative source, not random blog posts.
On top of that, **Custom Workflows** define the deterministic steps that must run before and after every agent turn, and they can be shared with the team. So the constraints don't live in one developer's head; they live inside the solution.
### Layer 2, Conformance: Plan Mode + ABP-Aware Review
![abp-agent-ai-review](abp-agent-ai-review.gif)
The second layer calls for "plan-build flows and fitness-function-like checks." ABP Agent has two concrete answers to this:
- **Plan mode:** The agent first inspects the solution in read-only mode, consults the ABP docs, and produces a structured implementation plan (Problem, Solution, Workflow Diagram, Files Affected, Expected Result). Once you approve it, the plan turns into implementation with a single click. This is exactly the "propose before acting" flow the framework asks for.
- **ABP-aware AI code review:** This is not a generic review; it catches **ABP-specific pattern violations**: POCOs in the wrong place, direct `DbContext` injection, hardcoded strings, plain exceptions, role-based authorization, and so on. When unsure, it checks the official ABP docs. This is the concrete form of a **fitness function** that audits generated code against the constraints.
### Layer 3, Knowledge: Analyze Engine + Lessons
![abp-agent-analyze-engine](abp-agent-analyze-engine.png)
The third layer calls for "a knowledge layer that feeds architectural context back to the agent": repository maps, ADRs. ABP Agent's **Analyze engine** is a higher-level version of this: the moment you open a solution, it scans every package and produces a **typed, ABP-role-aware** structural map. It knows what each type actually is: an aggregate root, a repository, an application service, a DTO, an ETO, a permission provider. The agent receives this map at the start of every session; it doesn't look at folders and guess, it **knows** the structure of the solution.
Add to that **lessons**: when the agent makes a mistake and gets corrected, it records the correction as a short, verified note and carries it into future sessions. This is a living counterpart to the ADR/AKM idea of persisting decisions and their rationale.
---
## The Problem → ABP Agent's Answer
| The problem being raised | ABP Studio AI Agent's answer |
| --- | --- |
| **Opacity:** decisions buried in code, no rationale | The Analyze engine makes the structure visible; Plan mode turns a decision into a written plan first |
| **Speed-review gap:** agent fast, review slow | Deterministic workflows + ABP-aware review close the loop inside the IDE |
| **Convergence onto narrow stacks / concentrated risk** | ABP already provides a consistent, secure, enterprise stack and conventions |
| **Governance / ADR gap** | Lessons + shared custom workflows put decisions on the record |
| **Implicit coupling:** the prompt dictates the infrastructure | Solution-awareness means infrastructure is determined by the real structure, not by guesswork |
The pattern is consistent: everything the governance framework says "should exist" is part of ABP Agent's design.
---
## An Honest Boundary
Let's be clear: the governance framework above is a general, ABP-independent discussion; it wasn't built to promote ABP. Nor are we claiming that "academia recommends ABP." Our claim is more modest and more solid: ABP Studio AI Agent's design **overlaps remarkably** with these **principles**. Vibe architecting is a real risk, and no tool reduces it to zero; but in enterprise .NET development, ABP Agent is built to reduce that risk meaningfully.
---
## Conclusion
The real question is not whether AI agents make architectural decisions; they do, and that is now an irreversible reality. The real question is this: are those decisions **visible, governed, and reviewable**, or do they get quietly buried in the code and turn into technical debt?
ABP Studio AI Agent is designed to answer "yes, visible and governed." It surfaces decisions instead of hiding them; it knows the structure of the solution instead of guessing; it learns and keeps a record instead of starting from scratch every time. If you build enterprise software in the age of vibe architecting, that is exactly where the difference lies.
- **ABP Studio AI Agent:** https://abp.io/studio/ai-agent
- **Live demo (community talk):** https://www.youtube.com/watch?v=GYVFn2lRuWw

4
docs/en/cli/differences-between-old-and-new-cli.md

@ -7,9 +7,9 @@
# Old ABP CLI vs New ABP CLI
ABP CLI (Command Line Interface) is a command line tool to perform some common operations for ABP based solutions or ABP Studio features. With **v8.2+**, the old/legacy ABP CLI has been replaced with a new [CLI](index.md) system to align with the new templating system and [ABP Studio](../studio/index.md). Also, some superior features/commands have been introduced with the new CLI, such as `kube-connect` and `kube-intercept` commands.
ABP CLI (Command Line Interface) is a command line tool to perform some common operations for ABP based solutions or ABP Studio features. With **v8.2+**, the old/classic ABP CLI has been replaced with a new [CLI](index.md) system to align with the new templating system and [ABP Studio](../studio/index.md). Also, some superior features/commands have been introduced with the new CLI, such as `kube-connect` and `kube-intercept` commands.
In this guide, you will learn the motivation behind this change, some questions that you may have, how to use the old/legacy CLI, its features, and more...
In this guide, you will learn the motivation behind this change, some questions that you may have, how to use the old/classic CLI, its features, and more...
## Reason For The Change

981
docs/en/cli/index.md

File diff suppressed because it is too large

66
docs/en/docs-nav.json

@ -1866,6 +1866,68 @@
}
]
},
{
"text": "React",
"items": [
{
"text": "Overview",
"path": "framework/ui/react/index.md",
"isIndex": true
},
{
"text": "Configuration and Development",
"items": [
{
"text": "Environment Variables",
"path": "framework/ui/react/environment-variables.md"
},
{
"text": "Unit Testing",
"path": "framework/ui/react/unit-testing.md"
}
]
},
{
"text": "Core Features",
"items": [
{
"text": "Authorization",
"path": "framework/ui/react/authorization.md"
},
{
"text": "Localization",
"path": "framework/ui/react/localization.md"
},
{
"text": "Permission Management",
"path": "framework/ui/react/permission-management.md"
},
{
"text": "HTTP Requests",
"path": "framework/ui/react/http-requests.md"
}
]
},
{
"text": "Customization and Components",
"items": [
{
"text": "Customization",
"path": "framework/ui/react/customization.md"
},
{
"text": "Components",
"path": "framework/ui/react/components/index.md",
"isIndex": true
}
]
},
{
"text": "Admin Console",
"path": "framework/ui/react/admin-console.md"
}
]
},
{
"text": "React Native",
"items": [
@ -1873,6 +1935,10 @@
"text": "Overview",
"path": "framework/ui/react-native",
"isIndex": true
},
{
"text": "Styling with NativeWind",
"path": "framework/ui/react-native/styling-with-nativewind.md"
}
]
},

11
docs/en/docs-params.json

@ -12,6 +12,17 @@
"NG": "Angular"
}
},
{
"name": "BlazorUI",
"displayName": "Blazor UI Library",
"values": {
"Blazorise": "Blazorise",
"MudBlazor": "MudBlazor"
},
"dependsOn": {
"UI": ["Blazor", "BlazorServer", "BlazorWebApp", "MAUIBlazor"]
}
},
{
"name": "DB",
"displayName": "Database",

4
docs/en/framework/infrastructure/background-jobs/index.md

@ -271,9 +271,9 @@ If multiple applications share the same storage for background jobs and workers
Set `ApplicationName` property in `AbpBackgroundJobWorkerOptions` to your application's name:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
public override void ConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpBackgroundJobWorkerOptions>(options =>
Configure<AbpBackgroundJobWorkerOptions>(options =>
{
options.ApplicationName = context.Services.GetApplicationName()!;
});

4
docs/en/framework/infrastructure/background-workers/index.md

@ -212,9 +212,9 @@ If multiple applications share the same storage for background jobs and workers
Set `ApplicationName` property in `AbpBackgroundJobWorkerOptions` to your application's name:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
public override void ConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpBackgroundJobWorkerOptions>(options =>
Configure<AbpBackgroundJobWorkerOptions>(options =>
{
options.ApplicationName = context.Services.GetApplicationName()!;
});

68
docs/en/framework/infrastructure/image-manipulation.md

@ -1,12 +1,12 @@
```json
//[doc-seo]
{
"Description": "Learn how to efficiently compress and resize images in your applications using ABP Framework's extensible services powered by ImageSharp and Magick.NET."
"Description": "Learn how to efficiently compress and resize images in your applications using ABP Framework's extensible services powered by ImageSharp, Magick.NET and SkiaSharp."
}
```
# Image Manipulation
ABP provides services to compress and resize images and implements these services with popular [ImageSharp](https://sixlabors.com/products/imagesharp/) and [Magick.NET](https://github.com/dlemstra/Magick.NET) libraries. You can use these services in your reusable modules, libraries and applications, so you don't depend on a specific imaging library.
ABP provides services to compress and resize images and implements these services with popular [ImageSharp](https://sixlabors.com/products/imagesharp/), [Magick.NET](https://github.com/dlemstra/Magick.NET) and [SkiaSharp](https://github.com/mono/SkiaSharp) libraries. You can use these services in your reusable modules, libraries and applications, so you don't depend on a specific imaging library.
> The image resizer/compressor system is designed to be extensible. You can implement your own image resizer/compressor contributor and use it in your application.
@ -46,10 +46,11 @@ public class YourModule : AbpModule
## Providers
ABP provides two image resizer/compressor implementations out of the box:
ABP provides three image resizer/compressor implementations out of the box:
* [Magick.NET](#magick-net-provider)
* [ImageSharp](#imagesharp-provider)
* [SkiaSharp](#skiasharp-provider)
You should install one of these provides to make it actually working.
@ -334,6 +335,67 @@ Configure<ImageSharpCompressOptions>(options =>
});
```
## SkiaSharp Provider
`Volo.Abp.Imaging.SkiaSharp` NuGet package implements the image operations using the [SkiaSharp](https://github.com/mono/SkiaSharp) library.
## Installation
You can add this package to your application by either using the [ABP CLI](../../cli) or manually installing it. Using the [ABP CLI](../../cli) is the recommended approach.
### Using the ABP CLI
Open a command line terminal in the folder of your project (.csproj file) and type the following command:
```bash
abp add-package Volo.Abp.Imaging.SkiaSharp
```
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.Imaging.SkiaSharp](https://www.nuget.org/packages/Volo.Abp.Imaging.SkiaSharp) NuGet package to your project:
```
dotnet add package Volo.Abp.Imaging.SkiaSharp
```
2. Add `AbpImagingSkiaSharpModule` to your [module](../architecture/modularity/basics.md)'s dependency list:
```csharp
[DependsOn(typeof(AbpImagingSkiaSharpModule))]
public class MyModule : AbpModule
{
//...
}
```
### Configuration
`SkiaSharpResizerOptions` is an [options object](../fundamentals/options.md) that is used to configure the SkiaSharp image resize system. It has the following properties:
* `SKSamplingOptions`: The sampling options used by SkiaSharp when resizing. (Default: `SKSamplingOptions.Default`)
* `Quality`: The quality of the encoded image (0-100). (Default: `75`)
`SkiaSharpCompressOptions` is an [options object](../fundamentals/options.md) that is used to configure the SkiaSharp image compression system. It has the following properties:
* `Quality`: The quality of the encoded image (0-100). (Default: `75`)
**Example usage:**
```csharp
Configure<SkiaSharpResizerOptions>(options =>
{
options.Quality = 80;
});
Configure<SkiaSharpCompressOptions>(options =>
{
options.Quality = 60;
});
```
## ASP.NET Core Integration
`Volo.Abp.Imaging.AspNetCore` NuGet package defines attributes for controller actions that can automatically compress and/or resize uploaded files.

2
docs/en/framework/infrastructure/text-templating/razor.md

@ -10,6 +10,8 @@
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.
## Installation

25
docs/en/framework/infrastructure/text-templating/scriban.md

@ -7,6 +7,31 @@
# Scriban Integration
## Safe Runtime (Sandbox)
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.

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:

98
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,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.
```razor
@using System.ComponentModel.DataAnnotations
<MudForm @ref="_form" @bind-IsValid="@_isValid" Model="@_model">
<MudStack Spacing="3">
<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>
</MudStack>
</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
`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).
{{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.Upload, 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.Upload, 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/framework/ui/index.md

@ -1,7 +1,7 @@
```json
//[doc-seo]
{
"Description": "Explore ABP's UI options, including MVC, Blazor, Angular, React Native, and MAUI, to build dynamic applications effortlessly."
"Description": "Explore ABP's UI options, including React, MVC, Blazor, Angular, React Native, and MAUI, to build dynamic applications effortlessly."
}
```
@ -9,6 +9,7 @@
ABP provides several options for building the user interface (UI) in your applications. Here are some of the officially supported UI options you can use with ABP:
* [React](./react/index.md) *(modern template system only)*
* [MVC / Razor Pages](./mvc-razor-pages/overall.md)
* [Blazor](./blazor/overall.md)
* [Angular](./angular/quick-start.md)

46
docs/en/framework/ui/mvc-razor-pages/overall.md

@ -17,8 +17,8 @@ ABP provides a convenient and comfortable way of creating web applications using
ASP.NET Core provides two models for UI development:
* **[MVC (Model-View-Controller)](https://docs.microsoft.com/en-us/aspnet/core/mvc/)** is the classic way that exists from the version 1.0. This model can be used to create UI pages/components and HTTP APIs.
* **[Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/)** was introduced with the ASP.NET Core 2.0 as a new way to create web pages.
- **[MVC (Model-View-Controller)](https://docs.microsoft.com/en-us/aspnet/core/mvc/)** is the classic way that exists from the version 1.0. This model can be used to create UI pages/components and HTTP APIs.
- **[Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/)** was introduced with the ASP.NET Core 2.0 as a new way to create web pages.
**ABP supports both** of the MVC and the Razor Pages models. However, it is suggested to create the **UI pages with Razor Pages** approach and use the **MVC model to build HTTP APIs**. So, all the pre-build modules, samples and the documentation is based on the Razor Pages for the UI development, while you can always apply the MVC pattern to create your own pages.
@ -32,18 +32,18 @@ The [application startup template](../../../solution-templates/application-modul
ABP provides a complete [Theming](theming.md) system with the following goals:
* Reusable [application modules](../../../modules) are developed **theme-independent**, so they can work with any UI theme.
* UI theme is **decided by the final application**.
* The theme is distributed via NuGet/NPM packages, so it is **easily upgradable**.
* The final application can **customize** the selected theme.
- Reusable [application modules](../../../modules) are developed **theme-independent**, so they can work with any UI theme.
- UI theme is **decided by the final application**.
- The theme is distributed via NuGet/NPM packages, so it is **easily upgradable**.
- The final application can **customize** the selected theme.
### Current Themes
Currently, three themes are **officially provided**:
* The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**.
* The [Lepton Theme](https://abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP](https://abp.io/) license.
* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](../../../ui-themes/lepton-x/mvc.md) and [lite](../../../ui-themes/lepton-x-lite/asp-net-core.md) choices.
- The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**.
- The [Lepton Theme](https://abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP](https://abp.io/) license.
- The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](../../../ui-themes/lepton-x/mvc.md) and [lite](../../../ui-themes/lepton-x-lite/asp-net-core.md) choices.
There are also some community-driven themes for the ABP (you can search on the web).
@ -72,7 +72,7 @@ You can use these libraries directly in your applications, without needing to ma
The themes provide the standard layouts. So, you have responsive layouts with the standard features already implemented. The screenshot below has taken from the Application Layout of the [Basic Theme](basic-theme.md):
![basic-theme-application-layout](../../../images/basic-theme-account-layout.png)
basic-theme-application-layout
See the [Theming](theming.md) document for more layout options and other details.
@ -90,13 +90,13 @@ Dynamic JavaScript API Client Proxy system allows you to consume your server sid
**Example: Get a list of authors from the server**
````js
```js
acme.bookStore.authors.author.getList({
maxResultCount: 10
}).then(function(result){
console.log(result.items);
});
````
```
`acme.bookStore.authors.author.getList` is an auto-generated function that internally makes an AJAX call to the server.
@ -108,7 +108,7 @@ ABP makes it easier & type safe to write Bootstrap HTML.
**Example: Render a Bootstrap modal**
````html
```html
<abp-modal>
<abp-modal-header title="Modal title" />
<abp-modal-body>
@ -116,7 +116,7 @@ ABP makes it easier & type safe to write Bootstrap HTML.
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Save|AbpModalButtons.Close)"></abp-modal-footer>
</abp-modal>
````
```
See the [Tag Helpers](tag-helpers) document for more.
@ -126,9 +126,9 @@ ABP provides `abp-dynamic-form` and `abp-input` tag helpers to dramatically simp
**Example: Use `abp-dynamic-form` to create a complete form based on a model**
````html
```html
<abp-dynamic-form abp-model="Movie" submit-button="true" />
````
```
See the [Forms & Validation](forms-validation.md) document for details.
@ -136,14 +136,14 @@ See the [Forms & Validation](forms-validation.md) document for details.
ABP provides a flexible and modular Bundling & Minification system to create bundles and minify style/script files on runtime.
````html
```html
<abp-style-bundle>
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
<abp-style src="/styles/my-global-style.css" />
</abp-style-bundle>
````
```
Also, Client Side Package Management system offers a modular and consistent way of managing 3rd-party library dependencies.
@ -157,11 +157,11 @@ See the [Bundling & Minification](bundling-minification.md) and [Client Side Pac
ABP provides a lot of built-in solutions to common application requirements;
* [Widget System](widgets.md) can be used to create reusable widgets & create dashboards.
* [Page Alerts](page-alerts.md) makes it easy to show alerts to the user.
* [Modal Manager](modals.md) provides a simple way to build and use modals.
* [Data Tables](data-tables.md) integration makes straightforward to create data grids.
- [Widget System](widgets.md) can be used to create reusable widgets & create dashboards.
- [Page Alerts](page-alerts.md) makes it easy to show alerts to the user.
- [Modal Manager](modals.md) provides a simple way to build and use modals.
- [Data Tables](data-tables.md) integration makes straightforward to create data grids.
## Customization
There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](customization-user-interface.md).
There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](customization-user-interface.md).

140
docs/en/framework/ui/react-native/index.md

@ -5,12 +5,12 @@
}
```
````json
```json
//[doc-params]
{
"Architecture": ["Monolith", "Tiered", "Microservice"]
}
````
```
# Getting Started with React Native
@ -18,7 +18,7 @@
The ABP platform provides a basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated with your ABP-based backends**.
![React Native gif](../../../images/react-native-introduction.gif)
> The startup template UI is built with **[NativeWind v4](https://www.nativewind.dev/)** (Tailwind CSS for React Native) on top of a shadcn-inspired neutral palette, with full **light/dark mode** support. See [Styling with NativeWind](styling-with-nativewind.md) for the styling system reference.
## How to Prepare Development Environment
@ -28,10 +28,9 @@ Please follow the steps below to prepare your development environment for React
2. **[Optional] Install Yarn:** You can install Yarn v1 (not v2) by following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 provides a better developer experience compared to npm v6 and below. You can skip this step and use npm, which is built into Node.js.
3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE that works seamlessly with TypeScript. While you can use any IDE, including Visual Studio or Rider, VS Code typically provides the best developer experience for React Native projects.
4. **[Optional] Install an Emulator/Simulator:** If you want to test on Android emulators or iOS simulators (instead of using the Web View method), you'll need to install one of the following:
- **Android Studio & Emulator:** Install [Android Studio](https://developer.android.com/studio) and set up an Android Virtual Device (AVD) through the AVD Manager. You can follow the [Android Studio Emulator guide](https://docs.expo.dev/workflow/android-studio-emulator/) on expo.io documentation.
- **Xcode & iOS Simulator:** On macOS, install [Xcode](https://developer.apple.com/xcode/) from the App Store, which includes the iOS Simulator. You can follow the [iOS Simulator guide](https://docs.expo.dev/workflow/ios-simulator/) on expo.io documentation.
> **Note:** The Web View method (recommended for quick testing) doesn't require an emulator or simulator. If you prefer a CLI-based approach for Android, you can check the [setting up android emulator without android studio](setting-up-android-emulator.md) guide as an alternative.
- **Android Studio & Emulator:** Install [Android Studio](https://developer.android.com/studio) and set up an Android Virtual Device (AVD) through the AVD Manager. You can follow the [Android Studio Emulator guide](https://docs.expo.dev/workflow/android-studio-emulator/) on expo.io documentation.
- **Xcode & iOS Simulator:** On macOS, install [Xcode](https://developer.apple.com/xcode/) from the App Store, which includes the iOS Simulator. You can follow the [iOS Simulator guide](https://docs.expo.dev/workflow/ios-simulator/) on expo.io documentation.
> **Note:** The Web View method (recommended for quick testing) doesn't require an emulator or simulator. If you prefer a CLI-based approach for Android, you can check the [setting up android emulator without android studio](setting-up-android-emulator.md) guide as an alternative.
## How to Start a New React Native Project
@ -41,7 +40,7 @@ You have multiple options to initiate a new React Native project that works with
ABP Studio is the most convenient and flexible way to create a React Native application based on the ABP framework. Follow the [tool documentation](../../../studio) and select the option below:
![React Native option](../../../images/react-native-option.png)
React Native option
### 2. Using ABP CLI
@ -61,7 +60,6 @@ This command creates a solution containing an **Angular** or **MVC** project (de
Before running the React Native application, install the dependencies by running `yarn install` or `npm install` in the `react-native` directory.
### Web View (Recommended - Quickest Method)
The quickest way to test the application is by using the web view. While testing on a physical device is also supported, we recommend using [local HTTPS development](https://docs.expo.dev/guides/local-https-development/) as it requires fewer backend modifications.
@ -69,28 +67,21 @@ The quickest way to test the application is by using the web view. While testing
Follow these steps to set up the web view:
1. Navigate to the `react-native` directory and start the application by running:
```bash
```bash
yarn web
```
```
2. Generate SSL certificates by running the following command in a separate directory:
```bash
```bash
mkcert localhost
```
```
3. Set up the local proxy by running:
```bash
```bash
yarn create:local-proxy
```
```
The default port is `443`. To use a different port, specify the `SOURCE_PORT` environment variable:
```bash
SOURCE_PORT=8443 yarn create:local-proxy
```
4. If you changed the port in the previous step, update the `apiUrl` in `Environment.ts` accordingly.
5. Update the mobile application settings in the database and re-run the migrations. If you specified a custom port, ensure the port is updated in the configuration as well:
```json
```json
"OpenIddict": {
"Applications": {
"MyApplication_Mobile": {
@ -99,7 +90,7 @@ Follow these steps to set up the web view:
}
}
}
```
```
### Running on Emulator/Simulator
@ -111,17 +102,17 @@ If you prefer to test on an Android emulator or iOS simulator, you'll need to co
{{ if Architecture == "Monolith" }}
![react native monolith environment local IP](../../../images/react-native-monolith-environment-local-ip.png)
react native monolith environment local IP
{{ else if Architecture == "Tiered" }}
![react native tiered environment local IP](../../../images/react-native-tiered-environment-local-ip.png)
react native tiered environment local IP
> Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.HttpApi.Host` or `.Web` project.
{{ else }}
![react native microservice environment local IP](../../../images/react-native-environment-local-ip.png)
react native microservice environment local IP
> Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.AuthServer` project.
@ -131,25 +122,91 @@ If you prefer to test on an Android emulator or iOS simulator, you'll need to co
> The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app, and it includes many features.
![expo-cli-options](../../../images/rn-options.png)
expo-cli-options
In the image above, you can start the application on an Android emulator, an iOS simulator, or a physical phone by scanning the QR code with the [Expo Client](https://expo.io/tools#client) or by choosing the corresponding option.
### Expo
![React Native login screen on iPhone 16](../../../images/rn-login-iphone.png)
Press **i** to open the iOS simulator, or scan the QR code from the Expo CLI with your phone to run on a physical device.
### Android Studio
1. Start the emulator in **Android Studio** before running the `yarn start` or `npm start` command.
2. Press **a** to open in Android Studio.
![React Native login screen on Android Device](../../../images/rn-login-android-studio.png)
<img width="360" src="../../../images/rn-login-iphone.png" alt="React Native login screen" />
Enter **admin** as the username and **1q2w3E\*** as the password to log in to the application.
Enter **admin** as the username and **1q2w3E** as the password to log in to the application.
The application is up and running. You can continue to develop your application based on this startup template.
## Navigation
The startup template ships with **two navigation styles**, switchable when the project is created:
- **Bottom Tab** — *the default* — three tabs at the bottom of the screen: **Home**, **Settings** and **Account**.
- **Drawer** — a side menu (hamburger) with two items: **Home** and **Settings**.
<img width="600" src="../../../images/rn-nav-comparison.png" alt="Bottom Tab vs Drawer navigation comparison" />
Every main tab or drawer item is wired to **its own** native stack (`@react-navigation/native-stack`). Pushing more screens stays on that branch: the Back stack belongs to that tab or drawer route and does not mix with others. Bottom Tab and Drawer use the **same screen components**; they differ in how those screens are grouped and opened from the outer shell (and where the sign‑in/sign‑up flow lives in Bottom Tab versus Drawer).
> **How to choose:** The mode is selected in **ABP Studio** during the *Mobile Framework* step. Switching modes after the project is generated is not a one-line change — you would need to add the missing navigator (and its `@react-navigation/drawer` or `@react-navigation/bottom-tabs` dependency) manually, then update `src/AppContainer.tsx` and `src/navigators/types.ts` to match. Pick the mode upfront when possible.
### Bottom Tab Navigation (default)
The root navigator is `BottomTabNavigator` (`src/navigators/BottomTabNavigator.tsx`) with three stacks:
- **HomeTab** → `HomeNavigator` → `HomeScreen` (hero greeting + feature cards).
- **SettingsTab** → `SettingsNavigator` → `SettingsScreen` (language, theme, profile/password shortcuts).
- **AccountTab** → `AccountNavigator` — *conditional stack* based on the authentication state read from Redux:
- **Authenticated:** `AccountScreen` → `ChangePasswordScreen`, `ProfilePictureScreen`.
- **Guest:** `LoginScreen` → `RegisterScreen`, `ForgotPasswordScreen`, `ResetPasswordScreen`.
Tab bar colors (active/inactive tint, background, border) are sourced from the `useThemeColors` hook so the bar follows the active light/dark theme.
#### The Account Screen
`AccountScreen` (`src/screens/Account/AccountScreen.tsx`) is the home of the AccountTab when the user is signed in. Its layout follows an iOS-style grouped pattern:
1. **Profile header** — circular avatar (profile picture or first-letter fallback), full name and email, centered at the top.
2. **Account actions card** — a single rounded card containing two rows with leading icon chips:
- **Profile Picture** → navigates to `ProfilePictureScreen`.
- **Change Password** → navigates to `ChangePasswordScreen`.
3. **Destructive logout button** — an outlined `destructive`-colored button that calls the `useLogout` hook.
### Drawer Navigation (alternative)
When the drawer mode is selected, `DrawerNavigator` (`src/navigators/DrawerNavigator.tsx`) replaces the bottom tabs. It exposes two drawer items:
- **HomeStack** → `HomeNavigator` → `HomeScreen`, plus the auth flow (`LoginScreen`, `RegisterScreen`, `ForgotPasswordScreen`, `ResetPasswordScreen`).
- **SettingsStack** → `SettingsNavigator` → `SettingsScreen`, `ChangePasswordScreen`, `ProfilePictureScreen`.
Note that there is **no `AccountTab` / `AccountScreen` in drawer mode** — auth lives in the Home stack and profile/password actions live in the Settings stack. The drawer side panel itself is fully custom.
#### The Drawer Content
`DrawerContent` (`src/components/DrawerContent/DrawerContent.tsx`) is the custom side panel rendered by `DrawerNavigator` via the `drawerContent` prop. From top to bottom:
1. **User header** — circular avatar (image or first-letter fallback) + full name + email when authenticated.
2. **Divider**.
3. **Navigation items** — Home and Settings rows with leading Ionicons; tapping navigates and closes the drawer.
4. **Auth row** — when authenticated, a **Logout** row that calls `useLogout`; when guest, a **Login** row that navigates to the Login screen inside `HomeStack`.
The whole panel uses NativeWind classes with `dark:` variants, so it follows the active theme automatically.
### Adding a New Screen
To add a screen to either navigation mode:
1. Create the screen component under `src/screens/<FeatureName>/<FeatureName>Screen.tsx` and export it from `src/screens/index.ts`.
2. Register it as a `Stack.Screen` inside the appropriate navigator (e.g. `HomeNavigator`, `SettingsNavigator`, or `AccountNavigator`).
3. Add the route to the matching `*ParamList` in `src/navigators/types.ts` so the screen props stay typed.
If the new screen needs to appear at the *root* level (a new tab or drawer item rather than a child of an existing stack), edit `BottomTabNavigator.tsx` or `DrawerNavigator.tsx` and update the corresponding `BottomTabParamList` / `RootDrawerParamList` type.
## How to Configure & Run the Backend (Required for Emulator/Simulator Testing)
> React Native application does not trust the auto-generated .NET HTTPS certificate. You should use **HTTP** during the development.
@ -172,11 +229,10 @@ A React Native application running on an Android emulator or a physical phone **
{{ if Architecture == "Monolith" }}
![React Native monolith host project configuration](../../../images/react-native-monolith-be-config.png)
React Native monolith host project configuration
- Open the `appsettings.json` file in the `.DbMigrator` folder. Replace the `localhost` address in the `RootUrl` property with your local IP address. Then, run the database migrator.
- Open the `appsettings.Development.json` file in the `.HttpApi.Host` folder. Add this configuration to accept global requests for testing the React Native application in the development environment.
```json
{
"Kestrel": {
@ -191,11 +247,10 @@ A React Native application running on an Android emulator or a physical phone **
{{ else if Architecture == "Tiered" }}
![React Native tiered project configuration](../../../images/react-native-tiered-be-config.png)
React Native tiered project configuration
- Open the `appsettings.json` file in the `.DbMigrator` folder. Replace the `localhost` address in the `RootUrl` property with your local IP address. Then, run the database migrator.
- Open the `appsettings.Development.json` file in the `.AuthServer` folder. Add this configuration to accept global requests for testing the React Native application in the development environment.
```json
{
"Kestrel": {
@ -207,9 +262,7 @@ A React Native application running on an Android emulator or a physical phone **
}
}
```
- Open the `appsettings.Development.json` file in the `.HttpApi.Host` folder. Add this configuration to accept global requests. Additionally, you need to configure the authentication server as mentioned above.
```json
{
"Kestrel": {
@ -230,10 +283,9 @@ A React Native application running on an Android emulator or a physical phone **
{{ else if Architecture == "Microservice" }}
![React Native microservice project configuration](../../../images/react-native-microservice-be-config.png)
React Native microservice project configuration
- Open the `appsettings.Development.json` file in the `.AuthServer` folder. Add this configuration to accept global requests for testing the React Native application in the development environment.
```json
{
"App": {
@ -248,9 +300,7 @@ A React Native application running on an Android emulator or a physical phone **
}
}
```
- Open the `appsettings.Development.json` file in the `.AdministrationService` folder. Add this configuration to accept global requests for testing the React Native application in the development environment. You should also provide the authentication server configuration. Additionally, you need to apply the same process for all services you will use in the React Native application.
```json
{
"App": {
@ -271,9 +321,7 @@ A React Native application running on an Android emulator or a physical phone **
}
}
```
- Update the `appsettings.json` file in the `.IdentityService` folder. Replace the `localhost` configuration with your local IP address for the React Native application.
```json
{
//...
@ -292,9 +340,7 @@ A React Native application running on an Android emulator or a physical phone **
}
}
```
- Finally, update the mobile gateway configurations as follows:
```json
//gateways/mobile/MyMicroserviceProject.MobileGateway/Properties/launchSettings.json
{
@ -319,7 +365,6 @@ A React Native application running on an Android emulator or a physical phone **
}
}
```
```json
//gateways/mobile/MyMicroserviceProject.MobileGateway/appsettings.json
{
@ -367,7 +412,6 @@ A React Native application running on an Android emulator or a physical phone **
}
}
```
{{ end }}
Run the backend application(s) as described in the [getting started document](../../../get-started).
Run the backend application(s) as described in the [getting started document](../../../get-started).

159
docs/en/framework/ui/react-native/styling-with-nativewind.md

@ -0,0 +1,159 @@
```json
//[doc-seo]
{
"Description": "Learn how the ABP React Native startup template is styled with NativeWind v4 (Tailwind CSS for React Native), including the semantic color tokens, dark mode and theme customization."
}
```
# Styling with NativeWind
The ABP React Native startup template uses **[NativeWind v4](https://www.nativewind.dev/)** — Tailwind CSS for React Native. Most components are styled through utility `className` props that are compiled at build time, so there is almost no styling runtime cost. The design system is based on the **shadcn/ui** neutral palette and supports **light and dark themes** out of the box.
> This page documents the styling system that ships with the template. For the general getting-started flow (installing tools, running the app, configuring the backend), see [Getting Started with React Native](index.md).
<img width="360" src="../../../images/rn-home-screen.png" alt="HomeScreen built with NativeWind" />
---
## 1. Project Files
NativeWind is wired in through a small set of configuration files at the root of the React Native project:
| File | Purpose |
|------|---------|
| `tailwind.config.js` | Defines the design tokens (colors, spacing, border radius), enables `darkMode: 'class'`, and registers the NativeWind preset. This is the source of truth for the theme. |
| `global.css` | Tailwind entry point with the three base directives (`@tailwind base; @tailwind components; @tailwind utilities;`). |
| `metro.config.js` | Wraps the default Expo Metro config with `withNativeWind(...)` and points it at `global.css`. |
| `babel.config.js` | Adds the `nativewind/babel` preset and sets `jsxImportSource: 'nativewind'` on `babel-preset-expo` so JSX understands the `className` prop. |
| `nativewind-env.d.ts` | TypeScript triple-slash reference (`/// <reference types="nativewind/types" />`) that adds `className` typings to React Native components. |
All of these files come pre-configured in the template. You normally only need to edit `tailwind.config.js` to customize the theme.
---
## 2. Semantic Color Tokens
Instead of raw color names like `bg-zinc-900`, the template exposes a small set of **semantic tokens** that follow the shadcn/ui convention. Each token has a default (light) value and a `dark` variant, and many also carry a paired `foreground` color for text/icons placed on top.
| Token | Use it for |
|-------|-----------|
| `background` / `foreground` | Screen background and primary text. |
| `card` / `card-border` | Surface containers (settings rows, feature cards) and their 1px border. |
| `primary` / `primary-foreground` | Filled call-to-action buttons. |
| `secondary` / `secondary-foreground` | Subtle filled elements such as icon chips inside cards. |
| `muted` / `muted-foreground` | Backgrounds and text for secondary information (subtitles, helper text). |
| `accent` / `accent-foreground` | Highlighted/active state — also used as the active tint of the bottom tab bar. |
| `destructive` / `destructive-foreground` | Delete/danger actions and error states. |
| `border` | Generic separators and outline borders. |
| `input` | Border color for text inputs (Paper `TextInput` outline). |
| `ring` | Focus ring/outline color. |
All token values live in `tailwind.config.js` under `theme.extend.colors`. Refer to that file for the exact hex values.
The template also extends Tailwind's spacing and border-radius scales so paddings and corners stay consistent across screens:
* **Spacing:** `xs` (4px), `sm` (8px), `md` (16px), `lg` (24px), `xl` (32px) — usable as `p-md`, `mt-lg`, `gap-sm`, etc.
* **Border radius:** `rounded-sm` (4px), `rounded-md` (8px), `rounded-lg` (12px), `rounded-xl` (16px), `rounded-2xl` (20px).
---
## 3. Dark Mode
Dark mode is enabled with `darkMode: 'class'` in `tailwind.config.js` and is driven by the device color scheme via NativeWind's `useColorScheme()` hook. Each color token has a sibling `*-dark` (and where applicable `*-dark-foreground`) variant, and components opt into it with the `dark:` prefix on their classes:
```tsx
<View className="flex-1 bg-background dark:bg-background-dark">
<Text className="text-foreground dark:text-foreground-dark">
Hello
</Text>
</View>
```
> **Pattern note.** The token shape used in this template is `{ DEFAULT, dark }` (and `{ foreground, 'dark-foreground' }` where applicable), so the dark-mode class is `dark:bg-background-dark` — not `dark:bg-background`. Follow this convention when you add new screens so the theme switch behaves consistently across the app.
The user can also switch themes manually from the **Settings** screen (system / light / dark), and the choice is persisted via Redux.
<img width="360" src="../../../images/rn-home-screen.png" alt="HomeScreen with light and dark theme" />
---
## 4. The `useThemeColors` Hook
A handful of APIs in the template do not accept a `className` — they need a *color value*. Examples:
* `react-native-paper`'s `TextInput` (which the template still uses for outlined inputs and validation styling).
* React Navigation's `screenOptions` (header background, tab bar tint, etc.).
* The native status bar.
For these cases the template ships a `useThemeColors` hook at `src/hooks/UseThemeColors.ts`. It returns theme-aware values that mirror the Tailwind tokens:
```ts
const {
primaryContainer, // Paper TextInput surface
headerBg, // Navigator headers
headerText,
iconColor, // Inactive icon tint
accentColor, // Active icon tint / focused tab
destructiveColor,
inputBorderColor,
} = useThemeColors();
```
**Rule of thumb:** prefer `className` with `dark:` variants whenever the component supports it; reach for `useThemeColors` only for the components listed above.
---
## 5. React Native Paper
`react-native-paper` is still in the template's `package.json`, but its usage has been narrowed down to a single component: **`TextInput`** in outlined mode (used for forms because of its strong validation/error UI). Buttons, lists, modals, drawers, and icons are all NativeWind + `@expo/vector-icons` (Ionicons) now.
If you add new forms, follow the same split:
* Use Paper's `TextInput` (with values from `useThemeColors`) for text fields.
* Use plain `View`/`Text`/`Pressable` with NativeWind classes for everything else.
---
## 6. Customizing the Theme
Most customization happens in `tailwind.config.js`. To introduce a brand color, extend the `colors` map and reuse the `{ DEFAULT, dark }` shape so the `dark:` variants keep working:
```js
// tailwind.config.js
module.exports = {
// ...
theme: {
extend: {
colors: {
// ...
brand: {
DEFAULT: '#2563eb',
dark: '#3b82f6',
foreground: '#ffffff',
'dark-foreground': '#ffffff',
},
},
},
},
};
```
You can then use it from any component:
```tsx
<Pressable className="bg-brand dark:bg-brand-dark rounded-lg px-md py-sm">
<Text className="text-brand-foreground dark:text-brand-dark-foreground font-semibold">
Get started
</Text>
</Pressable>
```
If the new color also needs to be reachable from non-`className` APIs (Paper, navigators, status bar), add a matching entry to `useThemeColors` so the rest of the template stays consistent.
---
## 7. Going Further
* [NativeWind documentation](https://www.nativewind.dev/) — class reference, advanced features (variants, animations).
* [Tailwind CSS documentation](https://tailwindcss.com/docs) — utility classes, responsive design, theming.
* [shadcn/ui](https://ui.shadcn.com/) — the design system the color tokens are inspired by.

191
docs/en/framework/ui/react/admin-console.md

@ -0,0 +1,191 @@
```json
//[doc-seo]
{
"Description": "Learn how the ABP Admin Console works with React UI applications and how it is hosted under /admin-console."
}
```
# Admin Console
The **ABP Admin Console** is the React-based administration UI for ABP applications. It provides management pages for ABP modules and is available in React UI solutions created with ABP Studio v3.0+ or `abp new --modern --ui-framework react`.
The Admin Console is delivered as the `Volo.Abp.AdminConsole` NuGet package for layered and single-layer solutions. In microservice solutions, the template also includes a standalone `apps/react-admin-console/` React app.
## What It Provides
The Admin Console contains administration pages for the ABP modules included in the host application. Module pages are activated based on the backend services available in the host, so a solution only shows pages for modules it actually has.
The built-in module areas include:
| Module | Notes |
| --- | --- |
| Identity Pro | User, role, claim, and organization unit management when Identity services are available. |
| Account Pro | Account management pages and account-related flows. |
| OpenIddict | Application and scope management when OpenIddict services are available. |
| Audit Logging UI | Optional. Visible when Audit Logging services are available. |
| AI Management | Optional. Visible when AI Management services are available. |
| Text Template Management | Optional. Visible when Text Template Management services are available. |
Other module pages, such as Setting Management, SaaS, GDPR, or customization pages, can also be available depending on the solution and installed modules.
## Hosting Model
The Admin Console is served under:
```text
/admin-console/*
```
API endpoints used by the Admin Console are served under:
```text
/admin-console/api/*
```
The `Volo.Abp.AdminConsole` package embeds the built React SPA under `wwwroot/admin-console/` and registers it with ABP's Virtual File System. `AdminConsoleSpaMiddleware` then serves static assets and falls back to `index.html` for client-side routes.
The middleware deliberately lets `/admin-console/api/*` requests pass through to MVC controllers.
## Layered and Single-Layer Templates
For layered and single-layer modern templates:
- The developer-owned React app is in the `react/` folder.
- The Admin Console UI is embedded in the backend through the `Volo.Abp.AdminConsole` NuGet package.
- There is no separate `react-admin-console/` source folder in the generated solution.
- The backend host serves Admin Console pages under `/admin-console/*`.
Example URL:
```text
https://localhost:44300/admin-console/
```
The main React app links to the Admin Console through `getAdminConsoleUrl()`.
## Microservice Template
For the microservice modern template:
- The main React app is in `apps/react/`.
- The Admin Console app is in `apps/react-admin-console/`.
- Both are served through the Web Gateway.
- The Admin Console has its own OpenIddict client, normally `<ProjectName>_AdminConsole`.
The main React app uses `adminConsoleUrl` from `dynamic-env.json` to open the Admin Console origin and `/admin-console` base path.
## Module Discovery
The Admin Console calls:
```text
GET /admin-console/api/modules
```
The backend checks for module application service contracts and returns which module areas are available. The discovery keys include:
| Key | Backend service check |
| --- | --- |
| `identity` | `Volo.Abp.Identity.IIdentityUserAppService` |
| `saas` | `Volo.Saas.Host.ITenantAppService` |
| `auditLogging` | `Volo.Abp.AuditLogging.IAuditLogsAppService` |
| `gdpr` | `Volo.Abp.Gdpr.IGdprRequestAppService` |
| `openIddict` | `Volo.Abp.OpenIddict.Applications.IApplicationAppService` |
| `textTemplateManagement` | `Volo.Abp.TextTemplateManagement.TextTemplates.ITemplateDefinitionAppService` |
| `aiManagement` | AI Management service contracts, with a legacy AI engine fallback. |
`settingManagement` is always returned as available by the discovery endpoint, while access to pages is still controlled by permissions.
## Configuration Endpoint
The Admin Console also uses:
```text
GET /admin-console/api/config
```
This endpoint provides Admin Console runtime settings such as authority, client ID, scopes, application name, customization options, and localization language configuration.
Host applications can configure Admin Console options from the `AdminConsole` configuration section or by configuring `AbpAdminConsoleOptions`.
## Configuring the Admin Console
In layered and single-layer modern React templates, the embedded Admin Console is configured from the backend host application's `appsettings.json` file. The generated template includes an `AdminConsole` section similar to the following:
```json
{
"AdminConsole": {
"IsEnabled": true,
"RedirectRootToAdminConsole": true,
"Authority": "https://localhost:44300",
"ClientId": "Acme_BookStore_AdminConsole",
"Scope": "openid profile email offline_access Acme_BookStore",
"LocalizationLanguages": [ "en", "tr" ],
"ThemeOverrideCssPath": "/theme-override.css",
"InitialTheme": "system",
"CustomizationPermissionName": "AdminConsole.Customization"
}
}
```
You can also configure the same values in the module class with `AbpAdminConsoleOptions`:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAdminConsoleOptions>(options =>
{
options.IsEnabled = true;
options.RedirectRootToAdminConsole = true;
options.Authority = "https://localhost:44300";
options.ClientId = "Acme_BookStore_AdminConsole";
options.Scope = "openid profile email offline_access Acme_BookStore";
options.LocalizationLanguages = new[] { "en", "tr" };
options.ThemeOverrideCssPath = "/theme-override.css";
options.InitialTheme = "system";
options.CustomizationPermissionName = "AdminConsole.Customization";
});
}
```
The most commonly changed options are:
| Option | Description |
| --- | --- |
| `IsEnabled` | Enables or disables the embedded Admin Console SPA middleware. |
| `RedirectRootToAdminConsole` | Redirects the backend root path (`/`) to `/admin-console`. |
| `Authority` | OpenID Connect authority URL. If it is `null`, the host origin is used. |
| `ClientId` | OpenIddict client ID used by the Admin Console SPA. |
| `Scope` | Space-separated OAuth scopes requested by the Admin Console. |
| `LocalizationLanguages` | UI language codes exposed to the Admin Console. If empty, the frontend falls back to `en`. |
| `ThemeOverrideCssPath` | Optional CSS path or absolute URL injected into the Admin Console HTML. |
| `InitialTheme` | Initial theme behavior: `light`, `dark`, `system`, or `both`. |
| `CustomizationPermissionName` | Permission required to show and use the Admin Console customization page. If not set, customization is disabled. |
The `ApplicationName`, `LogoUrl`, `InitialTheme`, and `ThemeOverrideCssPath` values can also be changed from the Admin Console customization UI when `CustomizationPermissionName` is configured and the current user has that permission. Values saved from the customization UI are stored as settings and override the defaults from configuration.
In microservice solutions, the Admin Console is a separate React app under `apps/react-admin-console/`. It still uses its own OpenIddict client (`<ProjectName>_AdminConsole`) and runtime configuration, while the backend exposes the same `/admin-console/api/config` and `/admin-console/api/modules` endpoints.
## Permissions
Admin Console routes still require permissions. For example:
- Identity pages use `AbpIdentity.*` permissions.
- OpenIddict pages use `OpenIddictPro.Application` and `OpenIddictPro.Scope`.
- Audit Logging uses `AuditLogging.AuditLogs`.
- Text Template Management uses `TextTemplateManagement.*`.
- AI Management uses `AIManagement.*`.
The main React app's Admin Console menu item only requires authentication. The Admin Console performs detailed permission checks for its own pages.
## Customization
The developer-owned React app is intended for application-specific pages. The Admin Console is an ABP-managed administration surface and should normally be updated by updating ABP packages.
For layered and single-layer hosts, the package supports host-side options such as application name, localization languages, and theme override CSS path. For larger UI changes, prefer building your own pages in the main React app or extending the backend modules through supported ABP extension points.
## See Also
- [React UI](./index.md)
- [Environment Variables](./environment-variables.md)
- [Permission Management](./permission-management.md)

183
docs/en/framework/ui/react/authorization.md

@ -0,0 +1,183 @@
```json
//[doc-seo]
{
"Description": "Learn how authentication and authorization are configured in ABP React UI applications."
}
```
# Authorization in React UI
OAuth is preconfigured in ABP React UI templates. When you create a React solution with ABP Studio v3.0+ or `abp new --modern --ui-framework react`, the template includes OpenID Connect settings, an OpenIddict client, route guards, and authentication hooks.
The React app authenticates against the ABP Auth Server using the **Authorization Code flow with PKCE**, which is the recommended flow for browser-based applications.
## Packages
The template uses these packages for authentication:
| Package | Purpose |
| --- | --- |
| `@volo/abp-oidc-auth` | Framework-agnostic OIDC client helpers for ABP/OpenIddict backends. |
| `@volo/abp-react-oidc-auth` | React adapter for the ABP OIDC client. |
| `oidc-client-ts` | Underlying OIDC protocol implementation. |
The package list also includes `@volo/abp-app-config` and `@volo/abp-react-app-config`, which are used to fetch application configuration and permissions after authentication.
## OAuth Configuration
The OIDC settings are resolved from runtime configuration first and fall back to `src/env.ts`.
```ts
export function getOAuthConfig(): {
issuer: string
redirectUri: string
clientId: string
scope: string
responseType: 'code'
} {
return {
issuer: loadedConfig?.oAuthConfig?.issuer ?? env.oauth.issuer,
redirectUri: loadedConfig?.oAuthConfig?.redirectUri ?? env.oauth.redirectUri,
clientId: loadedConfig?.oAuthConfig?.clientId ?? env.oauth.clientId,
scope: loadedConfig?.oAuthConfig?.scope ?? env.oauth.scope,
responseType: 'code',
}
}
```
The important configuration values are:
- `oAuthConfig.issuer`: Auth Server / OpenIddict authority URL.
- `oAuthConfig.redirectUri`: URL where the Auth Server redirects after login.
- `oAuthConfig.clientId`: OpenIddict client ID, normally `<ProjectName>_App`.
- `oAuthConfig.scope`: Scopes requested by the React app.
See [Environment Variables](./environment-variables.md) for the full runtime configuration model.
## Initializing Authentication
The app loads runtime configuration before initializing OIDC:
```tsx
async function bootstrap() {
await loadRuntimeConfig()
initUserManager()
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
)
}
```
`initUserManager()` creates the ABP React OIDC client:
```ts
client = createAbpReactOidcAuth({
authority: config.issuer,
clientId: config.clientId,
redirectUri: config.redirectUri,
postLogoutRedirectUri: config.redirectUri,
scope: config.scope,
responseType: config.responseType,
automaticSilentRenew: true,
userStoreType: 'localStorage',
userStorePrefix: `oidc.${config.clientId}`,
silentRedirectUri: `${window.location.origin}/silent-renew.html`,
})
```
The template stores the OIDC user in local storage and enables silent renewal with `public/silent-renew.html`.
## Auth Provider and Hook
`AuthProvider` wraps the app and handles the OIDC callback:
```tsx
export function AuthProvider({ children }: { children: ReactNode }) {
const authClient = getAuthClient()
useEffect(() => {
const params = new URLSearchParams(window.location.search)
if (!params.has('code') || !params.has('state')) return
void authClient.handleSigninCallback().then(() =>
window.history.replaceState({}, document.title, window.location.pathname)
)
}, [])
return <authClient.AuthProvider>{children}</authClient.AuthProvider>
}
```
Use `useAuth()` in components:
```tsx
import { useAuth } from '@/lib/auth/AuthContext'
export function LoginButton() {
const { isAuthenticated, isLoading, login, logout, user } = useAuth()
if (isLoading) return null
return isAuthenticated ? (
<button onClick={() => void logout()}>{user?.name ?? 'Logout'}</button>
) : (
<button onClick={() => void login()}>Login</button>
)
}
```
## Route Protection
The React template uses TanStack Router. Protected routes use `beforeLoad` guards.
```ts
const identityLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/identity',
component: IdentityLayout,
beforeLoad: authGuard,
})
```
`authGuard` checks the current OIDC user and redirects unauthenticated users to the Auth Server:
```ts
export async function authGuard({ location }: GuardContext) {
const user = await userManager.getUser()
if (!user || user.expired) {
await userManager.signinRedirect({
state: { returnUrl: location.href },
})
throw new Error('Redirecting to login')
}
}
```
Routes that also require a permission use `createPermissionGuard`:
```ts
const usersRoute = createRoute({
getParentRoute: () => identityLayoutRoute,
path: 'users',
component: UsersPage,
beforeLoad: createPermissionGuard('AbpIdentity.Users'),
})
```
Permission checks are explained in [Permission Management](./permission-management.md).
## OpenIddict Clients
The generated OpenIddict clients depend on the template:
- Layered and single-layer modern templates use the main React client, normally `<ProjectName>_App`.
- Microservice modern templates also include an Admin Console client, normally `<ProjectName>_AdminConsole`, because the Admin Console is a separate React app.
If you change URLs after generation, update both the runtime configuration and the corresponding OpenIddict client redirect URLs.
## See Also
- [Environment Variables](./environment-variables.md)
- [Permission Management](./permission-management.md)
- [Authorization](../../../framework/fundamentals/authorization/index.md)

184
docs/en/framework/ui/react/components/index.md

@ -0,0 +1,184 @@
```json
//[doc-seo]
{
"Description": "Learn about the component architecture and UI libraries used by ABP React UI applications."
}
```
# Components
ABP React UI templates use a source-owned component architecture. The generated app includes shadcn/ui-style primitives, layout components, feature components, route pages, and shared infrastructure under `src/lib/`.
The goal is to give you a working React application that you can customize without replacing framework-owned black boxes.
## Component Structure
The main React app is organized like this:
```text
src/
├── components/
│ ├── layout/
│ ├── ui/
│ └── identity/
├── lib/
│ ├── api/
│ ├── auth/
│ ├── i18n/
│ ├── routing/
│ └── theme/
├── locales/
├── pages/
└── routes/
```
The exact folders can vary by selected template options and modules.
## UI Stack
The React template uses:
| Library | Purpose |
| --- | --- |
| React | UI rendering. |
| Vite | Build tool and development server. |
| TanStack Router | Client-side routing. |
| TanStack Query | Server state, queries, mutations, and cache invalidation. |
| shadcn/ui-style components | Source-owned UI primitives built on Radix UI and Tailwind CSS. |
| Radix UI | Accessible low-level UI primitives. |
| Tailwind CSS | Utility-first styling and design tokens. |
| React Hook Form | Form state management. |
| Zod | Form and DTO validation schemas. |
| Axios | HTTP client. |
| i18next / react-i18next | Localization. |
| Zustand | Lightweight client state when needed. |
| Sonner | Toast notifications. |
| Lucide React | Icons. |
## `components/ui`
`src/components/ui/` contains reusable UI primitives. These components are copied into your project and can be edited directly.
Common components include:
- `Button`
- `Input`
- `Label`
- `Table`
- `Dialog`
- `DropdownMenu`
- `Select`
- `Card`
- `Tabs`
- `Badge`
- `DatePicker`
- `ConfirmDialog`
Use these primitives to build application pages and feature components.
```tsx
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
export function ReportCard() {
return (
<Card>
<CardHeader>
<CardTitle>Reports</CardTitle>
</CardHeader>
<CardContent>
<Button>Refresh</Button>
</CardContent>
</Card>
)
}
```
## Layout Components
Layout components are under `src/components/layout/`.
Important components include:
- `RootLayout`: root shell used by TanStack Router.
- `Header`: top bar, login button, theme toggle, and user menu.
- `Sidebar`: route-config-driven navigation menu.
- `UserMenu`: account-related dropdown menu.
The sidebar reads `src/lib/routing/route-config.ts`, checks authentication and permissions, and renders internal or external links.
## Feature Components
Feature-specific components should live near the feature that owns them. For example, Identity-specific layout components live under `src/components/identity/`, while Books-specific UI is implemented in `src/pages/books/BooksPage.tsx` in the sample template.
As a rule:
- Put generic, reusable primitives in `components/ui`.
- Put application layout in `components/layout`.
- Put feature-specific components under `components/<feature>` or next to the page when they are only used by one page.
## Pages
Route pages live under `src/pages/`. A page usually combines:
- UI primitives from `components/ui`.
- API functions from `src/lib/api`.
- Server state from TanStack Query.
- Form state from React Hook Form.
- Validation schemas from Zod.
- Permissions from `usePermissions()`.
- Localized strings from `useTranslation()`.
The Books page is the best full CRUD reference when the sample CRUD option is selected.
## Forms
Forms use React Hook Form and Zod:
```tsx
const productSchema = z.object({
name: z.string().min(1, 'Required'),
price: z.number().min(0),
})
type ProductFormData = z.infer<typeof productSchema>
const form = useForm<ProductFormData>({
resolver: zodResolver(productSchema),
defaultValues: {
name: '',
price: 0,
},
})
```
This keeps runtime validation and TypeScript types close to each other.
## Routing Components
Routes are configured in `src/routes/router.tsx` with TanStack Router. Use:
- `authGuard` for authenticated pages.
- `createPermissionGuard('Permission.Name')` for permission-protected pages.
- `RootLayout` and nested layouts for shared page structure.
Menu entries are configured separately in `src/lib/routing/route-config.ts`, so route registration and navigation display can evolve independently.
## API Components and Hooks
API functions live under `src/lib/api/` and use the shared `api` Axios instance. Components normally consume these functions through TanStack Query:
```tsx
const usersQuery = useQuery({
queryKey: ['app', 'users', queryParams],
queryFn: () => getAppUsers(queryParams),
})
```
This keeps HTTP details out of rendering components and gives you caching, loading states, refetching, and mutation invalidation.
## See Also
- [Customization](../customization.md)
- [HTTP Requests](../http-requests.md)
- [Unit Testing](../unit-testing.md)

208
docs/en/framework/ui/react/customization.md

@ -0,0 +1,208 @@
```json
//[doc-seo]
{
"Description": "Learn how to customize ABP React UI applications, including pages, themes, sidebar navigation, and the user menu."
}
```
# Customization
The React app generated by ABP is fully owned by your solution. All source code is available, so you can change pages, components, routes, themes, menus, API calls, and layout behavior just like in any other React application.
This page focuses on the main developer-owned React app. The same general approach applies to the public-web React app if your solution includes one. The Admin Console is an ABP-managed administration surface; see [Admin Console](./admin-console.md) for details.
## General Customization
Application pages live under `src/pages/`. The template includes practical references:
- **Users page**: a simple page that lists users and links to the Admin Console for full user and role management.
- **Books page**: a full CRUD sample when the sample CRUD option is selected during solution creation. It demonstrates TanStack Query, forms, Zod validation, dialogs, tables, permissions, and toast notifications.
Shared UI and infrastructure live under:
```text
src/
├── components/
│ ├── layout/
│ └── ui/
├── lib/
│ ├── api/
│ ├── auth/
│ ├── i18n/
│ ├── routing/
│ └── theme/
└── pages/
```
## Adding a Page
Create a page under `src/pages/`:
```tsx
export function ReportsPage() {
return (
<div className="space-y-6">
<h1 className="text-3xl font-bold tracking-tight">Reports</h1>
</div>
)
}
```
Register it with TanStack Router in `src/routes/router.tsx`:
```tsx
const reportsRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/reports',
component: ReportsPage,
beforeLoad: createPermissionGuard('MyProjectName.Reports'),
})
const routeTree = rootRoute.addChildren([
indexRoute,
reportsRoute,
])
```
Use `authGuard` for pages that only require authentication and `createPermissionGuard` for pages that require a permission.
## Theming
The React template uses **shadcn/ui**-style components, Radix UI primitives, Tailwind CSS, and CSS variables.
Theme tokens are defined in `src/styles/globals.css`:
```css
:root {
--background: oklch(0.978 0.003 264);
--foreground: oklch(0.205 0.008 264);
--primary: oklch(0.48 0.10 278);
--radius: 0.5rem;
}
.dark {
--background: oklch(0.16 0.004 264);
--foreground: oklch(0.92 0.005 264);
--primary: oklch(0.62 0.12 278);
}
```
ABP Studio's modern wizard can generate different shadcn theme color presets and light/dark/system theme behavior.
## Changing Theme Colors
To make a quick theme change, edit the CSS variables in `src/styles/globals.css`:
```css
:root {
--primary: oklch(0.623 0.188 259.6);
--primary-foreground: oklch(1 0 0);
}
```
Because the generated shadcn/ui components consume these variables through Tailwind tokens, the change applies across buttons, links, active sidebar entries, focus rings, and other components that use the primary color.
## Theme Mode Switcher
Theme mode is handled by `src/lib/theme/ThemeProvider.tsx`. It supports:
- `light`
- `dark`
- `system`
The header cycles through the allowed modes:
```tsx
const THEME_CYCLE: Theme[] = ['light', 'dark', 'system']
function ThemeToggle() {
const { theme, resolvedTheme, setTheme } = useTheme()
function cycleTheme() {
const currentIndex = THEME_CYCLE.indexOf(theme)
const nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % THEME_CYCLE.length
setTheme(THEME_CYCLE[nextIndex])
}
return <Button variant="ghost" size="icon" onClick={cycleTheme}>...</Button>
}
```
To remove the switcher or replace it with a dropdown, edit `src/components/layout/Header.tsx`.
## Modifying the Sidebar Menu
Sidebar navigation is defined in `src/lib/routing/route-config.ts`.
Add a menu item:
```ts
import { BarChart3 } from 'lucide-react'
export const routeConfig: RouteConfigItem[] = [
{
path: '/reports',
nameKey: 'Menu:Reports',
icon: BarChart3,
order: 10,
requiredPolicy: 'MyProjectName.Reports',
},
]
```
Then add the localization key to `src/locales/en.json`:
```json
{
"Menu:Reports": "Reports"
}
```
Use these properties depending on the menu item:
| Property | Use |
| --- | --- |
| `path` | Internal route path or logical path for an external item. |
| `nameKey` | Localization key shown in the sidebar. |
| `icon` | Optional Lucide icon. |
| `order` | Sorting order. |
| `requiredPolicy` | Hide the item unless the permission is granted. |
| `requiresAuth` | Hide the item unless the user is authenticated. |
| `externalHref` | Open an external URL or another app, such as the Admin Console. |
| `children` | Add nested sidebar items. |
## Sidebar vs User Menu
Use the **sidebar navigation** for application pages and module entry points.
Use the **user menu** for account-specific actions, profile links, sessions, security logs, linked accounts, and logout. The user menu is implemented in `src/components/layout/UserMenu.tsx`.
Example user menu item:
```tsx
<DropdownMenuItem asChild className="cursor-pointer">
<a href="/account/preferences">
<Settings className="size-4" />
{t('MyAccount::Preferences')}
</a>
</DropdownMenuItem>
```
## Customizing UI Components
shadcn/ui components are copied into your project under `src/components/ui/`. They are not black-box components from a package. You can edit them directly.
For example:
- Change button variants in `src/components/ui/button.tsx`.
- Change dialog structure in `src/components/ui/dialog.tsx`.
- Add a new reusable component under `src/components/ui/`.
- Add feature-specific components under `src/components/<feature>/`.
Keep generic primitives in `components/ui` and business-specific components close to the feature or page that owns them.
## See Also
- [Components](./components/index.md)
- [Permission Management](./permission-management.md)
- [Admin Console](./admin-console.md)

121
docs/en/framework/ui/react/environment-variables.md

@ -0,0 +1,121 @@
```json
//[doc-seo]
{
"Description": "Learn how runtime configuration and environment variables work in ABP React UI applications."
}
```
# Environment Variables
ABP React UI applications use a runtime configuration file and Vite environment variables together. The template is preconfigured by ABP Studio's modern wizard, available with ABP Studio **v3.0+**, so a newly created solution already contains working local values for the API, Auth Server, OpenIddict client, and Admin Console link.
You usually change these values when moving the application to another environment such as staging or production.
## Configuration Sources
The React template reads configuration from these places:
- `dynamic-env.json`: runtime configuration that can be changed without rebuilding the application.
- `public/dynamic-env.json`: the file served by the app. The Vite build copies the root `dynamic-env.json` into this location when it exists.
- `src/env.ts`: local fallback values used when runtime configuration is not loaded.
- `.env` files / shell variables: Vite variables such as `VITE_API_URL`, `VITE_AUTH_URL`, and `VITE_APP_URL`.
For layered and single-layer modern templates, the React app is in the `react/` folder. For the microservice modern template, it is in `apps/react/`.
## `dynamic-env.json`
The runtime configuration file has the same purpose as Angular's dynamic environment configuration: it lets you deploy the same build artifact to different environments and change the API or authentication endpoints at runtime.
```json
{
"application": {
"baseUrl": "https://localhost:3000",
"name": "Acme.BookStore"
},
"oAuthConfig": {
"issuer": "https://localhost:44301/",
"redirectUri": "https://localhost:3000",
"clientId": "Acme_BookStore_App",
"scope": "offline_access openid profile email phone AuthServer IdentityService AdministrationService"
},
"apis": {
"default": {
"url": "https://localhost:44300",
"rootNamespace": "Acme.BookStore"
}
},
"adminConsoleUrl": "https://localhost:44307"
}
```
The template loads `/dynamic-env.json` first and then tries `/getEnvConfig` for compatibility with environments that expose the file through that endpoint.
## Available Values
| Key | Description |
| --- | --- |
| `application.baseUrl` | Public URL of the React application. It is used as a fallback for OAuth redirect URLs. |
| `application.name` | Application name. |
| `application.logoUrl` | Optional logo URL for application branding. |
| `oAuthConfig.issuer` | Auth Server / OpenIddict authority URL. |
| `oAuthConfig.redirectUri` | Redirect URI registered for the React OpenIddict client. |
| `oAuthConfig.clientId` | OpenIddict client ID. The main React app uses `<ProjectName>_App`. |
| `oAuthConfig.scope` | OAuth scopes requested by the SPA. |
| `apis.default.url` | Backend API base URL. In microservice solutions, this normally points to the Web Gateway. |
| `apis.default.rootNamespace` | Root namespace used by generated API code and module-specific clients. |
| `adminConsoleUrl` | Origin of the Admin Console app. The React template uses it to open `/admin-console`. |
The `DynamicEnv` type also includes fields such as `production`, `oAuthConfig.requireHttps`, `oAuthConfig.responseType`, `oAuthConfig.strictDiscoveryDocumentValidation`, and `oAuthConfig.skipIssuerCheck`. The template's OIDC setup always uses the Authorization Code flow by setting `responseType` to `code`.
## Vite Variables
The React template uses Vite and reads environment variables with `loadEnv(mode, process.cwd(), '')`, so variables are not limited to the `VITE_` prefix inside `vite.config.ts`.
The important variables for developers are:
| Variable | Description |
| --- | --- |
| `VITE_API_URL` | Overrides the backend API or gateway URL used by the dev proxy and runtime fallback. |
| `VITE_AUTH_URL` | Overrides the Auth Server URL used by the dev proxy and runtime fallback. If omitted, the dev proxy can fall back to `VITE_API_URL`. |
| `VITE_APP_URL` | Overrides the React app URL used as the OAuth redirect URI fallback. |
Example:
```bash
VITE_API_URL=https://api.bookstore.example.com
VITE_AUTH_URL=https://auth.bookstore.example.com
VITE_APP_URL=https://bookstore.example.com
```
## What ABP Studio Preconfigures
When a React solution is created with ABP Studio v3.0+ or `abp new --modern`, the template fills these values from the generated solution configuration:
- Local launch ports for the React app, Web Gateway/API host, Auth Server, and Admin Console.
- The OpenIddict client ID, usually `<ProjectName>_App`.
- OAuth scopes based on the selected modules, such as Identity, Administration, SaaS, Audit Logging, GDPR, File Management, AI Management, Language Management, or Chat.
- `adminConsoleUrl` when the template includes a separate Admin Console application.
For local development, these generated values should work without manual changes. For production, update the API URL, Auth Server URL, redirect URI, client ID if you changed the seeded client, and any environment-specific scopes.
## Development Proxy
In development, `vite.config.ts` proxies these paths:
- `/api` to `VITE_API_URL` or the generated API/gateway URL.
- `/connect` to `VITE_AUTH_URL`, `VITE_API_URL`, or the generated Auth Server URL.
- `/getEnvConfig` to `VITE_API_URL` or the generated API/gateway URL.
This allows the React app to call same-origin paths during development while the backend services run on their own ports.
## Deployment
For deployment, prefer changing `dynamic-env.json` instead of rebuilding the React application for each environment. The file should be served with `application/json` content type and should not be rewritten to `index.html` by SPA fallback rules.
If your server exposes `/getEnvConfig`, configure it to return the same JSON content as `dynamic-env.json`.
## See Also
- [React UI](./index.md)
- [Authorization](./authorization.md)
- [HTTP Requests](./http-requests.md)

213
docs/en/framework/ui/react/http-requests.md

@ -0,0 +1,213 @@
```json
//[doc-seo]
{
"Description": "Learn how HTTP requests are made in ABP React UI applications with Axios, runtime configuration, and ABP interceptors."
}
```
# HTTP Requests
ABP React UI templates use [Axios](https://axios-http.com/) for HTTP requests. The generated app contains a shared Axios instance with ABP-specific request and response interceptors, plus typed API modules for backend endpoints.
The shared client is defined in `src/lib/api/axios.ts` and exported as `api`.
## Base URL
The Axios base URL is resolved at request time from runtime configuration:
```ts
export function getApiBaseUrl(): string {
const apiUrl = getApiUrl()
if (apiUrl.startsWith('http://') || apiUrl.startsWith('https://')) {
return apiUrl.replace(/\/$/, '') + '/api'
}
if (import.meta.env.DEV) {
return '/api'
}
return apiUrl.replace(/\/$/, '') + '/api'
}
```
The API URL comes from:
1. `dynamic-env.json` -> `apis.default.url`
2. `VITE_API_URL`
3. `src/env.ts` generated fallback
In microservice solutions, `apis.default.url` normally points to the Web Gateway. In layered and single-layer solutions, it normally points to the HTTP API host.
## Shared Axios Instance
The template creates one shared instance:
```ts
export const api = axios.create({
baseURL: '',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
},
})
```
Use this instance for application API modules instead of creating new Axios clients. It centralizes ABP headers, authentication, tenant handling, language handling, and redirects.
## Request Interceptor
Before each request, the template:
- Sets `baseURL` from runtime configuration.
- Adds `Authorization: Bearer <token>` from the OIDC user.
- Adds `__tenant` when the user has selected a tenant.
- Adds `Accept-Language` from i18next.
- Keeps default AJAX headers such as `X-Requested-With`.
```ts
api.interceptors.request.use(async (config) => {
config.baseURL = getApiBaseUrl()
const user = await userManager.getUser()
if (user?.access_token) {
config.headers.Authorization = `Bearer ${user.access_token}`
}
const tenantId = sessionStorage.getItem('abp_tenant_id')
if (tenantId && !config.headers.__tenant) {
config.headers.__tenant = tenantId
}
if (i18n?.language) {
config.headers['Accept-Language'] =
config.headers['Accept-Language'] ?? i18n.language
}
return config
})
```
## Response Interceptor
The response interceptor handles common authorization failures:
- `401 Unauthorized`: redirects to login unless `skipAuthRedirect` is set.
- `403 Forbidden`: redirects to `/403` unless `skip403Redirect` is set.
- Other errors are rejected so the caller can handle them.
```ts
api.interceptors.response.use(
(response) => response,
async (error) => {
const status = error.response?.status
if (status === 401 && !error.config?.skipAuthRedirect) {
await userManager.signinRedirect()
return Promise.reject(new Error('Unauthorized - redirecting to login'))
}
if (status === 403 && !error.config?.skip403Redirect) {
window.location.href = '/403'
return Promise.reject(new Error('Forbidden'))
}
return Promise.reject(error)
}
)
```
Use `skipAuthRedirect` or `skip403Redirect` for calls where the component should handle the error itself.
## Typed API Modules
The template organizes backend calls under `src/lib/api/`. For example, the Books sample defines DTOs and functions in `books.ts`:
```ts
import { api } from './axios'
export interface PagedResultDto<T> {
items: T[]
totalCount: number
}
export interface BookDto {
id: string
name?: string
price: number
}
export async function getBooks(): Promise<PagedResultDto<BookDto>> {
const { data } = await api.get<PagedResultDto<BookDto>>('/app/book', {
params: {
maxResultCount: 10,
skipCount: 0,
},
})
return data
}
```
Notice that the API module calls `/app/book`, not `/api/app/book`. The shared Axios base URL already includes the `/api` prefix when needed.
## Using Requests from Components
The template uses TanStack Query for server state:
```tsx
const { data, isLoading } = useQuery({
queryKey: ['books', skipCount],
queryFn: () =>
getBooks({
maxResultCount: 10,
skipCount,
sorting: 'creationTime desc',
}),
})
```
Mutations use `useMutation` and invalidate related queries after success:
```tsx
const createMutation = useMutation({
mutationFn: createBook,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['books'] })
toast.success(t('AbpUi::SavedSuccessfully'))
},
})
```
## Adding a New API Module
Create a file under `src/lib/api/`:
```ts
import { api } from './axios'
export interface ProductDto {
id: string
name: string
}
export async function getProducts(): Promise<ProductDto[]> {
const { data } = await api.get<ProductDto[]>('/app/product')
return data
}
```
Then consume it from a component with TanStack Query:
```tsx
const productsQuery = useQuery({
queryKey: ['products'],
queryFn: getProducts,
})
```
## Development Proxy
In development, Vite proxies `/api`, `/connect`, and `/getEnvConfig`. This lets the React app use same-origin paths while calls are forwarded to the backend, Auth Server, or gateway configured by `VITE_API_URL` and `VITE_AUTH_URL`.
## See Also
- [Environment Variables](./environment-variables.md)
- [Authorization](./authorization.md)
- [Permission Management](./permission-management.md)

142
docs/en/framework/ui/react/index.md

@ -0,0 +1,142 @@
```json
//[doc-seo]
{
"Description": "Learn how to build modern web applications with ABP React UI, including runtime configuration, authentication, Admin Console, shadcn/ui components, and testing."
}
```
# React UI
ABP provides a **React UI** option for building modern, client-side web applications. React UI is part of the **modern template system** and is available with **ABP Studio v3.0+** through the Modern Wizard or with `abp new --modern` using [ABP CLI](../../../cli/index.md).
React UI is not available in classic, non-modern templates. Use ABP Studio's modern template flow or `Volo.Abp.Studio.Cli` to create a React-based solution.
## Technology Stack
The React UI template is built with:
| Technology | Purpose |
| --- | --- |
| [Vite](https://vite.dev/) | Build tool and dev server |
| [React](https://react.dev/) | UI framework |
| [TanStack Router](https://tanstack.com/router) | Client-side routing |
| [TanStack Query](https://tanstack.com/query) | Server state and API request orchestration |
| [shadcn/ui](https://ui.shadcn.com/) | Source-owned component library built on Radix UI and Tailwind CSS |
| [Zod](https://zod.dev/) | Schema validation |
| [React Hook Form](https://react-hook-form.com/) | Form state management |
| [Axios](https://axios-http.com/) | HTTP client |
| [Vitest](https://vitest.dev/) | Unit testing |
| [OpenID Connect / OIDC](https://openid.net/connect/) | Authentication against the ABP Auth Server |
The template also includes ABP-specific NPM packages:
- [`@volo/abp-app-config`](https://github.com/volosoft/volo/tree/dev/abp/npm/packs/abp-app-config)
- [`@volo/abp-oidc-auth`](https://github.com/volosoft/volo/tree/dev/abp/npm/packs/abp-oidc-auth)
- [`@volo/abp-react-app-config`](https://github.com/volosoft/volo/tree/dev/abp/npm/packs/abp-react-app-config)
- [`@volo/abp-react-oidc-auth`](https://github.com/volosoft/volo/tree/dev/abp/npm/packs/abp-react-oidc-auth)
## React App and Admin Console
A modern React solution contains two UI surfaces:
- **Your React application**: the developer-owned SPA where you build application-specific pages and features.
- **ABP Admin Console**: the React-based administration UI for ABP modules.
The Admin Console is provided by the `Volo.Abp.AdminConsole` NuGet package in layered and single-layer templates. In microservice templates, it is also generated as a separate `apps/react-admin-console/` app and served through the Web Gateway.
See [Admin Console](./admin-console.md) for hosting, module discovery, and permission details.
## Solution Structure
The React app location depends on the modern template type:
- **Layered (`app --modern`) and single-layer (`app-nolayers --modern`)**: the React app lives in the `react/` folder at the solution root.
- **Microservice (`microservice --modern`)**: the React app lives at `apps/react/`.
Typical structure:
```text
react/
├── dynamic-env.json
├── public/
├── src/
│ ├── components/
│ ├── lib/
│ ├── locales/
│ ├── pages/
│ ├── routes/
│ └── main.tsx
├── package.json
├── vite.config.ts
└── vitest.config.ts
```
## Creating a Solution
Install or update `Volo.Abp.Studio.Cli`, then create a modern solution:
```bash
# Layered app with React UI
abp new Acme.BookStore --template app --modern --ui-framework react
# Single-layer app with React UI
abp new Acme.BookStore --template app-nolayers --modern --ui-framework react
# Microservice solution with React UI
abp new Acme.BookStore --template microservice --modern --ui-framework react
```
You can also use ABP Studio v3.0+ and select the modern template flow in the New Solution wizard. The wizard preconfigures local ports, runtime configuration, OIDC clients, theme options, and React/Admin Console wiring based on the selected template and modules.
## Running the Application
Start the backend from ABP Studio or by running the backend host projects, then start the React development server.
For layered and single-layer templates:
```bash
cd react
npm install
npm run dev
```
For microservice templates:
```bash
cd apps/react
npm install
npm run dev
```
Run tests with:
```bash
npm run test
```
Build for production with:
```bash
npm run build
```
## Documentation Map
Use these pages to learn each part of the React UI:
- [Environment Variables](./environment-variables.md): runtime configuration, `dynamic-env.json`, Vite variables, and Studio-generated defaults.
- [Authorization](./authorization.md): OIDC, Authorization Code flow with PKCE, auth provider, hooks, and route guards.
- [Localization](./localization.md): i18next, local JSON resources, ABP localization keys, and request culture.
- [Permission Management](./permission-management.md): fetching granted policies, `usePermissions()`, route protection, and conditional UI.
- [HTTP Requests](./http-requests.md): Axios setup, interceptors, typed API modules, and TanStack Query usage.
- [Customization](./customization.md): changing pages, themes, sidebar items, user menu entries, and shadcn/ui components.
- [Components](./components/index.md): component architecture, UI primitives, layout components, forms, and routing.
- [Unit Testing](./unit-testing.md): Vitest, React Testing Library, examples, and test workflow.
- [Admin Console](./admin-console.md): the `Volo.Abp.AdminConsole` package, `/admin-console/*` hosting, module discovery, and optional modules.
## See Also
- [ABP Studio](../../../studio/index.md)
- [ABP CLI](../../../cli/index.md)
- [Authorization](../../../framework/fundamentals/authorization/index.md)
- [Localization](../../../framework/fundamentals/localization.md)

158
docs/en/framework/ui/react/localization.md

@ -0,0 +1,158 @@
```json
//[doc-seo]
{
"Description": "Learn how localization works in ABP React UI applications with i18next and ABP application configuration."
}
```
# Localization
ABP React UI templates use [i18next](https://www.i18next.com/) with [react-i18next](https://react.i18next.com/). The generated app includes local JSON resources and integrates with ABP application configuration through the `@volo/abp-app-config` packages.
## Localization Files
The main React app stores client-side translations under `src/locales/`.
```text
src/
├── locales/
│ └── en.json
└── lib/
└── i18n/
└── i18n.ts
```
The default `i18n.ts` imports the English resource and registers it:
```ts
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import en from '@/locales/en.json'
i18n.use(initReactI18next).init({
resources: {
en: { translation: en },
},
lng: 'en',
fallbackLng: 'en',
keySeparator: false,
nsSeparator: false,
interpolation: {
escapeValue: false,
},
})
```
`keySeparator` and `nsSeparator` are disabled so ABP-style keys such as `AbpIdentity::Users` and `Menu:Home` can be used directly.
## Using Localized Text
Use `useTranslation()` from `react-i18next` in components:
```tsx
import { useTranslation } from 'react-i18next'
export function BooksTitle() {
const { t } = useTranslation()
return <h1>{t('Menu:Books')}</h1>
}
```
ABP localization keys commonly use the `ResourceName::Key` format:
```tsx
{t('AbpIdentity::Users')}
{t('AbpAccount::Login')}
{t('AbpUi::SavedSuccessfully')}
```
Application-specific menu keys may use names like `Menu:Home` or `Menu:Books`.
## Adding a Translation
Add the key to `src/locales/en.json`:
```json
{
"Menu:Reports": "Reports",
"Reports": "Reports"
}
```
Then use it from a component:
```tsx
const { t } = useTranslation()
return <h1>{t('Reports')}</h1>
```
## Adding a Language
Create a new JSON file, for example `src/locales/tr.json`:
```json
{
"Menu:Reports": "Raporlar",
"Reports": "Raporlar"
}
```
Register it in `src/lib/i18n/i18n.ts`:
```ts
import en from '@/locales/en.json'
import tr from '@/locales/tr.json'
i18n.use(initReactI18next).init({
resources: {
en: { translation: en },
tr: { translation: tr },
},
lng: 'en',
fallbackLng: 'en',
})
```
If you add a language selector, call `i18n.changeLanguage('tr')` when the user chooses Turkish.
## Server-Side ABP Localization
ABP's backend localization system is still the source of truth for server-defined resources, validation messages, exception messages, and module texts. The React app uses ABP application configuration through `@volo/abp-app-config` / `@volo/abp-react-app-config` for auth and configuration data, and these packages can include localization resources when configured to do so.
The main template currently creates the app configuration client with:
```ts
export const appConfig = createAbpReactAppConfig({
baseUrl: () => getApiUrl(),
includeLocalizationResources: false,
})
```
Because `includeLocalizationResources` is disabled in the main React template, UI text is normally loaded from `src/locales/*.json`. If you enable server-provided localization resources, make sure your UI initialization merges them into i18next before rendering localized components.
## Request Culture
The shared Axios client sends the active i18next language with each request:
```ts
if (i18n?.language) {
config.headers['Accept-Language'] =
config.headers['Accept-Language'] ?? i18n.language
}
```
This lets backend responses, validation messages, and exception messages use the selected culture when the server supports it.
## Admin Console Localization
The Admin Console has its own React app and localization setup. In layered and single-layer templates, it is served from the `Volo.Abp.AdminConsole` package. In microservice templates, it is generated as `apps/react-admin-console/`.
The Admin Console host can expose available languages through `AdminConsole:LocalizationLanguages`, and `/admin-console/api/config` returns the normalized language list.
## See Also
- [React UI](./index.md)
- [HTTP Requests](./http-requests.md)
- [Localization](../../../framework/fundamentals/localization.md)

171
docs/en/framework/ui/react/permission-management.md

@ -0,0 +1,171 @@
```json
//[doc-seo]
{
"Description": "Learn how permissions are fetched, stored, checked, and applied in ABP React UI applications."
}
```
# Permission Management
ABP permissions are defined on the server side and are exposed to the React app through ABP application configuration. The React template uses those permissions to protect routes, hide sidebar items, and conditionally render UI actions.
For the server-side permission system, see [Authorization](../../../framework/fundamentals/authorization/index.md).
## Packages
The React template uses:
| Package | Purpose |
| --- | --- |
| `@volo/abp-app-config` | Framework-agnostic ABP application configuration client. |
| `@volo/abp-react-app-config` | React hooks and adapters for application configuration. |
The template creates a shared app configuration client in `src/lib/auth/permissions.ts`:
```ts
export const appConfig = createAbpReactAppConfig({
baseUrl: () => getApiUrl(),
includeLocalizationResources: false,
})
```
## Fetching Permissions
After the user logs in, `AuthProvider` fetches application configuration with the current access token:
```ts
const user = await authClient.getUserManager().getUser()
if (user && !user.expired) {
await fetchAppConfig(user.access_token ?? null)
}
```
`fetchAppConfig` also sends the current tenant ID when one is selected:
```ts
export async function fetchAppConfig(token: string | null): Promise<void> {
const headers: Record<string, string> = {}
const tenantId = sessionStorage.getItem('abp_tenant_id')
if (tenantId) headers.__tenant = tenantId
await appConfig.fetchConfig(token, { headers })
}
```
The response includes the current user's granted policies. These are stored by the app configuration client and exposed to React components.
## Checking Permissions in Components
Use `usePermissions()` from `src/lib/auth/permissions.ts`:
```tsx
import { usePermissions } from '@/lib/auth/permissions'
export function BookActions() {
const { isGranted } = usePermissions()
return (
<>
{isGranted('MyProjectName.Books.Edit') && <button>Edit</button>}
{isGranted('MyProjectName.Books.Delete') && <button>Delete</button>}
</>
)
}
```
The Books page uses this pattern for edit and delete actions:
```ts
const { isGranted } = usePermissions()
const canEdit = isGranted('MyProjectName.Books.Edit')
const canDelete = isGranted('MyProjectName.Books.Delete')
```
## Route Guards
Routes can require a permission by using `createPermissionGuard`:
```ts
const booksRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/books',
component: BooksPage,
beforeLoad: createPermissionGuard('MyProjectName.Books'),
})
```
`createPermissionGuard` runs the authentication guard first, fetches app configuration if needed, and redirects to `/403` when the required policy is not granted.
```ts
export function createPermissionGuard(requiredPolicy: string) {
return async (context: GuardContext) => {
await authGuard(context)
if (!appConfig.getSnapshot()?.initialized) {
const user = await userManager.getUser()
await fetchAppConfig(user?.access_token ?? null)
}
if (!isPolicyGranted(requiredPolicy)) throw redirect({ to: '/403' })
}
}
```
## Sidebar Visibility
The sidebar reads `routeConfig` and hides items that require missing permissions:
```ts
export const routeConfig: RouteConfigItem[] = [
{
path: '/identity/users',
nameKey: 'AbpIdentity::Users',
requiredPolicy: 'AbpIdentity.Users',
},
]
```
The sidebar checks each item:
```ts
if (item.requiresAuth && !isAuthenticated) return false
if (!item.requiredPolicy) return true
if (!isAuthenticated) return false
return isGranted(item.requiredPolicy)
```
Use `requiresAuth` for menu items that only require login. Use `requiredPolicy` when the item should only be visible to users with a specific permission.
## Compound Policies
The template's `isPolicyGranted` helper supports simple compound expressions:
- `PermissionA || PermissionB`
- `PermissionA && PermissionB`
This is useful for menu entries that should be visible when the user has one of several related module permissions.
## Where Permissions Are Applied
The generated React app uses permissions in these places:
- **Users page**: the `/identity/users` route and sidebar entry require `AbpIdentity.Users`. The page links to the Admin Console for full user and role management.
- **Books page**: the route requires `MyProjectName.Books`; edit and delete actions check `MyProjectName.Books.Edit` and `MyProjectName.Books.Delete`.
- **Admin Console link**: the sidebar entry uses `requiresAuth` because the Admin Console performs its own module and route permission checks.
The Admin Console applies module-specific permissions for pages such as:
- Identity users and roles: `AbpIdentity.*`.
- OpenIddict applications and scopes: `OpenIddictPro.Application` and `OpenIddictPro.Scope`.
- Audit Logging UI: `AuditLogging.AuditLogs`.
- Text Template Management: `TextTemplateManagement.*`.
- AI Management: `AIManagement.*`.
## Multi-Tenancy
When a tenant is selected, the template stores the tenant ID in `sessionStorage` as `abp_tenant_id`. Permission and API requests send it with the `__tenant` header. This ensures the backend returns permissions and data for the selected tenant context.
## See Also
- [Authorization](./authorization.md)
- [HTTP Requests](./http-requests.md)
- [Authorization](../../../framework/fundamentals/authorization/index.md)

150
docs/en/framework/ui/react/unit-testing.md

@ -0,0 +1,150 @@
```json
//[doc-seo]
{
"Description": "Learn how to run and write unit tests in ABP React UI applications with Vitest and React Testing Library."
}
```
# Unit Testing React UI
ABP React UI templates are preconfigured for unit testing. A solution created with ABP Studio v3.0+ or `abp new --modern --ui-framework react` includes Vitest, jsdom, React Testing Library, and jest-dom.
You can add a test file and run the test command without adding extra test infrastructure.
## Test Stack
The React template uses:
| Package | Purpose |
| --- | --- |
| `vitest` | Test runner and assertion library. |
| `jsdom` | Browser-like DOM environment for component tests. |
| `@testing-library/react` | Render React components and query the DOM like a user. |
| `@testing-library/jest-dom` | Extra DOM assertions such as `toBeInTheDocument`. |
The template also includes `src/test/setup.ts`, which imports `@testing-library/jest-dom/vitest` and initializes the React i18n setup.
## Configuration
The test configuration is in `vitest.config.ts`:
```ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
include: ['src/**/*.{test,spec}.{ts,tsx}'],
globals: true,
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
```
Tests can import application files with the same `@/` alias used by the app.
## Running Tests
Install dependencies once:
```bash
npm install
```
Run tests in watch mode:
```bash
npm run test
```
Run tests once, which is useful for CI:
```bash
npm run test:run
```
The template's `package.json` maps these commands to `vitest` and `vitest run`.
## Example Test
The template includes example tests under `src/`. For example, `src/pages/home/HomePage.test.tsx` renders the home page and mocks the authentication hook:
```tsx
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import { HomePage } from './HomePage'
import * as auth from '@/lib/auth/AuthContext'
vi.mock('@/lib/auth/AuthContext', () => ({
useAuth: vi.fn(),
}))
describe('HomePage', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('renders login prompt when not authenticated', () => {
vi.mocked(auth.useAuth).mockReturnValue({
isAuthenticated: false,
isLoading: false,
user: null,
login: vi.fn(),
logout: vi.fn(),
navigateToLogin: vi.fn(),
getAccessToken: vi.fn(),
} as unknown as ReturnType<typeof auth.useAuth>)
render(<HomePage />)
expect(screen.getByText('Welcome')).toBeInTheDocument()
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument()
})
})
```
This style keeps the test focused on visible behavior. Dependencies that would require real authentication, network calls, or browser redirects are mocked.
## Writing a Component Test
Create a `*.test.tsx` file next to the component:
```tsx
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { Button } from '@/components/ui/button'
describe('Button', () => {
it('renders its content', () => {
render(<Button>Save</Button>)
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument()
})
})
```
Prefer queries such as `getByRole`, `getByLabelText`, and `getByText` because they describe what the user can see or do.
## Writing a Service or Hook Test
For non-component logic, use Vitest directly. The template includes tests for routing guards, permissions, authentication context, and Axios interceptors.
When testing API code, mock the shared Axios instance or the lower-level dependency instead of calling a real backend. When testing permission behavior, mock the application configuration client or use the exported permission helpers.
## Interpreting Output
Vitest reports each test file, failed assertions, stack traces, and a summary of passed/failed tests. In watch mode, it reruns affected tests when files change. In `test:run` mode, Vitest exits with a non-zero status code if any test fails, which makes it suitable for CI pipelines.
If a component test fails because an ABP service is not initialized, mock the hook or provider used by the component. For example, pages that call `useAuth()` or `usePermissions()` should provide a controlled mock for those hooks unless the test is specifically verifying the provider.
## See Also
- [Components](./components/index.md)
- [Authorization](./authorization.md)
- [Permission Management](./permission-management.md)

BIN
docs/en/get-started/images/abp-studio-mobile-sample.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save