Browse Source

Merge branch 'rel-10.4' into auto-merge/rel-10-3/4646

# Conflicts:
#	docs/en/cli/index.md
pull/25616/head
maliming 4 months ago
parent
commit
8d5c762d49
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      .claude/settings.local.json
  2. 22
      .github/workflows/auto-pr.yml
  3. 2
      .github/workflows/build-and-test.yml
  4. 130
      .github/workflows/update-studio-docs.yml
  5. 131
      Directory.Packages.props
  6. 4
      common.props
  7. 214
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/POST.md
  8. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/account-settings.png
  9. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/cover-image.png
  10. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email.png
  11. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email2.png
  12. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-identity.png
  13. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-index.png
  14. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-studio.png
  15. BIN
      docs/en/Blog-Posts/2026-04-29 v10_4_Preview/studio-switch-to-preview.png
  16. 10
      docs/en/Community-Articles/2026-03-10-Tutorial-Validator/article.md
  17. 2
      docs/en/Community-Articles/2026-03-17-Shared-User-Accounts-in-ABP/POST.md
  18. 167
      docs/en/Community-Articles/2026-03-29-Url-Based-Localization/POST.md
  19. BIN
      docs/en/Community-Articles/2026-03-29-Url-Based-Localization/cover.png
  20. BIN
      docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/blazor-server-zh-hans.png
  21. BIN
      docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/blazor-webapp-tr.png
  22. BIN
      docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/module-identity-users.png
  23. BIN
      docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/mvc-home-en.png
  24. BIN
      docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/mvc-home-tr.png
  25. 254
      docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/Post.md
  26. BIN
      docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/cover.png
  27. BIN
      docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic1.jpg
  28. BIN
      docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic2.png
  29. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/cover.png
  30. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-basic-theme-dashboard.png
  31. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-identity-users.png
  32. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-leptonx-dashboard.png
  33. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-leptonx-lite-dashboard.png
  34. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-permission-management.png
  35. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-saas-tenants.png
  36. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-studio-blazor-ui-library-dropdown.png
  37. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-studio-first-run.png
  38. BIN
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-vs-blazorise-leptonx.png
  39. 213
      docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/post.md
  40. 4
      docs/en/cli/differences-between-old-and-new-cli.md
  41. 1016
      docs/en/cli/index.md
  42. 106
      docs/en/docs-nav.json
  43. 11
      docs/en/docs-params.json
  44. 2
      docs/en/framework/architecture/multi-tenancy/index.md
  45. 1
      docs/en/framework/fundamentals/index.md
  46. 41
      docs/en/framework/fundamentals/localization.md
  47. 173
      docs/en/framework/fundamentals/url-based-localization.md
  48. 164
      docs/en/framework/infrastructure/app-urls.md
  49. 1
      docs/en/framework/infrastructure/emailing.md
  50. 68
      docs/en/framework/infrastructure/image-manipulation.md
  51. 2
      docs/en/framework/infrastructure/text-templating/razor.md
  52. 25
      docs/en/framework/infrastructure/text-templating/scriban.md
  53. 2
      docs/en/framework/ui/angular/authorization.md
  54. 1
      docs/en/framework/ui/angular/checkbox-component.md
  55. 18
      docs/en/framework/ui/angular/data-table-column-extensions.md
  56. 19
      docs/en/framework/ui/angular/dynamic-form-extensions.md
  57. 20
      docs/en/framework/ui/angular/entity-action-extensions.md
  58. 2
      docs/en/framework/ui/angular/extensions-overall.md
  59. 16
      docs/en/framework/ui/angular/feature-libraries.md
  60. 2
      docs/en/framework/ui/angular/features.md
  61. 2
      docs/en/framework/ui/angular/how-replaceable-components-work-with-extensions.md
  62. 2
      docs/en/framework/ui/angular/internet-connection-service.md
  63. 2
      docs/en/framework/ui/angular/oauth-module.md
  64. 41
      docs/en/framework/ui/angular/page-toolbar-extensions.md
  65. 5
      docs/en/framework/ui/angular/quick-start.md
  66. 2
      docs/en/framework/ui/angular/settings.md
  67. 411
      docs/en/framework/ui/angular/testing.md
  68. 15
      docs/en/framework/ui/blazor/basic-theme.md
  69. 63
      docs/en/framework/ui/blazor/components/submit-button.md
  70. 78
      docs/en/framework/ui/blazor/customization-overriding-components.md
  71. 30
      docs/en/framework/ui/blazor/data-table-column-extensions.md
  72. 39
      docs/en/framework/ui/blazor/entity-action-extensions.md
  73. 61
      docs/en/framework/ui/blazor/error-handling.md
  74. 127
      docs/en/framework/ui/blazor/forms-validation.md
  75. 25
      docs/en/framework/ui/blazor/overall.md
  76. 97
      docs/en/framework/ui/blazor/page-header.md
  77. 46
      docs/en/framework/ui/blazor/page-layout.md
  78. 75
      docs/en/framework/ui/blazor/page-toolbar-extensions.md
  79. 50
      docs/en/framework/ui/blazor/theming.md
  80. 7
      docs/en/framework/ui/index.md
  81. 46
      docs/en/framework/ui/mvc-razor-pages/overall.md
  82. 74
      docs/en/framework/ui/react-native/index.md
  83. 191
      docs/en/framework/ui/react/admin-console.md
  84. 183
      docs/en/framework/ui/react/authorization.md
  85. 184
      docs/en/framework/ui/react/components/index.md
  86. 208
      docs/en/framework/ui/react/customization.md
  87. 121
      docs/en/framework/ui/react/environment-variables.md
  88. 213
      docs/en/framework/ui/react/http-requests.md
  89. 142
      docs/en/framework/ui/react/index.md
  90. 158
      docs/en/framework/ui/react/localization.md
  91. 171
      docs/en/framework/ui/react/permission-management.md
  92. 150
      docs/en/framework/ui/react/unit-testing.md
  93. 2
      docs/en/get-started/index.md
  94. 8
      docs/en/get-started/layered-web-application.md
  95. 65
      docs/en/get-started/microservice.md
  96. 8
      docs/en/get-started/single-layer-web-application.md
  97. 2
      docs/en/guides/ms-multi-tenant-domain-resolving.md
  98. BIN
      docs/en/images/abp-studio-ai-agent.png
  99. BIN
      docs/en/images/layered-project-dependencies-blazor-server.png
  100. BIN
      docs/en/images/layered-project-dependencies-blazor-wasm.png

2
.claude/settings.local.json

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

22
.github/workflows/auto-pr.yml

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

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

@ -86,7 +86,7 @@ jobs:
- name: Codecov - name: Codecov
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v7
with: with:
use_oidc: true use_oidc: true
fail_ci_if_error: true fail_ci_if_error: true

130
.github/workflows/update-studio-docs.yml

@ -213,17 +213,18 @@ jobs:
CRITICAL RULES: CRITICAL RULES:
1. Extract ONLY essential, user-facing changes 1. Extract ONLY essential, user-facing changes
2. Format as bullet points starting with "- " 2. Format as markdown bullet points starting with "* "
3. Keep it concise and professional 3. Keep it concise, friendly and easy to scan
4. Match the style of existing release notes 4. Match the style of existing release notes
5. Skip internal/technical details unless critical 5. Skip internal/technical details unless critical
6. Return ONLY the bullet points (no version header, no date) 6. Return ONLY the bullet points (no version header, no date)
7. One change per line 7. One change per line
8. Prefer short action-oriented summaries like "AI Agent Upgrades: Added browser automation tools"
Output example: Output example:
- Fixed books sample for blazor-webapp tiered solution * AI Agent Upgrades: Added browser automation tools
- Enhanced Module Installation UI * Module Setup Improvements: Added guidance for modularity options
- Added AI Management option to Startup Templates * UI Polish: Improved sidebar icons and visual consistency
Return ONLY the formatted bullet points. Return ONLY the formatted bullet points.
@ -240,56 +241,85 @@ jobs:
echo "✅ Using AI-formatted release notes" echo "✅ Using AI-formatted release notes"
echo "$AI_RESPONSE" > .tmp/final-notes.txt echo "$AI_RESPONSE" > .tmp/final-notes.txt
else else
echo "⚠️ AI unavailable - using aggressive cleaning on raw release notes" echo "⚠️ AI unavailable - generating concise user-friendly summaries from raw notes"
# Clean and format raw notes with aggressive filtering python3 <<'PYTHON_EOF'
echo "$RAW_NOTES" | while IFS= read -r line; do import os
# Skip empty lines import re
[ -z "$line" ] && continue
raw = os.environ.get("RAW_NOTES", "")
# Skip section headers lines = raw.splitlines()
[[ "$line" =~ ^#+.*What.*Changed ]] && continue
[[ "$line" =~ ^##[[:space:]] ]] && continue output = []
seen = set()
# Skip full changelog links
[[ "$line" =~ ^\*\*Full\ Changelog ]] && continue def clean_line(text: str) -> str:
[[ "$line" =~ ^Full\ Changelog ]] && continue text = text.strip()
if not text:
# Remove leading bullet/asterisk return ""
line=$(echo "$line" | sed 's/^[[:space:]]*[*-][[:space:]]*//')
# Drop markdown headers/changelog lines.
# Aggressive cleaning: remove entire " by @user in https://..." suffix if re.match(r"^#+\s", text, flags=re.I):
line=$(echo "$line" | sed 's/[[:space:]]*by @[a-zA-Z0-9_-]*[[:space:]]*in https:\/\/github\.com\/[^[:space:]]*//g') return ""
if re.match(r"^\*\*?\s*full\s+changelog", text, flags=re.I):
# Remove remaining "by @username" or "by username" return ""
line=$(echo "$line" | sed 's/[[:space:]]*by @[a-zA-Z0-9_-]*[[:space:]]*$//g') if re.match(r"^full\s+changelog", text, flags=re.I):
line=$(echo "$line" | sed 's/[[:space:]]*by [a-zA-Z0-9_-]*[[:space:]]*$//g') return ""
# Remove standalone @mentions text = re.sub(r"^[\s\-*•]+", "", text)
line=$(echo "$line" | sed 's/@[a-zA-Z0-9_-]*//g') text = re.sub(r"\s+by\s+@?[a-zA-Z0-9_-]+\s+in\s+https?://\S+", "", text)
text = re.sub(r"\s+by\s+@?[a-zA-Z0-9_-]+\s*$", "", text)
# Clean trailing periods if orphaned text = re.sub(r"@([a-zA-Z0-9_-]+)", "", text)
line=$(echo "$line" | sed 's/\.[[:space:]]*$//') text = re.sub(r"\s*\([^)]*#\d+\)\s*$", "", text)
text = re.sub(r"\s+#\d+\s*$", "", text)
# Trim all whitespace text = re.sub(r"\s+", " ", text).strip(" .:-")
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if len(text) < 8:
# Skip if line is empty or too short return ""
[ -z "$line" ] && continue
[ ${#line} -lt 5 ] && continue # Make user-friendly short title + summary when possible.
if ":" in text:
# Capitalize first letter if lowercase left, right = [p.strip() for p in text.split(":", 1)]
line="$(echo ${line:0:1} | tr '[:lower:]' '[:upper:]')${line:1}" left = left[:40].rstrip(" .")
right_words = right.split()
# Add clean bullet and output right = " ".join(right_words[:14]).rstrip(" .")
echo "- $line" text = f"{left}: {right}" if right else left
done > .tmp/final-notes.txt else:
words = text.split()
if len(words) > 16:
text = " ".join(words[:16]).rstrip(" .")
return text
for line in lines:
cleaned = clean_line(line)
if not cleaned:
continue
# Normalize casing and deduplicate.
cleaned = cleaned[0].upper() + cleaned[1:] if cleaned else cleaned
key = cleaned.lower()
if key in seen:
continue
seen.add(key)
output.append(f"* {cleaned}")
if len(output) >= 8:
break
with open('.tmp/final-notes.txt', 'w', encoding='utf-8') as f:
f.write("\n".join(output))
PYTHON_EOF
fi fi
# Normalize bullets to "* " even if AI returns "- ".
sed -E 's/^[[:space:]]*-[[:space:]]+/* /' .tmp/final-notes.txt > .tmp/final-notes.normalized.txt
mv .tmp/final-notes.normalized.txt .tmp/final-notes.txt
# Safety check: verify we have content # Safety check: verify we have content
if [ ! -s .tmp/final-notes.txt ]; then if [ ! -s .tmp/final-notes.txt ]; then
echo "⚠️ No valid release notes extracted, using minimal fallback" echo "⚠️ No valid release notes extracted, using minimal fallback"
echo "- Release ${{ steps.payload.outputs.version }}" > .tmp/final-notes.txt echo "* Release ${{ steps.payload.outputs.version }}" > .tmp/final-notes.txt
fi fi
echo "=== Final release notes ===" echo "=== Final release notes ==="

131
Directory.Packages.props

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

4
common.props

@ -1,8 +1,8 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Version>10.3.0</Version> <Version>10.4.1</Version>
<LeptonXVersion>5.3.0</LeptonXVersion> <LeptonXVersion>5.4.1</LeptonXVersion>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn> <NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl> <PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl> <PackageProjectUrl>https://abp.io/</PackageProjectUrl>

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

10
docs/en/Community-Articles/2026-03-10-Tutorial-Validator/article.md

@ -1,4 +1,6 @@
# Automatically Validate Your Documentation: How We Built a Tutorial Validator # Automatically Validate Your Documentation: How We Built an AI Tutorial Validator
> If you're in a hurry and want to quickly check the repository, you can find the source code of the AI Tutorial Validator here 👉 [github.com/abpframework/ai-tutorial-validator](https://github.com/abpframework/ai-tutorial-validator)
Writing a tutorial is difficult. Keeping technical documentation accurate over time is even harder. Writing a tutorial is difficult. Keeping technical documentation accurate over time is even harder.
If you maintain developer documentation, you probably know the problem: a tutorial that worked a few months ago can silently break after a framework update, dependency change, or a small missing line in a code snippet. If you maintain developer documentation, you probably know the problem: a tutorial that worked a few months ago can silently break after a framework update, dependency change, or a small missing line in a code snippet.
@ -34,7 +36,7 @@ It treats tutorials like testable workflows, ensuring that every step works exac
## How the Tutorial Validator Works? ## How the Tutorial Validator Works?
the tutorial validator validates tutorials using a three-stage pipeline: The tutorial validator validates tutorials using a three-stage pipeline:
1. **Analyst**: Scrapes tutorial pages and converts instructions into a structured test plan 1. **Analyst**: Scrapes tutorial pages and converts instructions into a structured test plan
2. **Executor**: Follows the plan step by step in a clean environment 2. **Executor**: Follows the plan step by step in a clean environment
@ -43,7 +45,7 @@ the tutorial validator validates tutorials using a three-stage pipeline:
![the tutorial validator Analyst](docs/images/image-1.png) ![the tutorial validator Analyst](docs/images/image-1.png)
It identifies commands, code edits, HTTP requests, and expected outcomes. It identifies commands, code edits, HTTP requests, and expected outcomes.
The key idea is simple: if a developer would need to do it, the validator does it too. The key idea is simple: if a developer needs to do it, the validator does it too.
That includes running terminal commands, editing files, checking HTTP responses, and validating build outcomes. That includes running terminal commands, editing files, checking HTTP responses, and validating build outcomes.
![the tutorial validator Executor](docs/images/image-2.png) ![the tutorial validator Executor](docs/images/image-2.png)
@ -108,6 +110,6 @@ If your team maintains technical tutorials, this project can give you a practica
--- ---
You can find the source-code of the tutorial validator at this repo 👉 https://github.com/abpframework/tutorial-validator You can find the source code of the tutorial validator at this repo 👉 [github.com/abpframework/ai-tutorial-validator](https://github.com/abpframework/ai-tutorial-validator)
We would love to hear your feedback, ideas and waiting PRs to improve this application. We would love to hear your feedback, ideas and waiting PRs to improve this application.

2
docs/en/Community-Articles/2026-03-17-Shared-User-Accounts-in-ABP/POST.md

@ -50,6 +50,8 @@ After signing into a tenant, a tenant switcher appears in the user menu — clic
Users can also leave a tenant. Leaving doesn't delete the association record — it marks it as inactive. This preserves foreign key relationships with other entities. If the user is invited back later, the association is simply reactivated instead of recreated. Users can also leave a tenant. Leaving doesn't delete the association record — it marks it as inactive. This preserves foreign key relationships with other entities. If the user is invited back later, the association is simply reactivated instead of recreated.
The same soft removal is available to a tenant admin from the user list — a **Remove from tenant** action that takes a user off the tenant without touching the global account. Useful for the obvious case: an employee leaves the company, the admin removes them from the tenant, but their account (and any other tenant they belong to) stays intact.
Back to our earlier scenario: the financial consultant now has one account, one password. She picks which company to work in at login, switches between them during the day. The system knows it's the same person, and the audit log can trace her actions across every tenant. Back to our earlier scenario: the financial consultant now has one account, one password. She picks which company to work in at login, switches between them during the day. The system knows it's the same person, and the audit log can trace her actions across every tenant.
## Invitations ## Invitations

167
docs/en/Community-Articles/2026-03-29-Url-Based-Localization/POST.md

@ -0,0 +1,167 @@
# SEO-Friendly Localized URLs in ABP with a Single Line of Configuration
ABP has always supported language switching via the `?culture=en` query string and the culture cookie. That works fine for most applications — but it has a limitation that shows up quickly once SEO or link-sharing matters.
Consider a book-store app where users browse in their language:
- A Spanish user shares a product link. The recipient opens it in English because the cookie on *their* machine says `en`.
- Search engines crawl the same URL in every language, making it impossible to create separate sitemaps per locale.
- A user shares a link like `/Books/Detail?id=42&culture=es`. When the server processes the request, it sets the culture cookie and then redirects to `/Books/Detail?id=42` — stripping the `?culture=` parameter. The shared link no longer carries the intended language.
Embedding the culture in the URL path — `/es/books`, `/zh-Hans/about` — solves all three. Each language has its own stable URL, readable by humans and index-friendly for search engines.
ABP supports this out of the box. You opt in with a single configuration property, and the framework takes care of routing, URL generation, menu links, and language switching automatically.
## Enabling URL-Based Localization
In your ABP module class, add:
```csharp
Configure<AbpRequestLocalizationOptions>(options =>
{
options.UseRouteBasedCulture = true;
});
```
That is the only change you need to make.
## MVC / Razor Pages
MVC and Razor Pages have the most complete support — everything works automatically. No code changes needed in your pages or controllers.
![MVC sample — English](images/mvc-home-en.png)
![MVC sample — Turkish](images/mvc-home-tr.png)
## What Happens Automatically
When you set `UseRouteBasedCulture = true`, ABP automatically:
- Registers ASP.NET Core's built-in [`RouteDataRequestCultureProvider`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.localization.routing.routedatarequestcultureprovider) to detect culture from the URL path.
- Adds a `{culture}/{controller}/{action}` conventional route for MVC controllers, with a route constraint to prevent non-culture URL segments (like `/enterprise/products`) from matching.
- Adds `{culture}/...` route selectors to all Razor Pages at startup.
- Injects the current culture into all `Url.Page()` and `Url.Action()` calls, so generated URLs automatically include the culture prefix.
- Prepends the culture prefix to navigation menu item URLs.
You do not need to configure these individually.
## URL Generation Just Works
In a Razor Page or view running under a culture-prefixed URL (say, `/zh-Hans/Books`), you do not need to pass a `culture` parameter anywhere:
```cshtml
@Url.Page("/Books/Detail", new { id = book.Id })
@* Generates: /zh-Hans/Books/Detail?id=42 *@
@Url.Action("About", "Home")
@* Generates: /zh-Hans/Home/About *@
```
If you explicitly pass a different `culture` value, that takes precedence — so cross-language links are also straightforward:
```cshtml
@Url.Page("/Books/Index", new { culture = "tr" })
@* Generates: /tr/Books *@
```
## Language Switching
The built-in ABP language switcher already works with route-based culture. When a user switches language, the culture segment in the URL is automatically replaced:
| Current URL | Switch to | Redirect to |
|---|---|---|
| `/tr/books` | `en` | `/en/books` |
| `/zh-Hans/about` | `en` | `/en/about` |
| `/tenant-a/zh-Hans/about` | `en` | `/tenant-a/en/about` |
No theme changes, no language switcher changes — the existing UI component just works.
## Blazor Support
Blazor Server and Blazor WebAssembly (WebApp) both support URL-based localization. Culture detection and cookie persistence work automatically on the initial page load (SSR). Menu URLs and language switching also work automatically.
![Blazor Server sample](images/blazor-server-zh-hans.png)
![Blazor WebApp sample](images/blazor-webapp-tr.png)
ABP's built-in module pages (Identity, Settings, etc.) also work with URL-based localization out of the box:
![Identity module — User Management](images/module-identity-users.png)
### Manual step: Blazor component routes
The only manual step for Blazor is adding `@page "/{culture}/..."` routes to your own pages. ASP.NET Core does not support automatically adding route selectors to Blazor components (unlike Razor Pages), so you must add them explicitly:
```razor
@page "/"
@page "/{culture}"
@code {
[Parameter]
public string? Culture { get; set; }
}
```
```razor
@page "/Products"
@page "/{culture}/Products"
@code {
[Parameter]
public string? Culture { get; set; }
}
```
> **ABP's built-in module pages** (Identity, Tenant Management, Settings, Account, etc.) already ship with `@page "/{culture}/..."` route variants. You only need to add these routes to your own application pages.
### Blazor WebApp (WASM) configuration
The WASM client project does not need any `UseRouteBasedCulture` configuration. It reads the setting from the server automatically.
```csharp
// Server project — the only place you need to configure
Configure<AbpRequestLocalizationOptions>(options =>
{
options.UseRouteBasedCulture = true;
});
```
## Multi-Tenancy
URL-based localization is fully compatible with ABP's multi-tenant routing. Language switching supports tenant-prefixed URLs, so `/tenant-a/zh-Hans/About` correctly switches to `/tenant-a/en/About` without any additional configuration.
## UI Framework Support Overview
| UI Framework | Route Registration | URL Generation | Menu URLs | Language Switch | Manual Work |
|---|---|---|---|---|---|
| **MVC / Razor Pages** | Automatic | Automatic | Automatic | Automatic | None |
| **Blazor Server** | Manual `@page` routes | N/A | Automatic | Automatic | Add `{culture}` route to pages |
| **Blazor WebApp (WASM)** | Manual `@page` routes | N/A | Automatic | Automatic | Add `{culture}` route to pages |
## Running the Sample
A runnable sample is available at [abp-samples/UrlBasedLocalization](https://github.com/abpframework/abp-samples/tree/master/UrlBasedLocalization), with three projects:
| Project | UI Type | URL | Command |
|---|---|---|---|
| `BookStore.Mvc` | MVC / Razor Pages | `https://localhost:44335` | `dotnet run --project src/BookStore.Mvc` |
| `BookStore.Blazor.Server` | Blazor Server | `https://localhost:44336` | `dotnet run --project src/BookStore.Blazor.Server` |
| `BookStore.Blazor.WebApp` | Blazor WebApp (InteractiveAuto) | `https://localhost:44337` | `dotnet run --project src/BookStore.Blazor.WebApp` |
Supported languages: English, Türkçe, Français, 简体中文.
## Summary
To add SEO-friendly localized URL paths to your ABP application:
1. Set `options.UseRouteBasedCulture = true` in your module.
2. For **Blazor** projects, add `@page "/{culture}/..."` routes to your own pages.
Everything else — route registration, URL generation, menu links, and language switching — is handled automatically.
## References
- [URL-Based Localization — ABP Documentation](https://abp.io/docs/latest/framework/fundamentals/url-based-localization)
- [Localization — ABP Documentation](https://abp.io/docs/latest/framework/fundamentals/localization)
- [abp-samples/UrlBasedLocalization — GitHub](https://github.com/abpframework/abp-samples/tree/master/UrlBasedLocalization)
- [Request Localization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization/select-language-culture)

BIN
docs/en/Community-Articles/2026-03-29-Url-Based-Localization/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/blazor-server-zh-hans.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/blazor-webapp-tr.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/module-identity-users.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/mvc-home-en.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/en/Community-Articles/2026-03-29-Url-Based-Localization/images/mvc-home-tr.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

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-06-09-mudblazor-in-abp-framework/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-basic-theme-dashboard.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-identity-users.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-leptonx-dashboard.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-leptonx-lite-dashboard.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-permission-management.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-saas-tenants.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-studio-blazor-ui-library-dropdown.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-studio-first-run.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/mud-vs-blazorise-leptonx.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

213
docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/post.md

@ -0,0 +1,213 @@
# ABP 10.4.2 Expands Blazor UI Options with MudBlazor Support
With ABP 10.4.2, new Blazor projects can now use **MudBlazor** (Material Design) as an alternative to the long-standing default, **Blazorise** (Bootstrap 5). Framework, themes (LeptonX / LeptonX Lite / Basic), modules, solution templates, ABP Studio, and ABP Suite all support both libraries side by side. The 10.4.2 packages are live on nuget.org.
## Why add another Blazor UI library?
Blazorise has been ABP's default Blazor UI library for years and **remains the default and is fully supported** — existing Blazorise projects can keep moving at their own pace, and upgrading to 10.4 does not change anything for them.
We added MudBlazor because one Blazor UI choice cannot fit every team:
- **Design language** — Bootstrap and Material Design serve different audiences, and forcing a single choice does not fit every team
- **Open-source preference** — MudBlazor is MIT-licensed, which works well for teams that want an open-source frontend component stack without extra component-library licensing or compliance overhead
- **Ecosystem fit** — Material Design third-party components (charts, rich text editors, data visualization, and so on) tend to integrate more naturally with a MudBlazor project
For new projects you can start with MudBlazor right away. Existing Blazorise projects do not need to be rewritten just to switch UI libraries.
### Who should consider MudBlazor?
- Teams that want the frontend component stack **fully open source** with no licensing to manage (individual developers, open-source community projects, education / learning settings)
- Organizations with internal **third-party dependency or supply-chain compliance** requirements that prefer MIT-licensed components
- New Blazor projects that want to start with **Material Design**
- Teams already comfortable with the **MudBlazor ecosystem** (charts, rich text, rich UI components)
## What the MudBlazor option covers
### Framework core
`Volo.Abp.MudBlazorUI` provides the MudBlazor implementation of ABP's UI service abstractions, so code written against `IUiMessageService` / `IUiNotificationService` / `IUiPageProgressService` runs unchanged in a MudBlazor project. Key building blocks:
- `MudBlazorUiMessageService` — `Info` / `Success` / `Warn` / `Error` / `Confirm` rendered through `MudDialog`
- `MudBlazorUiNotificationService` — toast notifications via `MudSnackbar`
- `MudBlazorUiPageProgressService` — top progress bar via `MudProgressLinear`
- `AbpMudCrudPageBase<...>` — the MudBlazor counterpart to Blazorise's `AbpCrudPageBase`
- `AbpMudExtensibleDataGrid<TItem>` — a `MudDataGrid` wrapper integrated with Object Extension and time-zone conversion
- `UiMessageAlert` / `UiNotificationAlert` / `PageAlert` — page-level alert and notification containers
Theming is split across three hosts — Blazor Server, WebAssembly, and MauiBlazor — each shipped with matching bundling contributors and modules that wire MudBlazor's JS and CSS into the ABP bundle system.
### Three themes
- **LeptonX MudBlazor**
- **LeptonX Lite MudBlazor**
- **Basic Theme MudBlazor**
Each theme's layout adopts MudBlazor components such as `MudAppBar`, `MudDrawer`, `MudNavLink`, and `MudMenu`, while keeping the theme's original color palette, dim / light / system modes, and RTL support.
![LeptonX MudBlazor Dashboard](mud-leptonx-dashboard.png)
*LeptonX rendered with MudBlazor*
![LeptonX Lite MudBlazor Dashboard](mud-leptonx-lite-dashboard.png)
*LeptonX Lite rendered with MudBlazor*
![Basic Theme MudBlazor Dashboard](mud-basic-theme-dashboard.png)
*Basic Theme rendered with MudBlazor*
The LeptonX themes reuse the same `lpx-*` CSS classes across both UI libraries, so the overall information architecture, page layout, and theme experience stay consistent with the Blazorise version. Individual controls follow each UI library's own conventions.
![Blazorise vs MudBlazor on the same LeptonX theme](mud-vs-blazorise-leptonx.png)
*The same LeptonX theme — MudBlazor on the left, Blazorise on the right*
### Module coverage
Open-source modules in `abpframework/abp` that ship with a MudBlazor implementation:
- **Account**
- **Identity** — Users / Roles / OUs / ClaimTypes
- **Permission Management** — parent/child permissions with `MudTreeView` and tri-state `MudCheckBox`
- **Setting Management** — grouped settings with `MudTabs` (including theme switching)
- **Tenant Management**
- **Feature Management**
Additional MudBlazor implementations available on the Pro side, for example:
- **Identity Pro** — extra management around Sessions, SecurityLogs, and more
- **OpenIddict Pro** — Application / Scope management
- **Saas** — Tenant / Edition management with a connection-string dialog
- **Audit Logging** — `MudDataGrid` with a detail `MudDialog`
- **Language Management** / **Text Template Management**
- **File Management** / **Chat** / **CMS Kit Pro**
- **AI Management** / **GDPR** / **Payment**, and more
![Identity user management with MudDataGrid](mud-identity-users.png)
*Identity user management built on `AbpMudExtensibleDataGrid`*
![Permission management modal](mud-permission-management.png)
*Permission Management uses `MudTreeView` and tri-state `MudCheckBox` for parent/child permissions*
![Saas tenants list](mud-saas-tenants.png)
*Saas module: tenant list with a "New tenant" dialog that includes connection-string editing*
### Component mapping at a glance
If you already know Blazorise, here are the most common mappings:
| Blazorise | MudBlazor |
|-----------|-----------|
| `TextEdit @bind-Text` | `MudTextField @bind-Value` |
| `Select / SelectItem` | `MudSelect / MudSelectItem` |
| `DataGrid` | `MudDataGrid` (wrapped by ABP as `AbpMudExtensibleDataGrid`) |
| `Modal Show()/Hide()` | `MudDialog ShowAsync()/CloseAsync()` |
| `Validations` | `MudForm` + built-in validation |
| `Row / Column ColumnSize.Is6` | `MudGrid / MudItem xs="12" sm="6"` |
| Bootstrap Icons `bi-*` | `Icons.Material.Filled.*` |
A full mapping table with razor examples lives in the [ABP Blazor UI documentation](https://abp.io/docs/latest/framework/ui/blazor).
### Supported Blazor project types
ABP's MudBlazor support covers the Blazor project types you can create and run directly:
- **Blazor Server** (`-u blazor-server`)
- **Blazor WebAssembly** (`-u blazor`)
- **Blazor WebApp** (`-u blazor-webapp`, including InteractiveAuto)
### ABP Suite
ABP Suite detects the solution's UI library and generates the matching CRUD page automatically:
```csharp
public partial class Books : AbpMudCrudPageBase<IBookAppService, BookDto, Guid, GetBookListInput, CreateUpdateBookDto>
{
private MudDialog _createDialog;
private MudForm _createFormRef;
}
```
The razor templates also split by UI library — Blazorise uses `<DataGrid>` + `<Modal>` + `<Validations>`, MudBlazor uses `<MudDataGrid>` + `<MudDialog>` + `<MudForm>`.
## Choosing between Blazorise and MudBlazor
Both UI libraries are production-ready and neither is strictly better. Common factors:
- **Familiarity** — teams comfortable with Bootstrap tend to stay on Blazorise; teams comfortable with Material Design pick MudBlazor
- **Design system** — Bootstrap-style products lean toward Blazorise, Material Design products lean toward MudBlazor
- **Ecosystem** — existing Bootstrap component libraries or design assets fit Blazorise; Material Design third-party components fit MudBlazor more naturally
- **Existing projects** — keep maintaining live Blazorise projects as they are; if you want to try MudBlazor, start a new project with it
- **Licensing** — the two UI libraries have different license terms, so check each library's official license page before making a choice ([Blazorise](https://blazorise.com/license) / [MudBlazor](https://github.com/MudBlazor/MudBlazor/blob/dev/LICENSE))
Do not mix the two libraries within a single project — the choice is per solution, not per file.
## Creating a MudBlazor project in ABP Studio
### ABP Studio (recommended)
Open ABP Studio → **New Solution** → pick a template → in the UI configuration step, select **Blazor UI library = MudBlazor**. Everything else works the same as a Blazorise project. After Build & Run you land on a MudBlazor-styled application.
![ABP Studio New Solution wizard with MudBlazor selected](mud-studio-blazor-ui-library-dropdown.png)
*New Solution wizard: pick MudBlazor for the Blazor UI library*
![First run after creation](mud-studio-first-run.png)
*Studio Build & Run brings up a MudBlazor + LeptonX dashboard in the embedded browser*
### CLI
```bash
# Blazorise (default; --blazor-ui-library can be omitted)
abp new MyApp -u blazor
# MudBlazor
abp new MyApp -u blazor --blazor-ui-library mudblazor
# Tiered + WebApp + LeptonX + MudBlazor
abp new MyApp -t app --tiered -u blazor-webapp --blazor-ui-library mudblazor --theme leptonx
# Microservice + MudBlazor + Blazor Server
abp new MyApp -t microservice -u blazor-server --blazor-ui-library mudblazor
# Reusable Module + MudBlazor
abp new My.Module -t module -u blazor --blazor-ui-library mudblazor
```
Run `abp new --help` for the full option list.
Suite-generated MudBlazor CRUD pages are covered in the **ABP Suite** section above.
---
## Try it out
```bash
abp new MyMudApp -u blazor-server --blazor-ui-library mudblazor --theme leptonx
```
Documentation:
- [Forms & Validation (MudBlazor)](https://abp.io/docs/latest/framework/ui/blazor/forms-validation?BlazorUI=MudBlazor)
- [LeptonX with MudBlazor](https://abp.io/docs/latest/ui-themes/lepton-x/blazor)
- [Basic Theme MudBlazor variant](https://abp.io/docs/latest/framework/ui/blazor/basic-theme)
- [Page Header (MudBlazor)](https://abp.io/docs/latest/framework/ui/blazor/page-header)
## FAQ
**I'm already using Blazorise — will upgrading to 10.4 / 10.4.2 break my project?**
No. Blazorise stays the default, and package paths, type names, and namespaces are fully compatible. Follow the standard ABP upgrade flow.
**Can I use Blazorise and MudBlazor in the same project?**
We don't recommend it. The UI library is a project-level choice — themes, bundling, and module dependencies all switch with it. Mixing both within a single solution leads to bundle conflicts, duplicated layouts, and similar issues.
**What about my custom razor pages?**
Your custom Razor pages are tied to the UI library they were built with, so switching libraries means rewriting those pages using the component mapping above. Template-generated pages and module-provided pages don't need to be touched.
## Wrapping up
MudBlazor is now a first-class Blazor UI library in ABP. With 10.4.2 released, every related package, theme, template, Studio integration, and Suite generator is in place — you can try it out with a single `abp new` command.
If you hit a bug, have a suggestion, or want a particular module's MudBlazor UX prioritized, let us know via [GitHub Issues](https://github.com/abpframework/abp/issues) or [abp.io support](https://abp.io/support).
## References
- [MudBlazor official site](https://mudblazor.com)
- [ABP Blazor UI documentation](https://abp.io/docs/latest/framework/ui/blazor)
- [ABP LeptonX theme](https://abp.io/themes/leptonx)
- [ABP Studio download](https://abp.io/studio)

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

@ -7,9 +7,9 @@
# Old ABP CLI vs New ABP CLI # 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 ## Reason For The Change

1016
docs/en/cli/index.md

File diff suppressed because it is too large

106
docs/en/docs-nav.json

@ -340,10 +340,6 @@
"text": "Monitoring Applications", "text": "Monitoring Applications",
"path": "studio/monitoring-applications.md" "path": "studio/monitoring-applications.md"
}, },
{
"text": "Model Context Protocol (MCP)",
"path": "studio/model-context-protocol.md"
},
{ {
"text": "Working with Kubernetes", "text": "Working with Kubernetes",
"path": "studio/kubernetes.md" "path": "studio/kubernetes.md"
@ -358,6 +354,36 @@
} }
] ]
}, },
{
"text": "AI Agent",
"items": [
{
"text": "Overview",
"path": "studio/ai-agent.md",
"isIndex": true
},
{
"text": "Configuration",
"path": "studio/ai-agent-configuration.md"
},
{
"text": "Workflows",
"path": "studio/ai-agent-workflows.md"
},
{
"text": "Built-in Capabilities",
"path": "studio/ai-agent-built-in-capabilities.md"
},
{
"text": "Git Integration",
"path": "studio/ai-agent-git-integration.md"
},
{
"text": "Coding with AI Agent",
"path": "studio/coding-with-ai-agent.md"
}
]
},
{ {
"text": "Concepts", "text": "Concepts",
"path": "studio/concepts.md" "path": "studio/concepts.md"
@ -586,6 +612,10 @@
} }
] ]
}, },
{
"text": "Application URLs",
"path": "framework/infrastructure/app-urls.md"
},
{ {
"text": "Background Jobs", "text": "Background Jobs",
"items": [ "items": [
@ -1040,7 +1070,7 @@
] ]
} }
] ]
}, }
] ]
}, },
{ {
@ -1866,6 +1896,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", "text": "React Native",
"items": [ "items": [
@ -2198,6 +2290,10 @@
} }
] ]
}, },
{
"text": "Modular Monolith",
"path": "solution-templates/modular-monolith"
},
{ {
"text": "Microservice Solution", "text": "Microservice Solution",
"isLazyExpandable": true, "isLazyExpandable": true,

11
docs/en/docs-params.json

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

2
docs/en/framework/architecture/multi-tenancy/index.md

@ -357,6 +357,8 @@ context.Services
``` ```
The configuration above resolves the current tenant from the incoming request (inbound). To make the **outbound** URLs your application generates — such as the password reset link inside an Account email — point to the tenant's subdomain as well, configure `AppUrlOptions`. See [Application URLs](../../infrastructure/app-urls.md#multi-tenant-aware-urls).
##### Custom Tenant Resolvers ##### Custom Tenant Resolvers
You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below: You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below:

1
docs/en/framework/fundamentals/index.md

@ -17,6 +17,7 @@ The following documents explains the fundamental building blocks to create ABP s
* [Dependency Injection](./dependency-injection.md) * [Dependency Injection](./dependency-injection.md)
* [Exception Handling](./exception-handling.md) * [Exception Handling](./exception-handling.md)
* [Localization](./localization.md) * [Localization](./localization.md)
* [URL-Based Localization](./url-based-localization.md)
* [Logging](./logging.md) * [Logging](./logging.md)
* [Object Extensions](./object-extensions.md) * [Object Extensions](./object-extensions.md)
* [Options](./options.md) * [Options](./options.md)

41
docs/en/framework/fundamentals/localization.md

@ -126,6 +126,43 @@ var str2 = L["Hi__0"]; // Bye World!
var str3 = L["Hi__1"]; // Hello World! var str3 = L["Hi__1"]; // Hello World!
```` ````
You can have more than one localization file with the same culture: files will be merged. This is useful for large modules where splitting translations by feature keeps each file manageable.
**Example file structure:**
```
Localization/
└── MyResource/
├── en.json ← base / shared strings
├── en_Authors.json ← Author feature strings
├── en_Books.json ← Book feature strings
└── en_Users.json ← User feature strings
```
Files are sorted by name (ordinal order) before merging, so the effective merge order is `en.json` → `en_Authors.json` → `en_Books.json` → `en_Users.json`.
```
en.json en_Authors.json en_Books.json
┌─────────────────┐ ┌───────────────┐ ┌──────────────────┐
│ DisplayName=Name│ │ Author.Id=Id │ │ Book.Id=ISBN │
│ SaveButton=Save │ │ Author.Bio=.. │ │ Book.Title=Title │
└─────────────────┘ └───────────────┘ └──────────────────┘
│ │ │
└──────────────────┴──────────────────┘
│ merge (later file wins on duplicate keys)
▼
┌────────────────────┐
│ DisplayName = Name │
│ SaveButton = Save │
│ Author.Id = Id │
│ Author.Bio = ... │
│ Book.Id = ISBN │
│ Book.Title = Title│
└────────────────────┘
```
> Note: If the same key is defined in multiple files, the value from the last file (in sort order) wins.
### Default Resource ### Default Resource
`AbpLocalizationOptions.DefaultResourceType` can be set to a resource type, so it is used when the localization resource was not specified: `AbpLocalizationOptions.DefaultResourceType` can be set to a resource type, so it is used when the localization resource was not specified:
@ -294,6 +331,10 @@ Configure<AbpLocalizationOptions>(options =>
}); });
``` ```
## URL-Based Localization
ABP supports embedding the culture code directly in the URL path (e.g. `/en/products`, `/zh-Hans/about`), which is useful for SEO-friendly and shareable localized URLs. See the [URL-Based Localization](./url-based-localization.md) document for details.
## The Client Side ## The Client Side
See the following documents to learn how to reuse the same localization texts in the JavaScript side; See the following documents to learn how to reuse the same localization texts in the JavaScript side;

173
docs/en/framework/fundamentals/url-based-localization.md

@ -0,0 +1,173 @@
````json
//[doc-seo]
{
"Description": "Learn how to use ABP's URL-based localization to embed culture in the URL path, enabling SEO-friendly and shareable localized URLs."
}
````
# URL-Based Localization
ABP supports embedding the current culture directly in the URL path, for example `/tr/products` or `/en/about`. This approach is widely used by documentation sites, e-commerce platforms, and any site that needs SEO-friendly, shareable localized URLs.
By default, ABP detects language from QueryString (`?culture=tr`), Cookie, and `Accept-Language` header. URL path detection is **opt-in** and fully backward-compatible.
## Enabling URL-Based Localization
Configure the `AbpRequestLocalizationOptions` in your [module class](../architecture/modularity/basics.md):
````csharp
Configure<AbpRequestLocalizationOptions>(options =>
{
options.UseRouteBasedCulture = true;
});
````
That's all you need. The framework automatically handles the rest.
## What Happens Automatically
When you set `UseRouteBasedCulture` to `true`, ABP automatically registers the following:
* **`RouteDataRequestCultureProvider`** — A built-in ASP.NET Core provider that reads `{culture}` from route data. ABP inserts it after `QueryStringRequestCultureProvider` and before `CookieRequestCultureProvider`.
* **`{culture}/{controller}/{action}` route** — A conventional route for MVC controllers. The `{culture}` parameter uses a custom route constraint (`AbpCultureRouteConstraint`) that only matches culture values configured in `AbpLocalizationOptions.Languages`, so URLs like `/enterprise/products` are not mistaken for culture-prefixed routes.
* **`AbpCultureRoutePagesConvention`** — An `IPageRouteModelConvention` that adds `{culture}/...` route selectors to all Razor Pages.
* **`AbpCultureRouteUrlHelperFactory`** — Replaces the default `IUrlHelperFactory` to auto-inject culture into `Url.Page()` and `Url.Action()` calls.
* **`AbpCultureMenuItemUrlProvider`** — Prepends the culture prefix to navigation menu item URLs (MVC / Blazor Server).
* **`AbpWasmCultureMenuItemUrlProvider`** — Prepends the culture prefix to menu item URLs in Blazor WebAssembly (reads the `UseRouteBasedCulture` flag from `/api/abp/application-configuration`).
You do not need to configure these individually.
## URL Generation
When a request has a `{culture}` route value, all URL generation methods automatically include the culture prefix:
````csharp
// In a Razor Page — culture is auto-injected, no manual parameter needed
@Url.Page("/About") // Generates: /zh-Hans/About
@Url.Action("About", "Home") // Generates: /zh-Hans/Home/About
````
Menu items registered via `IMenuContributor` also automatically get the culture prefix. No changes are needed in your menu contributors or theme.
## Language Switching
ABP's built-in language switcher (the `/Abp/Languages/Switch` action) automatically replaces the culture segment in the `returnUrl`. The controller reads the culture from the request cookie to identify the current page culture and replaces it with the new one:
| Before switching | After switching to English |
|---|---|
| `/tr/products` | `/en/products` |
| `/tenant-a/zh-Hans/about` | `/tenant-a/en/about` |
| `/home?culture=tr&ui-culture=tr` | `/home?culture=en&ui-culture=en` |
| `/about` (no prefix) | `/about` (unchanged) |
No changes are needed in any theme or language switcher component.
## MVC / Razor Pages
MVC and Razor Pages have the most complete support. Everything works automatically when `UseRouteBasedCulture = true` — route registration, URL generation, menu links, and language switching. **No code changes are needed in your pages or controllers.**
## Blazor Server
Blazor Server uses SignalR (WebSocket) for the interactive circuit. The HTTP middleware pipeline only runs on the **initial page load** — subsequent interactions happen over the WebSocket connection. ABP handles this by persisting the detected URL culture to a **Cookie** on the first request, so the entire Blazor circuit uses the correct language.
Culture detection, cookie persistence, menu URLs, and language switching all work automatically. No additional configuration is needed beyond the `UseRouteBasedCulture` option.
### What requires manual changes
**Blazor component routes**: ASP.NET Core does not provide an `IPageRouteModelConvention` equivalent for Blazor components. You must manually add the `{culture}` route to each page:
````razor
@page "/"
@page "/{culture}"
@code {
[Parameter]
public string? Culture { get; set; }
}
````
````razor
@page "/About"
@page "/{culture}/About"
@code {
[Parameter]
public string? Culture { get; set; }
}
````
> This applies to your own application pages. ABP built-in module pages (Identity, Tenant Management, Settings, Account, etc.) already include `@page "/{culture}/..."` routes out of the box — you do not need to add them manually.
## Blazor WebAssembly (WebApp)
Blazor WebAssembly (WASM) runs in the browser. On the **first page load**, the server renders the page via SSR, and the culture is detected from the URL. After WASM downloads, subsequent renders run in the browser. The WASM app fetches `/api/abp/application-configuration` from the server to get the current culture, so the culture stays consistent.
Culture detection, cookie persistence, menu URLs, and language switching all work automatically. The WASM client reads the `UseRouteBasedCulture` flag from the server via `/api/abp/application-configuration`, so no client-side configuration is needed.
### What requires manual changes
Same as Blazor Server — you must manually add `@page "/{culture}/..."` routes to your Blazor pages.
## Angular
The [ABP Angular UI](../ui/angular/quick-start.md) runs in the browser. The server still applies `UseRouteBasedCulture`; the client reads **`localization.useRouteBasedCulture`** from `/api/abp/application-configuration` (same payload as other UI types). There is no separate Angular setting.
### Routing
Angular does not add a culture segment to your route config automatically. Use **`withOptionalRouteCulturePrefix`** from **`@abp/ng.core`** so one route tree matches both **`/identity/users`** and **`/en/identity/users`** (the first path segment is matched only when it looks like a culture code, e.g. `en`, `tr`, `zh-Hans`).
````typescript
import { Routes } from '@angular/router';
import { withOptionalRouteCulturePrefix } from '@abp/ng.core';
const appRoutesCore: Routes = [
// ... your routes (path: '', 'account', 'identity', lazy children, etc.)
];
export const appRoutes = withOptionalRouteCulturePrefix(appRoutesCore);
````
![Angular: routes wrapped with optional culture prefix](../../images/url-based-localization-angular-routes.png)
### URL → session language
When **`useRouteBasedCulture`** is **true**, **`RouteBasedCultureService`** (from `@abp/ng.core`) keeps the session language aligned with the first URL segment after navigation. This runs during application bootstrap and on each **`NavigationEnd`**.
### Menu links, breadcrumbs, and `routerLink`
Menu paths from **`RoutesService`** are usually **without** a culture prefix (`/identity/users`). Use the **`abpRouteCultureUrl`** pipe on **`routerLink`** (or **`RouteBasedCultureUrlService.prefixPathWithCulture`**) so links navigate to **`/en/identity/users`** when route-based culture is enabled. The **Basic** theme navigation and **Theme Shared** breadcrumb links follow this pattern.
![Angular: culture-prefixed menu or URL bar](../../images/url-based-localization-angular-menu-url.png)
### Language switcher (toolbar)
If the user selects a language in the UI, call **`RouteBasedCultureUrlService.applyLanguageSelection(cultureName)`** (or **`navigateToUrlWithCulture`**) instead of only updating the session language. That rewrites the current URL’s culture segment (or prepends it) so the address bar and session stay consistent; **`RouteBasedCultureService`** then picks up the culture from the URL after navigation.
### Active menu, breadcrumbs, and route matching
The browser URL may be **`/en/identity/users`** while menu items and **`RoutesService`** paths stay **`/identity/users`**. For comparisons (active state, **`findRoute`**, permission guard, dynamic layout), normalize the current URL with **`RouteBasedCultureUrlService.normalizeForMenuMatch`** (or **`stripCulturePrefixIfEnabled`**) or use **`getRoutePathForMatching`** where **`getRoutePath`** was used.
### Configuration refresh
**`RouteBasedCultureUrlService`** refreshes its cached **`useRouteBasedCulture`** and **languages** when application configuration is updated (for example after **`refreshAppState`**), so hot paths do not query configuration on every change detection cycle.
## Multi-Tenancy Compatibility
URL-based localization is fully compatible with [multi-tenancy URL routing](../architecture/multi-tenancy/index.md). The culture route is registered as a conventional route `{culture}/{controller}/{action}`. If your application uses tenant routing (e.g., `/{tenant}/...`), the tenant middleware strips the tenant segment before routing, and the culture segment is handled separately.
Language switching also supports tenant-prefixed URLs. For example, `/tenant-a/zh-Hans/About` correctly switches to `/tenant-a/en/About`.
## API Routes
Routes like `/api/products` have no `{culture}` segment, so `RouteDataRequestCultureProvider` returns `null` and falls through to the next provider (Cookie → `Accept-Language` → default). API routes are completely unaffected.
## Culture Detection Priority
ASP.NET Core has a built-in [`RouteDataRequestCultureProvider`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.localization.routing.routedatarequestcultureprovider) (in `Microsoft.AspNetCore.Localization.Routing`) that reads culture from route data, but it is not included in the default provider list. When `UseRouteBasedCulture` is enabled, ABP inserts it after `QueryStringRequestCultureProvider` and before `CookieRequestCultureProvider`. The resulting provider order is:
1. `QueryStringRequestCultureProvider` (ASP.NET Core default — useful for debugging and testing)
2. `RouteDataRequestCultureProvider` (URL path — inserted by ABP when enabled)
3. `CookieRequestCultureProvider` (ASP.NET Core default)
4. `AcceptLanguageHeaderRequestCultureProvider` (ASP.NET Core default)
If a URL contains an invalid culture code (e.g. `/xyz1234/page`), `RequestLocalizationMiddleware` ignores it and falls through to the next provider. No error is thrown.

164
docs/en/framework/infrastructure/app-urls.md

@ -0,0 +1,164 @@
```json
//[doc-seo]
{
"Description": "Configure cross-application URLs in ABP with AppUrlOptions and IAppUrlProvider, including multi-tenant subdomain templates and redirect URL validation."
}
```
# Application URLs
ABP provides the `AppUrlOptions` options class and the `IAppUrlProvider` service to centrally configure and resolve URLs that point to **other applications** in your solution (for example, an MVC/Razor Pages UI, an Auth Server, an HTTP API host, etc.). They are typically used when code in one application needs to build a link that targets another — like the Account module putting a **password reset link** into an email.
* Defines `AppUrlOptions` to register the **root URL** and named relative URLs of each application.
* Provides `IAppUrlProvider` to **resolve** those URLs at runtime, with optional **tenant-aware** placeholder substitution.
* Supports **subdomain-style templates** (e.g. `https://{0}.example.com`) that produce per-tenant URLs without extra code.
* Maintains a `RedirectAllowedUrls` list used by `IAppUrlProvider.IsRedirectAllowedUrlAsync` to validate redirect targets.
> `AppUrlOptions` is defined in the `Volo.Abp.UI.Navigation` package, which comes pre-installed with the [application startup template](../../solution-templates/layered-web-application).
## Configuring Application URLs
`AppUrlOptions` exposes a dictionary of **applications**, each with a `RootUrl` and a set of named `Urls`.
**Example: Set the root URL and a named URL for the MVC application**
```csharp
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].RootUrl = "https://my-app.com";
options.Applications["MVC"].Urls["MyPage"] = "my-page";
});
```
* `"MVC"` is the **application key**. Some modules (such as Account) register their URLs under a known key — `"MVC"` is the default for the **server-side UI**. You can use any key you want for your own applications.
* `RootUrl` is the **base URL** of that application.
* `Urls[urlName]` is a **relative path** appended to `RootUrl`. The final URL is built as `RootUrl.EnsureEndsWith('/') + Urls[urlName]`, so the relative path should **not** start with a `/`. When `RootUrl` is `null`, the value of `Urls[urlName]` is returned as-is.
The Account module, for example, **pre-registers** its URLs in its application module:
**Example: How the Account module registers the password reset URL**
```csharp
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].Urls[AccountUrlNames.PasswordReset] = "Account/ResetPassword";
});
```
> So configuring `Applications["MVC"].RootUrl` in your own module is usually enough to make password reset and similar Account email links point to the right host.
### Defaults in the application startup template
The ABP **application startup template** wires `Applications["MVC"].RootUrl` to the `App:SelfUrl` setting and seeds `RedirectAllowedUrls` from `App:RedirectAllowedUrls`:
```csharp
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
options.RedirectAllowedUrls.AddRange(
configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>());
});
```
> This is why Account email links point to your **host URL** out of the box: they reuse `App:SelfUrl`. If that default isn't what you want — for example, in a subdomain-based **multi-tenant** setup — override `Applications["MVC"].RootUrl` with the template you need (see [Multi-Tenant Aware URLs](#multi-tenant-aware-urls)).
## Using `IAppUrlProvider`
[Inject](../fundamentals/dependency-injection.md) the `IAppUrlProvider` service into any class that needs to build a cross-application URL.
**Example: Resolve a root URL and a named URL of the MVC application**
```csharp
public class MyNotificationSender : ITransientDependency
{
private readonly IAppUrlProvider _appUrlProvider;
public MyNotificationSender(IAppUrlProvider appUrlProvider)
{
_appUrlProvider = appUrlProvider;
}
public async Task SendAsync()
{
var rootUrl = await _appUrlProvider.GetUrlAsync("MVC");
var pageUrl = await _appUrlProvider.GetUrlAsync("MVC", "MyPage");
}
}
```
* `GetUrlAsync(appName)` returns the configured `RootUrl` for the given application.
* `GetUrlAsync(appName, urlName)` returns the **combined URL** described above.
* `GetUrlAsync(...)` throws an `AbpException` when the resolved URL is `null` or empty (e.g. both `RootUrl` and `Urls[urlName]` are unset). Use `GetUrlOrNullAsync(...)` if you'd rather get `null` and decide what to do yourself.
* `NormalizeUrlAsync(url)` applies tenant placeholder substitution to a URL string that you already have. Useful when the URL doesn't come from `AppUrlOptions`.
## Multi-Tenant Aware URLs
If your solution uses **subdomain-based** multi-tenancy (see the [Domain/Subdomain Tenant Resolver](../architecture/multi-tenancy/index.md#domainsubdomain-tenant-resolver)), you'll usually want the **outbound URLs** you generate (email links, redirects) to also be tenant-aware — otherwise the link in a password reset email won't point to the tenant's subdomain.
`AppUrlOptions` supports the following **placeholders** in any URL value. They are substituted by `IAppUrlProvider` based on the **current tenant**:
| Placeholder | Replaced with |
| --- | --- |
| `{0}` | Current tenant **name** |
| `{%{{{ {{tenantName}} }}}%}` | Current tenant **name** |
| `{%{{{ {{tenantId}} }}}%}` | Current tenant **id** |
The `{0}` placeholder uses the **same convention** as `AddDomainTenantResolver("{0}.example.com")`, so a typical subdomain-tenant setup looks like this:
**Example: Tenant-aware Account email links via a subdomain template**
```csharp
Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.example.com");
});
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].RootUrl = "https://{0}.example.com";
});
```
With this configuration, password reset emails sent to a tenant whose name is `acme` will contain a link starting with `https://acme.example.com/`, matching the tenant's subdomain.
### Host (no tenant) Fallback
When there is **no current tenant** (host-side request), the placeholder **and the dot following it** are removed together:
| Template | Tenant `acme` | Host (no tenant) |
| --- | --- | --- |
| `https://{0}.example.com` | `https://acme.example.com` | `https://example.com` |
| `https://{%{{{ {{tenantId}} }}}%}.example.com` | `https://3a21....example.com` | `https://example.com` |
A single subdomain-style template like the ones above therefore works for **both** tenant and host scenarios without extra configuration.
> If your subdomain is based on the tenant **id** rather than the name, use `https://{%{{{ {{tenantId}} }}}%}.example.com`. The resolver's `{0}` placeholder accepts both name and id when finding a tenant, but `AppUrlOptions` substitutes `{0}` with the tenant **name**; if those two don't match, switch to the explicit `{%{{{ {{tenantId}} }}}%}` form on the `AppUrlOptions` side.
## Redirect Allowed URLs
`AppUrlOptions.RedirectAllowedUrls` is a list of URL entries used by `IAppUrlProvider.IsRedirectAllowedUrlAsync(url)` to decide whether a redirect target is allowed. A URL is allowed when it satisfies **either** of:
* **Prefix match**: the URL string **starts with** a configured entry (case-insensitive).
* **Subdomain match**: the URL and the entry have the **same scheme** and **port**, and the URL's host **ends with** `.{entry-host}`.
**Example: Register allowed redirect URLs (including a wildcard)**
```csharp
Configure<AppUrlOptions>(options =>
{
options.RedirectAllowedUrls.Add("https://my-app.com");
options.RedirectAllowedUrls.Add("https://admin.my-app.com");
options.RedirectAllowedUrls.Add("https://*.my-app.com");
});
```
* A **plain entry** like `https://my-app.com` allows any URL that starts with that prefix, plus any subdomain of `my-app.com`.
* A **wildcard entry** like `https://*.my-app.com` allows any subdomain of `my-app.com`; the `*.` is stripped before the subdomain check.
* Entries also go through **tenant placeholder substitution**, so `https://{0}.my-app.com` is resolved to the current tenant's URL first (e.g. `https://acme.my-app.com`) and then compared. Use the wildcard form when you need to allow *any* tenant subdomain regardless of the current tenant.
## See Also
* [Multi-Tenancy](../architecture/multi-tenancy/index.md)
* [Account Module](../../modules/account.md)
* [Emailing](emailing.md)

1
docs/en/framework/infrastructure/emailing.md

@ -265,3 +265,4 @@ So, don't confuse if you don't receive emails on DEBUG mode. Emails will be sent
## See Also ## See Also
* [MailKit integration for sending emails](./mail-kit.md) * [MailKit integration for sending emails](./mail-kit.md)
* [Application URLs](./app-urls.md) — for building cross-application links inside email content (e.g. password reset links).

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

@ -1,12 +1,12 @@
```json ```json
//[doc-seo] //[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 # 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. > 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 ## 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) * [Magick.NET](#magick-net-provider)
* [ImageSharp](#imagesharp-provider) * [ImageSharp](#imagesharp-provider)
* [SkiaSharp](#skiasharp-provider)
You should install one of these provides to make it actually working. 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 ## ASP.NET Core Integration
`Volo.Abp.Imaging.AspNetCore` NuGet package defines attributes for controller actions that can automatically compress and/or resize uploaded files. `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 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 ## Installation

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

@ -7,6 +7,31 @@
# Scriban Integration # 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 ## Installation
It is suggested to use the [ABP CLI](../../../cli) to install this package. It is suggested to use the [ABP CLI](../../../cli) to install this package.

2
docs/en/framework/ui/angular/authorization.md

@ -105,7 +105,7 @@ function configureAuthFilter() {
} }
``` ```
- `AuthErrorFilter:` is a model for filter object and it have 3 properties - `AuthErrorFilter:` is a model for filter object and it has 3 properties
- `id:` a unique key in the list for the filter object - `id:` a unique key in the list for the filter object
- `executable:` a status for the filter object. If it's false then it won't work, yet it'll stay in the list - `executable:` a status for the filter object. If it's false then it won't work, yet it'll stay in the list
- `execute:` a function that stores the skip logic - `execute:` a function that stores the skip logic

1
docs/en/framework/ui/angular/checkbox-component.md

@ -14,7 +14,6 @@ The ABP Checkbox Component is a reusable form input component for the checkbox t
- `label` - `label`
- `labelClass (default form-check-label)` - `labelClass (default form-check-label)`
- `checkboxId` - `checkboxId`
- `checkboxReadonly`
- `checkboxReadonly (default form-check-input)` - `checkboxReadonly (default form-check-input)`
- `checkboxStyle` - `checkboxStyle`

18
docs/en/framework/ui/angular/data-table-column-extensions.md

@ -15,6 +15,8 @@ Entity prop extension system allows you to add a new column to the data table fo
You will have access to the current entity in your code and display its value, make the column sortable, perform visibility checks, and more. You can also render custom HTML in table cells. You will have access to the current entity in your code and display its value, make the column sortable, perform visibility checks, and more. You can also render custom HTML in table cells.
> **Standalone-first:** Current ABP templates use standalone APIs. The `loadChildren` examples below lazy-load routes from `createRoutes({ ... })` — they do not require NgModules. Legacy NgModule projects can pass the same options to `IdentityModule.forLazy({ ... })` instead. See [ABP Now Supports Angular Standalone Applications](https://abp.io/community/articles/abp-now-supports-angular-standalone-applications-zzi2rr2z).
## How to Set Up ## How to Set Up
In this example, we will add a "Name" column and display the value of the `name` field in the user management page of the [Identity Module](../../../modules/identity.md). In this example, we will add a "Name" column and display the value of the `name` field in the user management page of the [Identity Module](../../../modules/identity.md).
@ -64,7 +66,7 @@ Import `identityEntityPropContributors` in your routing configuration and pass i
```js ```js
// src/app/app.routes.ts // src/app/app.routes.ts
// other imports import { Routes } from '@angular/router';
import { identityEntityPropContributors } from './entity-prop-contributors'; import { identityEntityPropContributors } from './entity-prop-contributors';
export const APP_ROUTES: Routes = [ export const APP_ROUTES: Routes = [
@ -84,6 +86,20 @@ export const APP_ROUTES: Routes = [
]; ];
``` ```
#### Legacy NgModule projects
```js
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
entityPropContributors: identityEntityPropContributors,
}),
),
},
```
That is it, `nameProp` entity prop will be added, and you will see the "Name" column next to the usernames on the grid in the users page (`UsersComponent`) of the `identity` package. That is it, `nameProp` entity prop will be added, and you will see the "Name" column next to the usernames on the grid in the users page (`UsersComponent`) of the `identity` package.
## How to Render Custom HTML in Cells ## How to Render Custom HTML in Cells

19
docs/en/framework/ui/angular/dynamic-form-extensions.md

@ -16,6 +16,8 @@ Form prop extension system allows you to add a new field to the create and/or ed
You can validate the field, perform visibility checks, and do more. You will also have access to the current entity when creating a contributor for an edit form. You can validate the field, perform visibility checks, and do more. You will also have access to the current entity when creating a contributor for an edit form.
> **Standalone-first:** Current ABP templates use standalone APIs. The `loadChildren` examples below lazy-load routes from `createRoutes({ ... })` — they do not require NgModules. Legacy NgModule projects can pass the same options to `IdentityModule.forLazy({ ... })` instead. See [ABP Now Supports Angular Standalone Applications](https://abp.io/community/articles/abp-now-supports-angular-standalone-applications-zzi2rr2z).
## How to Set Up ## How to Set Up
In this example, we will add a "Date of Birth" field in the user management page of the [Identity Module](../../../modules/identity.md) and validate it. In this example, we will add a "Date of Birth" field in the user management page of the [Identity Module](../../../modules/identity.md) and validate it.
@ -69,7 +71,7 @@ Import `identityCreateFormPropContributors` and `identityEditFormPropContributor
```js ```js
// src/app/app.routes.ts // src/app/app.routes.ts
// other imports import { Routes } from '@angular/router';
import { import {
identityCreateFormPropContributors, identityCreateFormPropContributors,
identityEditFormPropContributors, identityEditFormPropContributors,
@ -93,6 +95,21 @@ export const APP_ROUTES: Routes = [
]; ];
``` ```
#### Legacy NgModule projects
```js
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
createFormPropContributors: identityCreateFormPropContributors,
editFormPropContributors: identityEditFormPropContributors,
}),
),
},
```
That is it, `birthdayProp` form prop will be added, and you will see the datepicker for the "Date of Birth" field right before the "Email address" in the forms of the users page in the `identity` package. That is it, `birthdayProp` form prop will be added, and you will see the datepicker for the "Date of Birth" field right before the "Email address" in the forms of the users page in the `identity` package.
## Object Extensions ## Object Extensions

20
docs/en/framework/ui/angular/entity-action-extensions.md

@ -15,6 +15,8 @@ Entity action extension system allows you to add a new action to the action menu
You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can also access the current entity in your code. You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can also access the current entity in your code.
> **Standalone-first:** Current ABP templates use standalone APIs. The `loadChildren` examples below lazy-load routes from `createRoutes({ ... })` — they do not require NgModules. Legacy NgModule projects can pass the same options to `IdentityModule.forLazy({ ... })` instead. See [ABP Now Supports Angular Standalone Applications](https://abp.io/community/articles/abp-now-supports-angular-standalone-applications-zzi2rr2z).
## How to Set Up ## How to Set Up
In this example, we will add a "Click Me!" action and alert the current row's `userName` in the user management page of the [Identity Module](../../../modules/identity.md). In this example, we will add a "Click Me!" action and alert the current row's `userName` in the user management page of the [Identity Module](../../../modules/identity.md).
@ -56,12 +58,12 @@ The list of actions, conveniently named as `actionList`, is a **doubly linked li
### Step 2. Import and Use Entity Action Contributors ### Step 2. Import and Use Entity Action Contributors
Import `identityEntityActionContributors` in your routing configuration and pass it to the static `configureRoutes` method for `identity` routes as seen below: Import `identityEntityActionContributors` in your routing configuration and pass it to the static `createRoutes` method for `identity` routes as seen below:
```js ```js
// src/app/app.routes.ts // src/app/app.routes.ts
// other imports import { Routes } from '@angular/router';
import { identityEntityActionContributors } from './entity-action-contributors'; import { identityEntityActionContributors } from './entity-action-contributors';
export const APP_ROUTES: Routes = [ export const APP_ROUTES: Routes = [
@ -81,6 +83,20 @@ export const APP_ROUTES: Routes = [
]; ];
``` ```
#### Legacy NgModule projects
```js
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
entityActionContributors: identityEntityActionContributors,
}),
),
},
```
That is it, `alertUserName` entity action will be added as the last action on the grid dropdown in the "Users" page (`UsersComponent`) of the `identity` package. That is it, `alertUserName` entity action will be added as the last action on the grid dropdown in the "Users" page (`UsersComponent`) of the `identity` package.
## How to Place a Custom Modal and Trigger It by Entity Actions ## How to Place a Custom Modal and Trigger It by Entity Actions

2
docs/en/framework/ui/angular/extensions-overall.md

@ -16,6 +16,8 @@ See the documents below for the details:
* [Page Toolbar Extension](page-toolbar-extensions.md) * [Page Toolbar Extension](page-toolbar-extensions.md)
* [Dynamic Form (or Form Prop) Extensions](dynamic-form-extensions.md) * [Dynamic Form (or Form Prop) Extensions](dynamic-form-extensions.md)
> **Standalone-first:** Current ABP templates use standalone APIs (`app.config.ts`, `app.routes.ts`, and `createRoutes()`). Extension examples register contributors through route-level lazy loading — `loadChildren` returns routes from `createRoutes({ ... })`, not NgModules. Legacy NgModule projects can pass the same contributor options to `SomeModule.forLazy({ ... })` instead. See [ABP Now Supports Angular Standalone Applications](https://abp.io/community/articles/abp-now-supports-angular-standalone-applications-zzi2rr2z) for the `forLazy()` → `createRoutes()` migration.
## Extensible Table Component ## Extensible Table Component
Using [ngx-datatable](https://github.com/swimlane/ngx-datatable) in extensible table. Using [ngx-datatable](https://github.com/swimlane/ngx-datatable) in extensible table.

16
docs/en/framework/ui/angular/feature-libraries.md

@ -9,6 +9,8 @@
ABP has an ever-growing number of feature modules and [introducing a new one](../../architecture/modularity/basics.md) is always possible. When the UI is Angular, these features have modular Angular libraries accompanying them. ABP has an ever-growing number of feature modules and [introducing a new one](../../architecture/modularity/basics.md) is always possible. When the UI is Angular, these features have modular Angular libraries accompanying them.
> **Standalone-first:** Current templates use standalone APIs. Configuration providers such as `provideIdentityConfig()` belong in `app.config.ts`, and features are lazy-loaded via `createRoutes()` in `app.routes.ts`. The `loadChildren` pattern below loads **route definitions**, not NgModules. In legacy NgModule projects, replace `createRoutes()` with `IdentityModule.forLazy()` (or the equivalent `forLazy()` method on the feature module). See [ABP Now Supports Angular Standalone Applications](https://abp.io/community/articles/abp-now-supports-angular-standalone-applications-zzi2rr2z).
## Feature Library Content ## Feature Library Content
Each library has at least two key elements: Each library has at least two key elements:
@ -85,6 +87,20 @@ When you load the identity feature like this, the "Users" page, for example, wil
Depending on the library, the `.createRoutes` static method may also receive some options that configure how the feature works. Depending on the library, the `.createRoutes` static method may also receive some options that configure how the feature works.
#### Legacy NgModule projects
If your application still uses NgModules, lazy-load the feature with `forLazy()` instead:
```js
{
path: "identity",
loadChildren: () =>
import("@abp/ng.identity").then((m) => m.IdentityModule.forLazy()),
},
```
Pass the same options object to `forLazy({ ... })` that you would pass to `createRoutes({ ... })` when configuring extensions or other feature options.
--- ---
<sup id="f-modify-route"><b>1</b></sup> _Libraries expect to work at a predefined path. Please check [how to patch a navigation element](./modifying-the-menu.md#how-to-patch-or-remove-a-navigation-element), if you want to use a different path from the default one (e.g. '/identity')._ <sup>[↩](#a-modify-route)</sup> <sup id="f-modify-route"><b>1</b></sup> _Libraries expect to work at a predefined path. Please check [how to patch a navigation element](./modifying-the-menu.md#how-to-patch-or-remove-a-navigation-element), if you want to use a different path from the default one (e.g. '/identity')._ <sup>[↩](#a-modify-route)</sup>

2
docs/en/framework/ui/angular/features.md

@ -7,7 +7,7 @@
# Features # Features
You can get the value of a feature on the client-side using the [config state service](./config-state.md) if it is allowed by the feature definition on the server-side. You can get the value of a feature on the client-side using the [config state service](./config-state-service.md) if it is allowed by the feature definition on the server-side.
> This document explains how to get feature values in an Angular application. See the [Features document](../../infrastructure/features.md) to learn the feature system. > This document explains how to get feature values in an Angular application. See the [Features document](../../infrastructure/features.md) to learn the feature system.

2
docs/en/framework/ui/angular/how-replaceable-components-work-with-extensions.md

@ -9,6 +9,8 @@
Additional UI extensibility points ([Entity action extensions](../angular/entity-action-extensions.md), [data table column extensions](../angular/data-table-column-extensions.md), [page toolbar extensions](../angular/page-toolbar-extensions.md) and others) are used in ABP pages to allow to control entity actions, table columns and page toolbar of a page. If you replace a page, you need to apply some configurations to be able to work extension components in your component. Let's see how to do this by replacing the roles page. Additional UI extensibility points ([Entity action extensions](../angular/entity-action-extensions.md), [data table column extensions](../angular/data-table-column-extensions.md), [page toolbar extensions](../angular/page-toolbar-extensions.md) and others) are used in ABP pages to allow to control entity actions, table columns and page toolbar of a page. If you replace a page, you need to apply some configurations to be able to work extension components in your component. Let's see how to do this by replacing the roles page.
> **Standalone-first:** The example below uses standalone components with an `imports` array and `inject()`. Current ABP templates follow this pattern. Module-based examples in older docs remain valid for legacy projects — see [ABP Now Supports Angular Standalone Applications](https://abp.io/community/articles/abp-now-supports-angular-standalone-applications-zzi2rr2z).
Create a new component called `MyRolesComponent`: Create a new component called `MyRolesComponent`:
```bash ```bash

2
docs/en/framework/ui/angular/internet-connection-service.md

@ -17,7 +17,7 @@ When you inject the InternetConnectionService you can get the current internet s
# How To Use # How To Use
İt's easy, just inject the service and get the network status. It's easy, just inject the service and get the network status.
**You can get via signal** **You can get via signal**
```ts ```ts

2
docs/en/framework/ui/angular/oauth-module.md

@ -7,7 +7,7 @@
# ABP OAuth Package # ABP OAuth Package
The authentication functionality has been moved from @abp/ng.core to @abp/ng.ouath since v7.0. The authentication functionality has been moved from @abp/ng.core to @abp/ng.oauth since v7.0.
If your app is version 8.3 or higher, you should include "provideAbpOAuth()" after "provideAbpCore()" in the `appConfig` array of your `app.config.ts`. If your app is version 8.3 or higher, you should include "provideAbpOAuth()" after "provideAbpCore()" in the `appConfig` array of your `app.config.ts`.

41
docs/en/framework/ui/angular/page-toolbar-extensions.md

@ -15,6 +15,8 @@ Page toolbar extension system allows you to add a new action to the toolbar of a
You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can also access to page data (the main record, usually an entity list) in your code. Additionally, you can pass in custom components instead of using the default button. You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can also access to page data (the main record, usually an entity list) in your code. Additionally, you can pass in custom components instead of using the default button.
> **Standalone-first:** Current ABP templates use standalone APIs. The `loadChildren` examples below lazy-load routes from `createRoutes({ ... })` — they do not require NgModules. Legacy NgModule projects can pass the same options to `IdentityModule.forLazy({ ... })` instead. See [ABP Now Supports Angular Standalone Applications](https://abp.io/community/articles/abp-now-supports-angular-standalone-applications-zzi2rr2z).
## How to Add an Action to Page Toolbar ## How to Add an Action to Page Toolbar
In this example, we will add a "Click Me!" action and log `userName` of all users in the user management page of the [Identity Module](../../../modules/identity.md) to the console. In this example, we will add a "Click Me!" action and log `userName` of all users in the user management page of the [Identity Module](../../../modules/identity.md) to the console.
@ -65,7 +67,7 @@ Import `identityToolbarActionContributors` in your routing configuration and pas
```js ```js
// src/app/app.routes.ts // src/app/app.routes.ts
// other imports import { Routes } from '@angular/router';
import { identityToolbarActionContributors } from './toolbar-action-contributors'; import { identityToolbarActionContributors } from './toolbar-action-contributors';
export const APP_ROUTES: Routes = [ export const APP_ROUTES: Routes = [
@ -85,6 +87,20 @@ export const APP_ROUTES: Routes = [
]; ];
``` ```
#### Legacy NgModule projects
```js
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
toolbarActionContributors: identityToolbarActionContributors,
}),
),
},
```
That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `identity` package. That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `identity` package.
## How to Add a Custom Component to Page Toolbar ## How to Add a Custom Component to Page Toolbar
@ -100,7 +116,7 @@ We need to have a component before we can pass it to the toolbar action contribu
```js ```js
// src/app/click-me-button.component.ts // src/app/click-me-button.component.ts
import { Component, Inject } from '@angular/core'; import { Component, inject } from '@angular/core';
import { IdentityUserDto } from '@abp/ng.identity/proxy'; import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { ActionData, EXTENSIONS_ACTION_DATA } from '@abp/ng.components/extensible'; import { ActionData, EXTENSIONS_ACTION_DATA } from '@abp/ng.components/extensible';
@ -109,10 +125,7 @@ import { ActionData, EXTENSIONS_ACTION_DATA } from '@abp/ng.components/extensibl
template: `<button class="btn btn-warning" (click)="handleClick()">Click Me!</button>`, template: `<button class="btn btn-warning" (click)="handleClick()">Click Me!</button>`,
}) })
export class ClickMeButtonComponent { export class ClickMeButtonComponent {
constructor( private data = inject<ActionData<IdentityUserDto[]>>(EXTENSIONS_ACTION_DATA);
@Inject(EXTENSIONS_ACTION_DATA)
private data: ActionData<IdentityUserDto[]>
) {}
handleClick() { handleClick() {
this.data.record.forEach(user => console.log(user.userName)); this.data.record.forEach(user => console.log(user.userName));
@ -168,7 +181,7 @@ Import `identityToolbarActionContributors` in your routing configuration and pas
```js ```js
// src/app/app.routes.ts // src/app/app.routes.ts
// other imports import { Routes } from '@angular/router';
import { identityToolbarActionContributors } from './toolbar-action-contributors'; import { identityToolbarActionContributors } from './toolbar-action-contributors';
export const APP_ROUTES: Routes = [ export const APP_ROUTES: Routes = [
@ -188,6 +201,20 @@ export const APP_ROUTES: Routes = [
]; ];
``` ```
#### Legacy NgModule projects
```js
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
toolbarActionContributors: identityToolbarActionContributors,
}),
),
},
```
That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `identity` package and it will be triggered by a custom button, i.e. `ClickMeButtonComponent`. Please note that **component projection is not limited to buttons** and you may use other UI components. That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `identity` package and it will be triggered by a custom button, i.e. `ClickMeButtonComponent`. Please note that **component projection is not limited to buttons** and you may use other UI components.
## How to Place a Custom Modal and Trigger It by Toolbar Actions ## How to Place a Custom Modal and Trigger It by Toolbar Actions

5
docs/en/framework/ui/angular/quick-start.md

@ -7,7 +7,7 @@
# ABP Angular Quick Start # ABP Angular Quick Start
**In this version ABP uses Angular [21.0.x](https://github.com/angular/angular/tree/21.0.x) version. You don't have to install Angular CLI globally** **In this version ABP uses Angular [21.2.x](https://github.com/angular/angular/tree/21.2.x) version. You don't have to install Angular CLI globally**
## How to Prepare Development Environment ## How to Prepare Development Environment
@ -22,7 +22,6 @@ Please follow the steps below to prepare your development environment for Angula
- [Visual Studio IntelliCode](https://marketplace.visualstudio.com/items?itemName=visualstudioexptteam.vscodeintellicode) - [Visual Studio IntelliCode](https://marketplace.visualstudio.com/items?itemName=visualstudioexptteam.vscodeintellicode)
- [Path Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense) - [Path Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense)
- [npm Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.npm-intellisense) - [npm Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.npm-intellisense)
- [Angular 10 Snippets - TypeScript, Html, Angular Material, ngRx, RxJS & Flex Layout](https://marketplace.visualstudio.com/items?itemName=Mikael.Angular-BeastCode)
- [JavaScript (ES6) code snippets](https://marketplace.visualstudio.com/items?itemName=xabikos.JavaScriptSnippets) - [JavaScript (ES6) code snippets](https://marketplace.visualstudio.com/items?itemName=xabikos.JavaScriptSnippets)
- [JavaScript Debugger](https://marketplace.visualstudio.com/items?itemName=ms-vscode.js-debug) (built-in, usually pre-installed) - [JavaScript Debugger](https://marketplace.visualstudio.com/items?itemName=ms-vscode.js-debug) (built-in, usually pre-installed)
- [Git History](https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory) - [Git History](https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory)
@ -167,7 +166,7 @@ When you run the development server, variables defined in _environment.ts_ take
2. Run `yarn` or `npm install` if you have not installed dependencies already. 2. Run `yarn` or `npm install` if you have not installed dependencies already.
3. Run `yarn build:prod` or `npm run build:prod`. 3. Run `yarn build:prod` or `npm run build:prod`.
<img alt="Angular compiler optimizing the build using Terser" src="./images/quick-start---self-signed-certificate-error.png" width="400px" style="max-width:100%"> <img alt="Browser blocking access to backend API due to self-signed certificate error" src="./images/quick-start---self-signed-certificate-error.png" width="400px" style="max-width:100%">
Depending on project size, the compilation may take a few minutes. When it is finished, the compiled output will be placed inside the _/dist_ folder. Voila! You have deployment-ready build artifacts. Depending on project size, the compilation may take a few minutes. When it is finished, the compiled output will be placed inside the _/dist_ folder. Voila! You have deployment-ready build artifacts.

2
docs/en/framework/ui/angular/settings.md

@ -7,7 +7,7 @@
# Settings # Settings
You can get settings on the client-side using the [config state service](./config-state.md) if they are allowed by their setting definition on the server-side. You can get settings on the client-side using the [config state service](./config-state-service.md) if they are allowed by their setting definition on the server-side.
> This document only explains how settings work in the Angular UI projects. See the [settings document](../../infrastructure/settings.md) to understand the ABP setting system. > This document only explains how settings work in the Angular UI projects. See the [settings document](../../infrastructure/settings.md) to understand the ABP setting system.

411
docs/en/framework/ui/angular/testing.md

@ -1,7 +1,7 @@
```json ```json
//[doc-seo] //[doc-seo]
{ {
"Description": "Learn how to unit test your ABP Angular UI applications with preconfigured Karma and Jasmine, plus ABP-specific testing topics." "Description": "Learn how to unit test your ABP Angular UI applications with preconfigured Vitest and TestBed, plus ABP-specific testing topics."
} }
``` ```
@ -9,89 +9,105 @@
ABP Angular UI is tested like any other Angular application. So, [the guide here](https://angular.dev/guide/testing) applies to ABP too. That said, we would like to point out some **unit testing topics specific to ABP Angular applications**. ABP Angular UI is tested like any other Angular application. So, [the guide here](https://angular.dev/guide/testing) applies to ABP too. That said, we would like to point out some **unit testing topics specific to ABP Angular applications**.
## Setup ## Test Stack
In Angular, unit tests use [Karma](https://karma-runner.github.io/) and [Jasmine](https://jasmine.github.io) by default. Although we like Jest more, we chose not to deviate from these defaults, so **the application template you download will have Karma and Jasmine preconfigured**. You can find the Karma configuration inside the _karma.conf.js_ file in the root folder. You don't have to do anything. Adding a spec file and running `npm test` will work. The application template you download is preconfigured for unit testing. You can add a `*.spec.ts` file and run `yarn test` without adding extra test infrastructure.
## Basics | Package / API | Purpose |
| --- | --- |
| [Vitest](https://vitest.dev/) | Test runner and assertion library. |
| [jsdom](https://github.com/jsdom/jsdom) | Browser-like DOM environment for component tests. |
| `@angular/core/testing` (`TestBed`) | The standard testing utilities of Angular for components, services, and pipes. |
| `@abp/ng.core/testing` | ABP testing module and helpers that replace real ABP services with mocks. |
| `@abp/ng.theme.shared/testing` | Testing module for shared theme features such as validation. |
An over-simplified spec file looks like this: ABP Angular packages in the [framework repository](https://github.com/abpframework/abp/tree/dev/npm/ng-packs) use the same Vitest setup. Library tests there also use [`@ngneat/spectator/vitest`](https://github.com/ngneat/spectator) for HTTP and component tests, but the application template uses `TestBed` directly.
```js ## Configuration
import { CoreTestingModule } from "@abp/ng.core/testing";
import { ThemeBasicTestingModule } from "@abp/ng.theme.basic/testing";
import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing";
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
import { NgxValidateCoreModule } from "@ngx-validate/core";
import { MyComponent } from "./my.component";
describe("MyComponent", () => { The test target in _angular.json_ uses Angular's built-in Vitest builder:
let fixture: ComponentFixture<MyComponent>;
beforeEach( ```json
waitForAsync(() => { // angular.json
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [
CoreTestingModule.withConfig(),
ThemeSharedTestingModule.withConfig(),
ThemeBasicTestingModule.withConfig(),
NgxValidateCoreModule,
],
providers: [
/* mock providers here */
],
}).compileComponents();
})
);
beforeEach(() => { "test": {
fixture = TestBed.createComponent(MyComponent); "builder": "@angular/build:unit-test"
fixture.detectChanges(); }
}); ```
it("should be initiated", () => { Spec files are compiled with _tsconfig.spec.json_, which enables Vitest globals:
expect(fixture.componentInstance).toBeTruthy();
}); ```json
}); // tsconfig.spec.json
{
"compilerOptions": {
"types": ["vitest/globals"]
},
"include": ["src/**/*.spec.ts"]
}
``` ```
If you take a look at the imports, you will notice that we have prepared some testing modules to replace built-in ABP modules. This is necessary for providing mocks for some features which otherwise would break your tests. Please remember to **use testing modules** and **call their `withConfig` static method**. You do not need a _karma.conf.js_ file. Angular CLI wires Vitest and jsdom for you.
## Tips ## Running Tests
### Angular Testing Library Run tests in watch mode:
```bash
yarn test
```
Although you can test your code with Angular TestBed, you may find [Angular Testing Library](https://testing-library.com/docs/angular-testing-library/intro) a good alternative. Run tests once, which is useful for CI:
The simple example above can be written with Angular Testing Library as follows: ```bash
ng test --watch=false
```
Vitest exits with a non-zero status code when a test fails, so the command above works in pipelines.
## Basics
```js An over-simplified spec file looks like this:
```ts
import { CoreTestingModule } from "@abp/ng.core/testing"; import { CoreTestingModule } from "@abp/ng.core/testing";
import { ThemeBasicTestingModule } from "@abp/ng.theme.basic/testing";
import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing"; import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing";
import { ComponentFixture } from "@angular/core/testing"; import { ComponentFixture, TestBed } from "@angular/core/testing";
import { NgxValidateCoreModule } from "@ngx-validate/core"; import { NgxValidateCoreModule } from "@ngx-validate/core";
import { render } from "@testing-library/angular"; import { AuthService } from "@abp/ng.core";
import { vi } from "vitest";
import { MyComponent } from "./my.component"; import { MyComponent } from "./my.component";
describe("MyComponent", () => { describe("MyComponent", () => {
let fixture: ComponentFixture<MyComponent>; let fixture: ComponentFixture<MyComponent>;
let mockAuthService: { isAuthenticated: boolean; navigateToLogin: ReturnType<typeof vi.fn> };
beforeEach(async () => { beforeEach(async () => {
const result = await render(MyComponent, { mockAuthService = {
isAuthenticated: false,
navigateToLogin: vi.fn(),
};
await TestBed.configureTestingModule({
imports: [ imports: [
CoreTestingModule.withConfig(), CoreTestingModule.withConfig(),
ThemeSharedTestingModule.withConfig(), ThemeSharedTestingModule.withConfig(),
ThemeBasicTestingModule.withConfig(),
NgxValidateCoreModule, NgxValidateCoreModule,
MyComponent,
], ],
providers: [ providers: [
/* mock providers here */ {
provide: AuthService,
useValue: mockAuthService,
},
], ],
}); }).compileComponents();
});
fixture = result.fixture; beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
}); });
it("should be initiated", () => { it("should be initiated", () => {
@ -100,45 +116,36 @@ describe("MyComponent", () => {
}); });
``` ```
Very similar, as you can see. The real difference kicks in when we use queries and fire events. If you take a look at the imports, you will notice that we have prepared some testing modules to replace built-in ABP modules. This is necessary for providing mocks for some features which otherwise would break your tests. Please remember to **use testing modules** and **call their `withConfig` static method**.
```js Current templates use standalone components, so put the component under test in the `imports` array instead of `declarations`.
// other imports
import { getByLabelText, screen } from "@testing-library/angular";
import userEvent from "@testing-library/user-event";
describe("MyComponent", () => { If your application uses `@abp/ng.theme.basic`, also import `ThemeBasicTestingModule.withConfig()` from `@abp/ng.theme.basic/testing`.
beforeEach(/* removed for sake of brevity */);
it("should display advanced filters", () => { ### Mocking Dependencies
const filters = screen.getByTestId("author-filters");
const nameInput = getByLabelText(filters, /name/i) as HTMLInputElement;
expect(nameInput.offsetWidth).toBe(0);
const advancedFiltersBtn = screen.getByRole("link", { name: /advanced/i }); Use Vitest mocks instead of Jasmine spies:
userEvent.click(advancedFiltersBtn);
expect(nameInput.offsetWidth).toBeGreaterThan(0); ```ts
import { vi } from "vitest";
userEvent.type(nameInput, "fooo{backspace}"); const deleteSpy = vi.fn().mockReturnValue(of(null));
expect(nameInput.value).toBe("foo"); fixture.componentInstance.service.delete = deleteSpy;
});
}); expect(deleteSpy).toHaveBeenCalledWith("some-id");
``` ```
The **queries in Angular Testing Library follow practices for maintainable tests**, the user event package provides a **human-like interaction** with the DOM, and the library in general has **a clear API** that simplifies component testing. Please find some useful links below: The template's `home.component.spec.ts` is a good reference for mocking ABP services and asserting DOM behavior with `TestBed`.
- [Queries](https://testing-library.com/docs/dom-testing-library/api-queries) ## Tips
- [User Event](https://testing-library.com/docs/ecosystem-user-event)
- [Examples](https://github.com/testing-library/angular-testing-library/tree/main/apps/example-app/src/app/examples)
### Clearing DOM After Each Spec ### Clearing DOM After Each Spec
One thing to remember is that Karma runs tests in real browser instances. That means, you will be able to see the result of your test code, but also have problems with components attached to the document body which may not get cleared after each test, even when you configure Karma to do so. Tests run in jsdom, not a real browser. Components attached to `document.body` — such as modals, confirmation dialogs, and toasts — may not be removed automatically between specs.
We have prepared a simple function with which you can clear any leftover DOM elements after each test. We have prepared a simple function with which you can clear leftover DOM elements after each test:
```js ```ts
// other imports // other imports
import { clearPage } from "@abp/ng.core/testing"; import { clearPage } from "@abp/ng.core/testing";
@ -147,239 +154,189 @@ describe("MyComponent", () => {
afterEach(() => clearPage(fixture)); afterEach(() => clearPage(fixture));
beforeEach(async () => {
const result = await render(MyComponent, {
/* removed for sake of brevity */
});
fixture = result.fixture;
});
// specs here // specs here
}); });
``` ```
Please make sure you use it because Karma will fail to remove dialogs otherwise and you will have multiple copies of modals, confirmation boxes, and alike. Please use it when you test features that render into the document body. Otherwise you may end up with multiple copies of modals, confirmation boxes, and similar elements.
### Waiting ### Waiting
Some components, modals, in particular, work off-detection-cycle. In other words, you cannot reach DOM elements inserted by these components immediately after opening them. Similarly, inserted elements are not immediately destroyed upon closing them. Some components, modals in particular, work off the change-detection cycle. In other words, you cannot reach DOM elements inserted by these components immediately after opening them. Similarly, inserted elements are not immediately destroyed upon closing them.
For this purpose, we have prepared a `wait` function. For this purpose, we have prepared a `wait` function:
```js ```ts
// other imports // other imports
import { wait } from "@abp/ng.core/testing"; import { wait } from "@abp/ng.core/testing";
describe("MyComponent", () => { describe("MyComponent", () => {
beforeEach(/* removed for sake of brevity */); let fixture: ComponentFixture<MyComponent>;
it("should open a modal", async () => { it("should open a modal", async () => {
const openModalBtn = screen.getByRole("button", { name: "Open Modal" }); const openModalBtn = fixture.nativeElement.querySelector('[role="button"]');
userEvent.click(openModalBtn); openModalBtn.click();
await wait(fixture); await wait(fixture);
const modal = screen.getByRole("dialog"); const modal = fixture.nativeElement.ownerDocument.querySelector('[role="dialog"]');
expect(modal).toBeTruthy(); expect(modal).toBeTruthy();
/* wait again after closing the modal */
}); });
}); });
``` ```
The `wait` function takes a second parameter, i.e. timeout (default: `0`). Try not to use it though. Using a timeout bigger than `0` is usually a signal that something is not quite right. The `wait` function takes a second parameter, i.e. timeout (default: `0`). Try not to use it though. Using a timeout bigger than `0` is usually a signal that something is not quite right.
## Testing Example ### Angular Testing Library
Although you can test your code with Angular TestBed, you may find [Angular Testing Library](https://testing-library.com/docs/angular-testing-library/intro) a good alternative. It is not included in the application template by default, but you can add `@testing-library/angular` and `@testing-library/user-event` if you prefer that style.
Here is an example test suite. It doesn't cover all, but gives quite a good idea about what the testing experience will be like. The ABP testing modules work the same way with Testing Library:
```js ```ts
import { clearPage, CoreTestingModule, wait } from "@abp/ng.core/testing"; import { CoreTestingModule } from "@abp/ng.core/testing";
import { ThemeBasicTestingModule } from "@abp/ng.theme.basic/testing";
import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing"; import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing";
import { ComponentFixture } from "@angular/core/testing"; import { ComponentFixture } from "@angular/core/testing";
import {
NgbCollapseModule,
NgbDatepickerModule,
NgbDropdownModule,
} from "@ng-bootstrap/ng-bootstrap";
import { NgxValidateCoreModule } from "@ngx-validate/core"; import { NgxValidateCoreModule } from "@ngx-validate/core";
import { CountryService } from "@proxy/countries"; import { render, screen } from "@testing-library/angular";
import { import { MyComponent } from "./my.component";
findByText,
getByLabelText,
getByRole,
getByText,
queryByRole,
render,
screen,
} from "@testing-library/angular";
import userEvent from "@testing-library/user-event";
import { BehaviorSubject, of } from "rxjs";
import { CountryComponent } from "./country.component";
const list$ = new BehaviorSubject({
items: [{ id: "ID_US", name: "United States of America" }],
totalCount: 1,
});
describe("Country", () => {
let fixture: ComponentFixture<CountryComponent>;
afterEach(() => clearPage(fixture)); describe("MyComponent", () => {
let fixture: ComponentFixture<MyComponent>;
beforeEach(async () => { beforeEach(async () => {
const result = await render(CountryComponent, { const result = await render(MyComponent, {
imports: [ imports: [
CoreTestingModule.withConfig(), CoreTestingModule.withConfig(),
ThemeSharedTestingModule.withConfig(), ThemeSharedTestingModule.withConfig(),
ThemeBasicTestingModule.withConfig(),
NgxValidateCoreModule, NgxValidateCoreModule,
NgbCollapseModule,
NgbDatepickerModule,
NgbDropdownModule,
], ],
providers: [ providers: [
{ /* mock providers here */
provide: CountryService,
useValue: {
getList: () => list$,
},
},
], ],
}); });
fixture = result.fixture; fixture = result.fixture;
}); });
it("should display advanced filters", () => { it("should be initiated", () => {
const filters = screen.getByTestId("country-filters"); expect(fixture.componentInstance).toBeTruthy();
const nameInput = getByLabelText(filters, /name/i) as HTMLInputElement;
expect(nameInput.offsetWidth).toBe(0);
const advancedFiltersBtn = screen.getByRole("link", { name: /advanced/i });
userEvent.click(advancedFiltersBtn);
expect(nameInput.offsetWidth).toBeGreaterThan(0);
userEvent.type(nameInput, "fooo{backspace}");
expect(nameInput.value).toBe("foo");
userEvent.click(advancedFiltersBtn);
expect(nameInput.offsetWidth).toBe(0);
});
it("should have a heading", () => {
const heading = screen.getByRole("heading", { name: "Countries" });
expect(heading).toBeTruthy();
}); });
});
```
it("should render list in table", async () => { The **queries in Angular Testing Library follow practices for maintainable tests**, the user event package provides a **human-like interaction** with the DOM, and the library in general has **a clear API** that simplifies component testing. Please find some useful links below:
const table = await screen.findByTestId("country-table");
const name = getByText(table, "United States of America"); - [Queries](https://testing-library.com/docs/dom-testing-library/api-queries)
expect(name).toBeTruthy(); - [User Event](https://testing-library.com/docs/ecosystem-user-event)
}); - [Examples](https://github.com/testing-library/angular-testing-library/tree/main/apps/example-app/src/app/examples)
it("should display edit modal", async () => { When you use Testing Library with modals or confirmation dialogs, combine it with `clearPage` and `wait` from `@abp/ng.core/testing` as shown above.
const actionsBtn = screen.queryByRole("button", { name: /actions/i });
userEvent.click(actionsBtn);
const editBtn = screen.getByRole("button", { name: /edit/i }); ## Testing Example
userEvent.click(editBtn);
await wait(fixture); Here is an example based on the application template's `home.component.spec.ts`. It shows how to mock an ABP service and assert component state and DOM output:
const modal = screen.getByRole("dialog"); ```ts
const modalHeading = queryByRole(modal, "heading", { name: /edit/i }); import { CoreTestingModule } from "@abp/ng.core/testing";
expect(modalHeading).toBeTruthy(); import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { NgxValidateCoreModule } from "@ngx-validate/core";
import { AuthService } from "@abp/ng.core";
import { vi } from "vitest";
import { HomeComponent } from "./home.component";
const closeBtn = getByText(modal, "×"); describe("HomeComponent", () => {
userEvent.click(closeBtn); let fixture: ComponentFixture<HomeComponent>;
let mockAuthService: { isAuthenticated: boolean; navigateToLogin: ReturnType<typeof vi.fn> };
await wait(fixture); beforeEach(async () => {
mockAuthService = {
isAuthenticated: false,
navigateToLogin: vi.fn(),
};
expect(screen.queryByRole("dialog")).toBeFalsy(); await TestBed.configureTestingModule({
imports: [
CoreTestingModule.withConfig(),
ThemeSharedTestingModule.withConfig(),
NgxValidateCoreModule,
HomeComponent,
],
providers: [
{
provide: AuthService,
useValue: mockAuthService,
},
],
}).compileComponents();
}); });
it("should display create modal", async () => { it("should be initiated", () => {
const newBtn = screen.getByRole("button", { name: /new/i }); fixture = TestBed.createComponent(HomeComponent);
userEvent.click(newBtn); fixture.detectChanges();
expect(fixture.componentInstance).toBeTruthy();
await wait(fixture);
const modal = screen.getByRole("dialog");
const modalHeading = queryByRole(modal, "heading", { name: /new/i });
expect(modalHeading).toBeTruthy();
}); });
it("should validate required name field", async () => { describe("when login state is false", () => {
const newBtn = screen.getByRole("button", { name: /new/i }); beforeEach(() => {
userEvent.click(newBtn); mockAuthService.isAuthenticated = false;
fixture = TestBed.createComponent(HomeComponent);
fixture.detectChanges();
});
await wait(fixture); it("hasLoggedIn should be false", () => {
expect(fixture.componentInstance.hasLoggedIn).toBe(false);
});
const modal = screen.getByRole("dialog"); it("button should exist", () => {
const nameInput = getByRole(modal, "textbox", { const button = fixture.nativeElement.querySelector('[role="button"]');
name: /^name/i, expect(button).toBeDefined();
}) as HTMLInputElement; });
userEvent.type(nameInput, "x"); describe("when button clicked", () => {
userEvent.type(nameInput, "{backspace}"); beforeEach(() => {
const button = fixture.nativeElement.querySelector('[role="button"]');
button.click();
});
const nameError = await findByText(modal, /required/i); it("navigateToLogin should have been called", () => {
expect(nameError).toBeTruthy(); expect(mockAuthService.navigateToLogin).toHaveBeenCalled();
});
});
}); });
});
```
it("should delete a country", () => { For list pages with modals, confirmations, and service proxies, keep using the ABP testing modules, mock your generated proxy services with `vi.fn()`, and use `clearPage` / `wait` when body-level UI is involved.
const getSpy = spyOn(fixture.componentInstance.list, "get");
const deleteSpy = jasmine.createSpy().and.returnValue(of(null));
fixture.componentInstance.service.delete = deleteSpy;
const actionsBtn = screen.queryByRole("button", { name: /actions/i });
userEvent.click(actionsBtn);
const deleteBtn = screen.getByRole("button", { name: /delete/i });
userEvent.click(deleteBtn);
const confirmText = screen.getByText("AreYouSure"); ## CI Configuration
expect(confirmText).toBeTruthy();
const confirmBtn = screen.getByRole("button", { name: "Yes" }); Run unit tests once in CI with:
userEvent.click(confirmBtn);
expect(deleteSpy).toHaveBeenCalledWith(list$.value.items[0].id); ```sh
expect(getSpy).toHaveBeenCalledTimes(1); ng test --watch=false
});
});
``` ```
## CI Configuration If you need a dedicated CI configuration, add one under the `test` target in _angular.json_:
You would need a different configuration for your CI environment. To set up a new configuration for your unit tests, find the test project in _angular.json_ file and add one as seen below:
```json ```json
// angular.json // angular.json
"test": { "test": {
"builder": "@angular-devkit/build-angular:karma", "builder": "@angular/build:unit-test",
"options": { /* several options here */ },
"configurations": { "configurations": {
"production": { "ci": {
"karmaConfig": "karma.conf.prod.js" "watch": false
} }
} }
} }
``` ```
Now you can copy the _karma.conf.js_ as _karma.conf.prod.js_ and use any configuration you like in it. Please check [Karma configuration file document](http://karma-runner.github.io/5.2/config/configuration-file.html) for config options. Then run:
Finally, don't forget to run your CI tests with the following command:
```sh ```sh
npm test -- --prod ng test --configuration=ci
``` ```
## See Also ## See Also

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

@ -10,7 +10,8 @@
````json ````json
//[doc-params] //[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. > 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 ## Installation
If you need to manually this theme, follow the steps below: 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 ```json
//[doc-seo] //[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 # 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. `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 ## Quick Example
@ -29,4 +38,54 @@ Notice that we didn't specify any text, like `Save Changes`. This is because `Su
<SubmitButton Clicked="@YourSaveOperation"> <SubmitButton Clicked="@YourSaveOperation">
@L["Save"] @L["Save"]
</SubmitButton> </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 ````json
//[doc-params] //[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: The content of the `MyBranding.razor` is shown below:
{{if BlazorUI == "Blazorise"}}
````html ````html
@using Volo.Abp.DependencyInjection @using Volo.Abp.DependencyInjection
{{if UI == "BlazorServer"}} {{if UI == "BlazorServer"}}
@ -58,6 +61,33 @@ The content of the `MyBranding.razor` is shown below:
</a> </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: 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). * `@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 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** **MyBlazor.razor**
````html ````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 ## 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. 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 ```json
//[doc-seo] //[doc-seo]
{ {
@ -102,6 +109,8 @@ public class CustomTableColumn
Navigate to the razor file and paste the following code. Navigate to the razor file and paste the following code.
{{if BlazorUI == "Blazorise"}}
```csharp ```csharp
@using System @using System
@using Volo.Abp.Identity @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. Navigate back to the `CustomizedUserManagement` class, and use `Component` property to specify the custom blazor component.
```csharp ```csharp

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

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json ```json
//[doc-seo] //[doc-seo]
{ {
@ -95,6 +102,8 @@ Here, the list of the properties that you use in the `EntityAction`.
#### Example #### Example
{{if BlazorUI == "Blazorise"}}
```csharp ```csharp
var clickMeAction = new EntityAction() 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 ````json
//[doc-params] //[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** **Example**
````csharp {{if BlazorUI == "Blazorise"}}
````razor
@page "/" @page "/"
@using Volo.Abp @using Volo.Abp
@ -60,11 +63,41 @@ There are different type of `Exception` classes handled differently by the ABP.
{{end}} {{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"}} {{if UI == "Blazor"}}
**Example** **Example**
````csharp {{if BlazorUI == "Blazorise"}}
````razor
@page "/" @page "/"
@using Volo.Abp @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}} {{end}}
ABP automatically handle the exception and show an error message to the user: ABP automatically handle the exception and show an error message to the user:

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

@ -1,12 +1,21 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json ```json
//[doc-seo] //[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 # 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. 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 ## Sample
@ -44,4 +53,118 @@ _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).
### Modal Focus Behavior
By default a `MudFocusTrap` inside `<MudDialog>` focuses the first tabbable child element after the dialog opens. When the dialog contains a `<MudTabs>` as the first child, that "first tabbable element" is the tab button — not the first input on the active tab — so the user has to click into the field manually.
For dialogs that contain a `<MudTabs>`, use this three-part setup to focus the intended input automatically:
```razor
<MudDialog DefaultFocus="DefaultFocus.None" @ref="_createDialog" Options="@CreateDialogOptions">
<DialogContent>
<MudForm @ref="@CreateFormRef" Model="@NewEntity">
<MudTabs @bind-ActivePanelIndex="@_createTabIndex" KeepPanelsAlive="true">
<MudTabPanel Text="@L["UserInformations"]">
<MudTextField @bind-Value="@NewEntity.UserName"
Label="@L["UserName"]"
AutoFocus="true"
Required="true" />
...
</MudTabPanel>
...
```
- `DefaultFocus="DefaultFocus.None"` on the `<MudDialog>` disables the focus trap's automatic focus so it doesn't grab the tab button.
- `KeepPanelsAlive="true"` on the `<MudTabs>` mounts every tab panel up front, so the first input's `firstRender` happens at dialog-open time (otherwise inactive panels are mounted later, and `AutoFocus` runs after the dialog is already visible).
- `AutoFocus="true"` on the first input asks MudBlazor to focus that field on its first render.
`DialogOptions.DefaultFocus` is ignored for inline dialogs (`<MudDialog @ref> + ShowAsync()`), so always set `DefaultFocus` directly on the `<MudDialog>` element.
For a dialog without `<MudTabs>` (first child is the input), `DefaultFocus="DefaultFocus.FirstChild"` (the MudBlazor default) is enough and you don't need `AutoFocus`.
{{end}}

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

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json ```json
//[doc-seo] //[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: 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. * [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. * [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. * [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. > 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 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): 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):

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

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json ```json
//[doc-seo] //[doc-seo]
{ {
@ -7,7 +14,23 @@
# Blazor UI: Page Header # Blazor UI: Page Header
You can use the`PageHeader` component to set the page title, the breadcrumb items and the toolbar items for a page. Before using the `PageHeader` component, you need to add a using statement for the `Volo.Abp.AspNetCore.Components.Web.Theming.Layout` namespace. You can use the `PageHeader` component to set the page title, the breadcrumb items and the toolbar items for a page. Before using the `PageHeader` component, you need to add a using statement for the namespace:
{{if BlazorUI == "Blazorise"}}
```razor
@using Volo.Abp.AspNetCore.Components.Web.Theming.Layout
```
{{end}}
{{if BlazorUI == "MudBlazor"}}
```razor
@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout
```
{{end}}
Once you add the `PageHeader` component to your page, you can control the related values using the parameters. Once you add the `PageHeader` component to your page, you can control the related values using the parameters.
@ -30,6 +53,8 @@ Breadcrumbs can be added using the `BreadcrumbItems` property.
**Example: Add Language Management to the breadcrumb items.** **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. Create a collection of `Volo.Abp.BlazoriseUI.BreadcrumbItem` objects and set the collection to the `BreadcrumbItems` parameter.
```csharp ```csharp
@ -44,6 +69,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. Navigate back to the razor page.
```csharp ```csharp
@ -57,19 +107,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. * 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. * 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. * `text`: The text to show for the breadcrumb item.
* `url` (optional): A URL to navigate to, if the user clicks to 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`. * `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
Page toolbar can be set using the `Toolbar` property. Page toolbar can be set using the `Toolbar` property.
**Example: Add a "New Item" toolbar item to the page toolbar.** **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 ```csharp
public partial class Index public partial class Index
@ -87,6 +156,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. Navigate back to the razor page and set the `Toolbar` parameter.
```csharp ```csharp

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

@ -1,3 +1,10 @@
```json
//[doc-params]
{
"BlazorUI": ["Blazorise", "MudBlazor"]
}
```
```json ```json
//[doc-seo] //[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. Menu item name can be set on runtime too.
{{if BlazorUI == "Blazorise"}}
```html ```html
@inject PageLayout PageLayout @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) ![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 ## BreadCrumbs
BreadCrumbItems are used to render breadcrumbs in the PageHeader. BreadCrumbItems are used to render breadcrumbs in the PageHeader.
{{if BlazorUI == "Blazorise"}}
```csharp ```csharp
@inject PageLayout PageLayout @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 ## Toolbar
ToolbarItems are used to render action toolbar items in the PageHeader. 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 ```json
//[doc-seo] //[doc-seo]
{ {
@ -27,6 +34,8 @@ We will use the [component override system](customization-overriding-components.
Here, the content of the overridden `SetToolbarItemsAsync` method. Here, the content of the overridden `SetToolbarItemsAsync` method.
{{if BlazorUI == "Blazorise"}}
```csharp ```csharp
protected override async ValueTask SetToolbarItemsAsync() protected override async ValueTask SetToolbarItemsAsync()
{ {
@ -38,10 +47,31 @@ protected override async ValueTask SetToolbarItemsAsync()
}, "file-import", Blazorise.Color.Secondary); }, "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. > 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. Here, the entire content of the file.
{{if BlazorUI == "Blazorise"}}
```csharp ```csharp
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars; 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). 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 ## Advanced Use Cases
@ -83,9 +144,21 @@ For this example, we've created a `MyToolbarComponent` component under the `/Pag
`MyToolbarComponent.razor` content: `MyToolbarComponent.razor` content:
````csharp {{if BlazorUI == "Blazorise"}}
````razor
<Button Color="Color.Dark">CLICK ME</Button> <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. We will leave the `MyToolbarComponent.razor.cs` file empty.
Then you can add the `MyToolbarComponent` to the user management page toolbar: 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 ````json
//[doc-params] //[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}} {{end}}
{{if BlazorUI == "Blazorise"}}
* [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. * [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. * [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. * [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. > 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 ### 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: 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. 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"}} {{if UI == "Blazor"}}
```bash ```bash
abp add-package Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme --with-source-code --add-to-solution-file 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}}
{{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 ### 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. 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.

7
docs/en/framework/ui/index.md

@ -1,16 +1,17 @@
```json ```json
//[doc-seo] //[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."
} }
``` ```
# ABP UI Options # ABP UI Options
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: ABP provides several options for building the user interface (UI) in your applications. React is part of the **modern template system**. 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) * [MVC / Razor Pages](./mvc-razor-pages/overall.md)
* [Blazor](./blazor/overall.md) * [Blazor](./blazor/overall.md)
* [Angular](./angular/quick-start.md) * [Angular](./angular/quick-start.md)
* [React Native](./react-native/index.md) * [React Native](./react-native/index.md)
* [MAUI](./maui/index.md) * [MAUI](./maui/index.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: 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. - **[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. - **[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. **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: 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. - Reusable [application modules](../../../modules) are developed **theme-independent**, so they can work with any UI theme.
* UI theme is **decided by the final application**. - UI theme is **decided by the final application**.
* The theme is distributed via NuGet/NPM packages, so it is **easily upgradable**. - The theme is distributed via NuGet/NPM packages, so it is **easily upgradable**.
* The final application can **customize** the selected theme. - The final application can **customize** the selected theme.
### Current Themes ### Current Themes
Currently, three themes are **officially provided**: 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 [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 [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 [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). 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): 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. 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** **Example: Get a list of authors from the server**
````js ```js
acme.bookStore.authors.author.getList({ acme.bookStore.authors.author.getList({
maxResultCount: 10 maxResultCount: 10
}).then(function(result){ }).then(function(result){
console.log(result.items); console.log(result.items);
}); });
```` ```
`acme.bookStore.authors.author.getList` is an auto-generated function that internally makes an AJAX call to the server. `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** **Example: Render a Bootstrap modal**
````html ```html
<abp-modal> <abp-modal>
<abp-modal-header title="Modal title" /> <abp-modal-header title="Modal title" />
<abp-modal-body> <abp-modal-body>
@ -116,7 +116,7 @@ ABP makes it easier & type safe to write Bootstrap HTML.
</abp-modal-body> </abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Save|AbpModalButtons.Close)"></abp-modal-footer> <abp-modal-footer buttons="@(AbpModalButtons.Save|AbpModalButtons.Close)"></abp-modal-footer>
</abp-modal> </abp-modal>
```` ```
See the [Tag Helpers](tag-helpers) document for more. 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** **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" /> <abp-dynamic-form abp-model="Movie" submit-button="true" />
```` ```
See the [Forms & Validation](forms-validation.md) document for details. 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. 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-bundle>
<abp-style src="/libs/bootstrap/css/bootstrap.css" /> <abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> <abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" /> <abp-style src="/libs/toastr/toastr.css" />
<abp-style src="/styles/my-global-style.css" /> <abp-style src="/styles/my-global-style.css" />
</abp-style-bundle> </abp-style-bundle>
```` ```
Also, Client Side Package Management system offers a modular and consistent way of managing 3rd-party library dependencies. 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; 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. - [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. - [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. - [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. - [Data Tables](data-tables.md) integration makes straightforward to create data grids.
## Customization ## 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).

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

@ -5,12 +5,12 @@
} }
``` ```
````json ```json
//[doc-params] //[doc-params]
{ {
"Architecture": ["Monolith", "Tiered", "Microservice"] "Architecture": ["Monolith", "Tiered", "Microservice"]
} }
```` ```
# Getting Started with React Native # 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**. 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) React Native gif
## How to Prepare Development Environment ## 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. 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. 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: 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. - **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. - **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.
> **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 ## 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: 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 ### 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. 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) ### 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. 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: Follow these steps to set up the web view:
1. Navigate to the `react-native` directory and start the application by running: 1. Navigate to the `react-native` directory and start the application by running:
```bash ```bash
yarn web yarn web
``` ```
2. Generate SSL certificates by running the following command in a separate directory: 2. Generate SSL certificates by running the following command in a separate directory:
```bash ```bash
mkcert localhost mkcert localhost
``` ```
3. Set up the local proxy by running: 3. Set up the local proxy by running:
```bash ```bash
yarn create:local-proxy yarn create:local-proxy
``` ```
The default port is `443`. To use a different port, specify the `SOURCE_PORT` environment variable: 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. 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: 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": { "OpenIddict": {
"Applications": { "Applications": {
"MyApplication_Mobile": { "MyApplication_Mobile": {
@ -99,7 +90,7 @@ Follow these steps to set up the web view:
} }
} }
} }
``` ```
### Running on Emulator/Simulator ### 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" }} {{ 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" }} {{ 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. > 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 }} {{ 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. > Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.AuthServer` project.
@ -131,22 +122,22 @@ 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. > 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. 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 ### Expo
![React Native login screen on iPhone 16](../../../images/rn-login-iphone.png) React Native login screen on iPhone 16
### Android Studio ### Android Studio
1. Start the emulator in **Android Studio** before running the `yarn start` or `npm start` command. 1. Start the emulator in **Android Studio** before running the `yarn start` or `npm start` command.
2. Press **a** to open in Android Studio. 2. Press **a** to open in Android Studio.
![React Native login screen on Android Device](../../../images/rn-login-android-studio.png) React Native login screen on Android Device
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. The application is up and running. You can continue to develop your application based on this startup template.
@ -172,11 +163,10 @@ A React Native application running on an Android emulator or a physical phone **
{{ if Architecture == "Monolith" }} {{ 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.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. - 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 ```json
{ {
"Kestrel": { "Kestrel": {
@ -191,11 +181,10 @@ A React Native application running on an Android emulator or a physical phone **
{{ else if Architecture == "Tiered" }} {{ 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.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. - 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 ```json
{ {
"Kestrel": { "Kestrel": {
@ -207,9 +196,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. - 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 ```json
{ {
"Kestrel": { "Kestrel": {
@ -230,10 +217,9 @@ A React Native application running on an Android emulator or a physical phone **
{{ else if Architecture == "Microservice" }} {{ 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. - 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 ```json
{ {
"App": { "App": {
@ -248,9 +234,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. - 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 ```json
{ {
"App": { "App": {
@ -271,9 +255,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. - Update the `appsettings.json` file in the `.IdentityService` folder. Replace the `localhost` configuration with your local IP address for the React Native application.
```json ```json
{ {
//... //...
@ -292,9 +274,7 @@ A React Native application running on an Android emulator or a physical phone **
} }
} }
``` ```
- Finally, update the mobile gateway configurations as follows: - Finally, update the mobile gateway configurations as follows:
```json ```json
//gateways/mobile/MyMicroserviceProject.MobileGateway/Properties/launchSettings.json //gateways/mobile/MyMicroserviceProject.MobileGateway/Properties/launchSettings.json
{ {
@ -319,7 +299,6 @@ A React Native application running on an Android emulator or a physical phone **
} }
} }
``` ```
```json ```json
//gateways/mobile/MyMicroserviceProject.MobileGateway/appsettings.json //gateways/mobile/MyMicroserviceProject.MobileGateway/appsettings.json
{ {
@ -367,7 +346,6 @@ A React Native application running on an Android emulator or a physical phone **
} }
} }
``` ```
{{ end }} {{ 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).

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. These packages are maintained by ABP and published on npm; use the following links to view their package details:
- [View `@volo/abp-app-config` on npm](https://www.npmjs.com/package/@volo/abp-app-config)
- [View `@volo/abp-oidc-auth` on npm](https://www.npmjs.com/package/@volo/abp-oidc-auth)
- [View `@volo/abp-react-app-config` on npm](https://www.npmjs.com/package/@volo/abp-react-app-config)
- [View `@volo/abp-react-oidc-auth` on npm](https://www.npmjs.com/package/@volo/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)

2
docs/en/get-started/index.md

@ -20,6 +20,8 @@ Please select one of the following documents best fits for your application:
- [WPF Application](wpf.md) - [WPF Application](wpf.md)
- [Console Application](console.md) - [Console Application](console.md)
If you seek a React-based web UI, use the **modern template system** with ABP Studio or `abp new --modern`. See [UI options](../framework/ui/index.md) for the full list of officially supported UI frameworks.
## Which Startup Template is Suitable for Me? ## Which Startup Template is Suitable for Me?
You can see the *[Solution Template Selection Guide](../solution-templates/guide.md)* if you are not sure which solution template is suitable for you. You can see the *[Solution Template Selection Guide](../solution-templates/guide.md)* if you are not sure which solution template is suitable for you.

8
docs/en/get-started/layered-web-application.md

@ -18,6 +18,10 @@
In this quick start guide, you will learn how to create and run a layered (and potentially modular) web application using [ABP Studio](../studio/index.md). In this quick start guide, you will learn how to create and run a layered (and potentially modular) web application using [ABP Studio](../studio/index.md).
> This page documents the **classic** layered flow.
>
> If you want **React UI**, use the **modern** template flow instead. Modern layered solutions create your application in the `react/` folder and host the ABP Admin Console from the backend at `/admin-console/`. See [React UI](../framework/ui/react/index.md) and the [ABP CLI modern templates](../cli/index.md#modern-templates) section for the correct path.
## Setup your development environment ## Setup your development environment
First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine: First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine:
@ -150,7 +154,7 @@ If you uncheck the *Kubernetes Configuration* option, the solution will not incl
On the next screen, you can configure the modularity options for your solution: On the next screen, you can configure the modularity options for your solution:
> If you select the *Setup as a modular solution* option, the solution is created more ready for [modular monolith development](../tutorials/modular-crm/index.md) and allows you to add sub-modules during the solution creation phase. > If your goal is a new modular monolith, prefer the dedicated **Modular Monolith** architecture in ABP Studio. The modularity option on this screen is the classic-host path for keeping this layered solution modularity-ready and allowing sub-modules during solution creation.
![abp-studio-new-solution-modularity](images/abp-studio-new-solution-dialog-modularity_dark.png) ![abp-studio-new-solution-modularity](images/abp-studio-new-solution-dialog-modularity_dark.png)
@ -281,4 +285,4 @@ You can start the following application(s):
## What's next? ## What's next?
- [TODO Application Tutorial with Layered Solution](../tutorials/todo/layered/index.md) - [TODO Application Tutorial with Layered Solution](../tutorials/todo/layered/index.md)
- [Web Application Development Tutorial](../tutorials/book-store/index.md) - [Web Application Development Tutorial](../tutorials/book-store/index.md)

65
docs/en/get-started/microservice.md

@ -11,6 +11,8 @@
In this quick start guide, you will learn how to create and run a microservice solution using [ABP Studio](../studio/index.md). In this quick start guide, you will learn how to create and run a microservice solution using [ABP Studio](../studio/index.md).
This guide follows the current **modern** microservice template in ABP Studio. When you select a web UI, ABP Studio creates the main application in `apps/react`, the administration UI in `apps/react-admin-console`, and optionally the public website in `apps/react-public-web`.
## Setup your development environment ## Setup your development environment
First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine: First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine:
@ -18,7 +20,7 @@ First things first! Let's setup your development environment before creating the
* [Visual Studio 2026](https://visualstudio.microsoft.com/vs/) or another IDE that supports .NET development * [Visual Studio 2026](https://visualstudio.microsoft.com/vs/) or another IDE that supports .NET development
* [.NET 10.0+](https://dotnet.microsoft.com/en-us/download/dotnet) * [.NET 10.0+](https://dotnet.microsoft.com/en-us/download/dotnet)
* [Node v22.11+](https://nodejs.org/) * [Node v22.11+](https://nodejs.org/)
* [Yarn v1.22+ (not v2+)](https://classic.yarnpkg.com/en/docs/install) or npm v10+ (already installed with Node), **This is required for the Angular applications.** * npm v10+ (already installed with Node) or [Yarn v1.22+ (not v2+)](https://classic.yarnpkg.com/en/docs/install) for the React applications
* [Docker Desktop (with Kubernetes enabled)](https://www.docker.com/products/docker-desktop/) * [Docker Desktop (with Kubernetes enabled)](https://www.docker.com/products/docker-desktop/)
* [Helm](https://helm.sh/docs/intro/install/) * [Helm](https://helm.sh/docs/intro/install/)
* [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/) * [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/)
@ -62,11 +64,11 @@ On that screen, you can enable multi-tenancy for your solution. After selecting
![abp-studio-new-solution-dialog-ui-framework](images/abp-studio-new-solution-dialog-ui-framework-microservice.png) ![abp-studio-new-solution-dialog-ui-framework](images/abp-studio-new-solution-dialog-ui-framework-microservice.png)
Here, you see all the possible UI options supported by that startup solution template. You can pick your favorite one and click the *Next* button for the *Mobile Framework* selection screen: For the modern microservice template, the web UI choices are `React` and `No UI`. Select `React` to create `MyCompanyName.MyProjectName.React` in `apps/react` together with the separate `MyCompanyName.MyProjectName.ReactAdminConsole` application in `apps/react-admin-console`. Then click the *Next* button for the *Mobile Framework* selection screen:
![abp-studio-new-solution-dialog-mobile-framework](images/abp-studio-new-solution-dialog-mobile-framework-microservice.png) ![abp-studio-new-solution-dialog-mobile-framework](images/abp-studio-new-solution-dialog-mobile-framework-microservice.png)
Here, you see all the mobile applications available in that startup solution template. These mobile applications are well-integrated into your solution and can use the same backend with your web application. They are simple (do not have pre-built features as much as the web application) but a very good starting point to build your mobile application. For the modern microservice template, the mobile choices are `React Native` and `None`. If you select `React Native`, ABP Studio creates the mobile app under `apps/mobile/react-native` and adds the `MyCompanyName.MyProjectName.MobileGateway` application.
> If you select a mobile application, an additional API Gateway is created that is only used by the mobile application. > If you select a mobile application, an additional API Gateway is created that is only used by the mobile application.
@ -74,7 +76,7 @@ Pick the one best for you, or select the *None* if you don't want a mobile appli
![abp-studio-new-solution-dialog-public-web-site](images/abp-studio-new-solution-dialog-public-web-site.png) ![abp-studio-new-solution-dialog-public-web-site](images/abp-studio-new-solution-dialog-public-web-site.png)
You can select a public website to be created in your solution. The public website is a simple landing page that can be used to introduce your product, provide documentation, and so on. If you enable the public website, ABP Studio creates the `MyCompanyName.MyProjectName.ReactPublicWeb` application under `apps/react-public-web` and the `MyCompanyName.MyProjectName.PublicGateway` application under `gateways/public`.
![abp-studio-new-solution-dialog-dynamic-localization](images/abp-studio-new-solution-dialog-dynamic-localization.png) ![abp-studio-new-solution-dialog-dynamic-localization](images/abp-studio-new-solution-dialog-dynamic-localization.png)
@ -136,7 +138,7 @@ Now, we are ready to allow ABP Studio to create our solution. Just click the *Cr
![abp-studio-created-new-microservice-solution](images/abp-studio-created-new-microservice-solution.png) ![abp-studio-created-new-microservice-solution](images/abp-studio-created-new-microservice-solution.png)
You can explore the solution, but you need to **wait for background tasks to be completed** before running any application in the solution (it can take up to a few minutes to set up all). You can explore the solution, but you need to **wait for background tasks to be completed** before running any application in the solution. ABP Studio uses that time to install the required client-side dependencies and create the Auth Server signing certificate.
> The solution structure can be different in your case based on the options you've selected. > The solution structure can be different in your case based on the options you've selected.
@ -148,9 +150,13 @@ This **solution** consists of several **modules** shown in the *Solution Explore
![abp-studio-created-microservice-solution-explorer](images/abp-studio-created-microservice-solution-explorer.png) ![abp-studio-created-microservice-solution-explorer](images/abp-studio-created-microservice-solution-explorer.png)
Each leaf item (e.g. `Acme.CloudCrm.IdentityService` or `Acme.CloudCrm.Web`) in the tree above is an ABP Studio module. They are grouped into solution folders (`apps`, `gateways`, and `services`). Each leaf item in the tree above is an ABP Studio module. They are grouped into solution folders (`apps`, `gateways`, and `services`):
* `apps`: contains `Acme.CloudCrm.AuthServer`, the main React UI as `Acme.CloudCrm.React`, the separate administration UI as `Acme.CloudCrm.ReactAdminConsole`, and optionally `Acme.CloudCrm.ReactPublicWeb`.
* `gateways`: contains `Acme.CloudCrm.WebGateway` for the main web UI, and optionally `Acme.CloudCrm.PublicGateway` and `Acme.CloudCrm.MobileGateway`.
* `services`: contains backend microservices such as `Acme.CloudCrm.AdministrationService`, `Acme.CloudCrm.IdentityService`, and the optional services selected in the wizard.
Each module has a separate .NET Solution. You can open a module's (or .NET solution's) folder by right-clicking a module in the *Solution Explorer* tree, select *Open with* -> *Explorer* option as shown below: The .NET applications in `apps/auth-server`, `gateways/*`, and `services/*` each have their own .NET solutions. The React applications are standard Node projects under `apps/react` and `apps/react-admin-console`, plus `apps/react-public-web` when the public website is enabled. You can open any module's folder by right-clicking it in the *Solution Explorer* tree and selecting *Open with* -> *Explorer* as shown below:
![abp-studio-open-module-folder](images/abp-studio-open-module-folder.png) ![abp-studio-open-module-folder](images/abp-studio-open-module-folder.png)
@ -158,15 +164,15 @@ If we open the `Acme.CloudCrm.IdentityService` module's path in the explorer, we
![abp-studio-microservice-example-identity-service-files](images/abp-studio-microservice-example-identity-service-files.png) ![abp-studio-microservice-example-identity-service-files](images/abp-studio-microservice-example-identity-service-files.png)
This microservice solution is designed to have separate .NET solutions for each service to make it possible to develop independently from the other services and applications. This microservice solution is designed so each backend service, gateway, and the Auth Server can be developed independently, while the React applications stay in their own app folders.
You can open any module's .NET solution in your favorite IDE and make your development. The following figure is a screenshot from the *Identity* microservice opened in Visual Studio: You can open any backend module's .NET solution in your favorite IDE and make your development. The following figure is a screenshot from the *Identity* microservice opened in Visual Studio:
![abp-studio-microservice-example-identity-service-in-visual-studio](images/abp-studio-microservice-example-identity-service-in-visual-studio.png) ![abp-studio-microservice-example-identity-service-in-visual-studio](images/abp-studio-microservice-example-identity-service-in-visual-studio.png)
If you explore that .NET solution, you will typically see some configuration code, and you won't see any business code. That's because the solution uses [pre-built application modules](../modules) as NuGet packages, and doesn't contain their source code. In this way, you can easily upgrade these application modules when a new version is available. If you explore that .NET solution, you will typically see composition and configuration code, and you won't see the source code of the built-in modules. That's because the solution uses [pre-built application modules](../modules) as NuGet packages. In this way, you can easily upgrade these application modules when a new version is available.
You will typically add new microservices to the solution and perform your business logic inside these new services (however, you can always want to download the source code of any pre-built application module and include it into your solution to freely customize it). You will typically add new microservices to the solution and perform your business logic inside these new services. You can also customize the React applications in `apps/react`, `apps/react-admin-console`, and, if enabled, `apps/react-public-web` when you need UI-specific changes.
## Running the Solution ## Running the Solution
@ -184,7 +190,7 @@ In the *Solution Runner* section (on the left side) you can see all the runnable
> A leaf item in the *Solution Runner* is called as an *Application* as it is an executable application, excluding items under `Containers`. > A leaf item in the *Solution Runner* is called as an *Application* as it is an executable application, excluding items under `Containers`.
As shown in the figure above, the executable applications are grouped into folders like `apps`, `gateways`, and `services`. You can start/stop them all, a group (folder) of them, or one by one. The `Containers` branch contains the needed docker containers for the applications. As shown in the figure above, the executable applications are grouped into folders like `apps`, `gateways`, and `services`. You can start/stop them all, a group (folder) of them, or one by one. The `Containers` branch contains the needed docker containers for the applications.
Before running the applications, you can run the all application by right-clicking the root item in the *Solution Runner* and select *Build* -> *Build All* action. However, you don't need to do that, because ABP Studio builds the applications before running them by default. Before running the applications, you can run the all application by right-clicking the root item in the *Solution Runner* and select *Build* -> *Build All* action. However, you don't need to do that, because ABP Studio builds the applications before running them by default.
@ -192,6 +198,19 @@ Before running the applications, you can run the all application by right-clicki
You can click the *Play* button on the root item in *Solution Runner* to start all the applications. You can click the *Play* button on the root item in *Solution Runner* to start all the applications.
For the common React-based web flow, the main applications are:
* `Acme.CloudCrm.AuthServer`
* `Acme.CloudCrm.WebGateway`
* `Acme.CloudCrm.AdministrationService`
* `Acme.CloudCrm.IdentityService`
* `Acme.CloudCrm.React`
* `Acme.CloudCrm.ReactAdminConsole`
If you enabled optional features, also start the related applications such as `Acme.CloudCrm.ReactPublicWeb`, `Acme.CloudCrm.PublicGateway`, `Acme.CloudCrm.MobileGateway`, `Acme.CloudCrm.SaasService`, `Acme.CloudCrm.AuditLoggingService`, `Acme.CloudCrm.GdprService`, `Acme.CloudCrm.ChatService`, or `Acme.CloudCrm.FileManagementService`.
> ABP Studio runs the React applications as CLI applications by executing `npm run dev` in their app folders.
> **About the Docker Containers** > **About the Docker Containers**
> >
> Docker will fetch the docker images before starting the containers in your first run (if they were not fetched before) and that process may take a few minutes depending on your internet connection speed. So, please wait for it to completely start. If the process takes more time than you expect, you can right-click on `Docker-Dependencies` and select the *Logs* command to see what's happening. > Docker will fetch the docker images before starting the containers in your first run (if they were not fetched before) and that process may take a few minutes depending on your internet connection speed. So, please wait for it to completely start. If the process takes more time than you expect, you can right-click on `Docker-Dependencies` and select the *Logs* command to see what's happening.
@ -200,29 +219,31 @@ You can click the *Play* button on the root item in *Solution Runner* to start a
> >
> Some applications/services may fail on the first run. That may be because of service and database dependencies were not satisfied and an error occurs on the application startup. ABP Studio automatically restarts failing services until it is successfully started. Being completely ready for such a distributed solution may take a while, but it will be eventually started. > Some applications/services may fail on the first run. That may be because of service and database dependencies were not satisfied and an error occurs on the application startup. ABP Studio automatically restarts failing services until it is successfully started. Being completely ready for such a distributed solution may take a while, but it will be eventually started.
Once all the applications are ready, you can right-click the `Web` application and select the *Browse* command: Once all the applications are ready, you can right-click the `Acme.CloudCrm.React` application and select the *Browse* command:
![abp-studio-microservice-solution-runner-browse](images/abp-studio-microservice-solution-runner-browse.png) ![abp-studio-microservice-solution-runner-browse](images/abp-studio-microservice-solution-runner-browse.png)
The *Browse* command opens the web application's UI in the built-in browser of ABP Studio: The *Browse* command opens the main React application's UI in the built-in browser of ABP Studio:
![abp-studio-microservice-solution-runner-browse-microservice](images/abp-studio-microservice-solution-runner-browse-microservice.png) ![abp-studio-microservice-solution-runner-browse-microservice](images/abp-studio-microservice-solution-runner-browse-microservice.png)
You can browse your application in a full-featured web browser in ABP Studio. Click the *Login* button in the application UI, enter `admin` as username and `1q2w3E*` as password to login to the application. You can browse your application in a full-featured web browser in ABP Studio. Click the *Login* button in the application UI, enter `admin` as username and `1q2w3E*` as password to log in to the application.
Use the same *Browse* command on `Acme.CloudCrm.ReactAdminConsole` to open the administration UI. That application runs separately and uses `/admin-console/` as its base path.
> You can also browse the other applications/services (that provides a UI) inside ABP Studio. In this way, you don't need to use an external browser or manually type the application's URL. > You can also browse the other applications/services (that provides a UI) inside ABP Studio. In this way, you don't need to use an external browser or manually type the application's URL.
## Developing Services Using the Solution Runner ## Developing Services Using the Solution Runner
Solution Runner not only runs a multi-applications system easier, but is also useful while developing your services and applications. In a microservice solution, you typically focus on one or a few services and applications. Assume that you want to make a development in `IdentityService`. You can use the following development flow: Solution Runner not only makes a multi-application system easier to run, but is also useful while developing your services and applications. In a microservice solution, you typically focus on one or a few services and applications. Assume that you want to make a development in `IdentityService`. You can use the following development flow:
* Start all the applications/services in the solution and test if everything works as expected. * Start all the applications/services in the solution and test if everything works as expected.
* Stop the `IdentityService` in the Solution Runner. * Stop the `IdentityService` in the Solution Runner.
* Open the `IdentityService`'s .NET solution in your favorite IDE (e.g. Visual Studio). As an easy way of opening it, you can use the *Solution Explorer*, find the `Acme.CloudCrm.IdentityService` module, right-click to it and select the *Open with* -> *Visual Studio* command. * Open the `IdentityService`'s .NET solution in your favorite IDE (e.g. Visual Studio). As an easy way of opening it, you can use the *Solution Explorer*, find the `Acme.CloudCrm.IdentityService` module, right-click it and select the *Open with* -> *Visual Studio* command.
* Make your development in the `IdentityService`. * Make your development in the `IdentityService`.
* Run (with or without debugging) your service in Visual Studio (or another IDE). * Run (with or without debugging) your service in Visual Studio (or another IDE).
Once you run the `IdentityService` in Visual Studio, it will be completely integrated into the rest of the system since they all run in your local machine. In addition, the `IdentityService` application will automatically connect to ABP Studio and send runtime data to it as it works in ABP Studio. When you run an application out of ABP Studio, it is shown as *external* in the Solution Runner and you can't stop it in ABP Studio (you should stop where you've started): Once you run the `IdentityService` in Visual Studio, it will be completely integrated into the rest of the system since they all run on your local machine. In addition, the `IdentityService` application will automatically connect to ABP Studio and send runtime data to it as it works in ABP Studio. When you run an application out of ABP Studio, it is shown as *external* in the Solution Runner and you can't stop it in ABP Studio (you should stop it where you've started it):
![abp-studio-microservice-solution-runner-external-service](images/abp-studio-microservice-solution-runner-external-service.png) ![abp-studio-microservice-solution-runner-external-service](images/abp-studio-microservice-solution-runner-external-service.png)
@ -236,6 +257,8 @@ To enable watching, right-click the application/service you want to watch, selec
Now, you can make your development on the `IdentityService`. Whenever you save a code file, it is automatically rebuilt and restarted by ABP Studio, so any change will be effective on the running solution in a few seconds. Now, you can make your development on the `IdentityService`. Whenever you save a code file, it is automatically rebuilt and restarted by ABP Studio, so any change will be effective on the running solution in a few seconds.
If you are working on the frontend instead, you can apply the same flow to `Acme.CloudCrm.React` or `Acme.CloudCrm.ReactAdminConsole`, and to `Acme.CloudCrm.ReactPublicWeb` when the public website is enabled, then continue from the related app folder with `npm run dev`.
When you enable watch for an application an *eye* icon is added near to the application: When you enable watch for an application an *eye* icon is added near to the application:
![abp-studio-microservice-solution-runner-watch-enabled-icon](images/abp-studio-microservice-solution-runner-watch-enabled-icon.png) ![abp-studio-microservice-solution-runner-watch-enabled-icon](images/abp-studio-microservice-solution-runner-watch-enabled-icon.png)
@ -271,7 +294,7 @@ After building the Docker images, it is ready to install the Helm chart to Kuber
> Installing chart should be fast. However, it may take time for being fully ready in Kubernetes. For example, if an image of a service (e.g. Redis, Rabbit) was not pulled before, it will need to pull image first. > Installing chart should be fast. However, it may take time for being fully ready in Kubernetes. For example, if an image of a service (e.g. Redis, Rabbit) was not pulled before, it will need to pull image first.
Once the solution is ready in Kubernetes, you can open a browser and visit the following URL: https://cloudcrm-local-web It will open a web page as shown below: Once the solution is ready in Kubernetes, you can open a browser and visit the following URL: `https://cloudcrm-local-web`. It opens the main web application as shown below:
![abp-studio-microservice-web-application-home-page](images/abp-studio-microservice-web-application-home-page.png) ![abp-studio-microservice-web-application-home-page](images/abp-studio-microservice-web-application-home-page.png)
@ -335,7 +358,7 @@ It will start the interception process, and finally you will see the *intercepti
![abp-studio-microservice-kubernetes-interception-enabled](images/abp-studio-microservice-kubernetes-interception-enabled.png) ![abp-studio-microservice-kubernetes-interception-enabled](images/abp-studio-microservice-kubernetes-interception-enabled.png)
From now on, all the traffic coming to the Audit Logging microservice is redirected to your local computer. If you open the Audit Logging page now (`https://cloudcrm-local-web/AuditLogs`), you get an error, because the request is redirected to your local machine but the Audit Logging service is not running on your local machine yet. From now on, all the traffic coming to the Audit Logging microservice is redirected to your local computer. If you open the Audit Logging page now (`https://cloudcrm-local-web/admin-console/audit-logs`), you get an error, because the request is redirected to your local machine but the Audit Logging service is not running on your local machine yet.
Open the `Acme.CloudCrm.AuditLoggingService` .NET solution in your IDE (e.g. Visual Studio), set the `Acme.CloudCrm.AuditLoggingService` as startup project and run it (using F5 for debug mode or CTRL+F5 to run it without debugging). Open the `Acme.CloudCrm.AuditLoggingService` .NET solution in your IDE (e.g. Visual Studio), set the `Acme.CloudCrm.AuditLoggingService` as startup project and run it (using F5 for debug mode or CTRL+F5 to run it without debugging).
@ -355,7 +378,7 @@ A typical development flow can be as the following:
* *Connect* to a Kubernetes cluster where the solution is already deployed (as explained in the *Kubernetes Integration: Connecting to the Cluster* section). You can do it yourself as explained in the *Kubernetes Integration: Working with Helm Charts* section. * *Connect* to a Kubernetes cluster where the solution is already deployed (as explained in the *Kubernetes Integration: Connecting to the Cluster* section). You can do it yourself as explained in the *Kubernetes Integration: Working with Helm Charts* section.
* *Intercept* a service you want to develop in your local machine. * *Intercept* a service you want to develop in your local machine.
* Develop, run, stop, fix, debug, re-run... your service easily in your local environment. You can test your service as integrated to others and visit the application UI in the Kubernetes (you can make it for the Web application as similar). * Develop, run, stop, fix, debug, re-run... your service easily in your local environment. You can test your service as integrated to others and visit the application UI in Kubernetes. You can follow a similar flow for the main React application or the Admin Console.
* Once your development is done, you can *Disable* the interception and re-deploy the service to the Kubernetes cluster. * Once your development is done, you can *Disable* the interception and re-deploy the service to the Kubernetes cluster.
To re-deploy a service to Kubernetes, right-click the service and select *Commands* -> *Redeploy* command: To re-deploy a service to Kubernetes, right-click the service and select *Commands* -> *Redeploy* command:

8
docs/en/get-started/single-layer-web-application.md

@ -17,6 +17,10 @@
In this quick start guide, you will learn how to create and run a single layer web application using [ABP Studio](../studio/index.md). In this quick start guide, you will learn how to create and run a single layer web application using [ABP Studio](../studio/index.md).
> This page documents the **classic** single-layer flow.
>
> If you want **React UI**, use the **modern** template flow instead. Modern single-layer solutions create your application in the `react/` folder and host the ABP Admin Console from the backend at `/admin-console/`. See [React UI](../framework/ui/react/index.md) and the [ABP CLI modern templates](../cli/index.md#modern-templates) section for the correct path.
## Setup your development environment ## Setup your development environment
First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine: First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine:
@ -116,7 +120,7 @@ You can change these settings later if needed. Then click the *Next* button for
Configure any additional options as needed and click the *Next* button to continue. On the next screen, you can configure the modularity options for your solution: Configure any additional options as needed and click the *Next* button to continue. On the next screen, you can configure the modularity options for your solution:
> If you select the *Setup as a modular solution* option, the solution is created more ready for [modular monolith development](../tutorials/modular-crm/index.md) and allows you to add sub-modules during the solution creation phase. > If your goal is a new modular monolith, prefer the dedicated **Modular Monolith** architecture in ABP Studio. The modularity option on this screen is the classic-host path for keeping this single-layer solution modularity-ready and allowing sub-modules during solution creation.
![abp-studio-no-layers-new-solution-modularity](images/abp-studio-no-layers-new-solution-modularity_dark.png) ![abp-studio-no-layers-new-solution-modularity](images/abp-studio-no-layers-new-solution-modularity_dark.png)
@ -188,4 +192,4 @@ You can use `admin` as username and `1q2w3E*` as default password to login to th
## What's next? ## What's next?
- [TODO Application Tutorial with Single-Layer Solution](../tutorials/todo/single-layer/index.md) - [TODO Application Tutorial with Single-Layer Solution](../tutorials/todo/single-layer/index.md)

2
docs/en/guides/ms-multi-tenant-domain-resolving.md

@ -68,6 +68,8 @@ This configuration will allow subdomain tenant resolving. Ex, if you have a tena
**For angular application**, you don't need to add anything since we will be overriding the angular environment via kubernetes values file. **For angular application**, you don't need to add anything since we will be overriding the angular environment via kubernetes values file.
> The configuration above resolves the current tenant from the incoming request. To make outbound URLs generated through `IAppUrlProvider` (Account email links, redirects, etc.) also tenant-aware, configure `AppUrlOptions` with the same `{0}` template. See [Application URLs](../framework/infrastructure/app-urls.md#multi-tenant-aware-urls).
## Configuring AuthServer ## Configuring AuthServer
When the tenant try to login from an application (Ex `https://volosoft.angular.mystore.dev`) it will be redirected to AuthServer (`https://volosoft.authserver.mystore.dev`) and you will be seeing a **HTTP 400 error** related to `invalid redirect_uri`. If you check the authserver application logs (under Logs/logs.txt file or console logs), you will notice that the `https://volosoft.angular.mystore.dev` is not a valid redirect_uri since it is not been seeded by the OpenIddictDataSeeder. Only the **host** applications, gateways and microservice URLs are seeded (like `https://angular.mystore.dev`). When the tenant try to login from an application (Ex `https://volosoft.angular.mystore.dev`) it will be redirected to AuthServer (`https://volosoft.authserver.mystore.dev`) and you will be seeing a **HTTP 400 error** related to `invalid redirect_uri`. If you check the authserver application logs (under Logs/logs.txt file or console logs), you will notice that the `https://volosoft.angular.mystore.dev` is not a valid redirect_uri since it is not been seeded by the OpenIddictDataSeeder. Only the **host** applications, gateways and microservice URLs are seeded (like `https://angular.mystore.dev`).

BIN
docs/en/images/abp-studio-ai-agent.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/images/layered-project-dependencies-blazor-server.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
docs/en/images/layered-project-dependencies-blazor-wasm.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

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

Loading…
Cancel
Save