Browse Source

Merge remote-tracking branch 'origin/rel-10.1' into issue/cms-kit-angular

pull/24234/head
sumeyye 1 week ago
parent
commit
1eb31129c1
  1. 8
      .claude/settings.local.json
  2. 21
      .github/workflows/auto-pr.yml
  3. 2
      .gitignore
  4. 124
      Directory.Packages.props
  5. 151
      ai-rules/README.md
  6. 182
      ai-rules/common/abp-core.mdc
  7. 236
      ai-rules/common/application-layer.mdc
  8. 186
      ai-rules/common/authorization.mdc
  9. 92
      ai-rules/common/cli-commands.mdc
  10. 244
      ai-rules/common/ddd-patterns.mdc
  11. 153
      ai-rules/common/dependency-rules.mdc
  12. 299
      ai-rules/common/development-flow.mdc
  13. 249
      ai-rules/common/infrastructure.mdc
  14. 165
      ai-rules/common/multi-tenancy.mdc
  15. 257
      ai-rules/data/ef-core.mdc
  16. 206
      ai-rules/data/mongodb.mdc
  17. 83
      ai-rules/template-specific/app-nolayers.mdc
  18. 209
      ai-rules/template-specific/microservice.mdc
  19. 234
      ai-rules/template-specific/module.mdc
  20. 274
      ai-rules/testing/patterns.mdc
  21. 224
      ai-rules/ui/angular.mdc
  22. 210
      ai-rules/ui/blazor.mdc
  23. 262
      ai-rules/ui/mvc.mdc
  24. 4
      common.props
  25. 2
      docs/en/deployment/configuring-production.md
  26. 14
      docs/en/docs-nav.json
  27. 2
      docs/en/framework/api-development/standard-apis/configuration.md
  28. 2
      docs/en/framework/architecture/domain-driven-design/application-services.md
  29. 23
      docs/en/framework/architecture/domain-driven-design/entities.md
  30. 69
      docs/en/framework/architecture/microservices/index.md
  31. 2
      docs/en/framework/architecture/modularity/extending/customizing-application-modules-guide.md
  32. 97
      docs/en/framework/fundamentals/authorization/index.md
  33. 241
      docs/en/framework/fundamentals/authorization/resource-based-authorization.md
  34. 2
      docs/en/framework/fundamentals/dynamic-claims.md
  35. 2
      docs/en/framework/fundamentals/exception-handling.md
  36. 2
      docs/en/framework/fundamentals/index.md
  37. 13
      docs/en/framework/infrastructure/artificial-intelligence/microsoft-extensions-ai.md
  38. 2
      docs/en/framework/infrastructure/background-jobs/hangfire.md
  39. 2
      docs/en/framework/infrastructure/interceptors.md
  40. 29
      docs/en/framework/infrastructure/object-to-object-mapping.md
  41. 25
      docs/en/framework/ui/angular/checkbox-component.md
  42. 22
      docs/en/framework/ui/angular/form-input-component.md
  43. 1
      docs/en/framework/ui/angular/form-validation.md
  44. 56
      docs/en/framework/ui/angular/how-replaceable-components-work-with-extensions.md
  45. 2
      docs/en/framework/ui/angular/permission-management.md
  46. 8
      docs/en/framework/ui/angular/quick-start.md
  47. 14
      docs/en/framework/ui/angular/ssr-configuration.md
  48. 5
      docs/en/framework/ui/angular/theming.md
  49. 4
      docs/en/framework/ui/blazor/authorization.md
  50. 2
      docs/en/framework/ui/blazor/page-toolbar-extensions.md
  51. 2
      docs/en/framework/ui/mvc-razor-pages/auto-complete-select.md
  52. 2
      docs/en/framework/ui/mvc-razor-pages/javascript-api/ajax.md
  53. 2
      docs/en/framework/ui/mvc-razor-pages/javascript-api/auth.md
  54. 8
      docs/en/framework/ui/mvc-razor-pages/modals.md
  55. 2
      docs/en/framework/ui/mvc-razor-pages/navigation-menu.md
  56. 2
      docs/en/framework/ui/mvc-razor-pages/page-toolbar-extensions.md
  57. 4
      docs/en/framework/ui/mvc-razor-pages/tag-helpers/index.md
  58. 2
      docs/en/framework/ui/mvc-razor-pages/theming.md
  59. 2
      docs/en/framework/ui/mvc-razor-pages/toolbars.md
  60. 6
      docs/en/framework/ui/mvc-razor-pages/widgets.md
  61. 226
      docs/en/framework/ui/react-native/index.md
  62. 316
      docs/en/framework/ui/react-native/setting-up-android-emulator.md
  63. BIN
      docs/en/get-started/images/abp-studio-background-tasks.png
  64. BIN
      docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png
  65. BIN
      docs/en/get-started/images/abp-studio-created-new-microservice-solution.png
  66. BIN
      docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png
  67. BIN
      docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png
  68. BIN
      docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png
  69. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png
  70. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png
  71. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png
  72. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png
  73. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png
  74. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png
  75. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png
  76. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner.png
  77. BIN
      docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png
  78. BIN
      docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png
  79. BIN
      docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png
  80. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png
  81. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png
  82. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-admin-password.png
  83. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png
  84. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png
  85. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png
  86. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png
  87. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png
  88. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png
  89. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png
  90. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png
  91. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png
  92. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png
  93. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png
  94. BIN
      docs/en/get-started/images/abp-studio-open-module-folder.png
  95. BIN
      docs/en/get-started/images/abp-studio-welcome-screen.png
  96. 4
      docs/en/get-started/layered-web-application.md
  97. 10
      docs/en/get-started/microservice.md
  98. 6
      docs/en/get-started/pre-requirements.md
  99. 4
      docs/en/get-started/single-layer-web-application.md
  100. BIN
      docs/en/images/resource-based-permission.gif

8
.claude/settings.local.json

@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(yarn nx g:*)",
"Bash(npx vitest:*)"
]
}
}

21
.github/workflows/auto-pr.yml

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

2
.gitignore

@ -328,4 +328,4 @@ deploy/_run_all_log.txt
# No commit yarn.lock files in the subfolders of templates directory
templates/**/yarn.lock
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Logs/logs.txt
templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/Properties/launchSettings.json
templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/Properties/launchSettings.json

124
Directory.Packages.props

@ -7,7 +7,6 @@
<PackageVersion Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="4.0.0" />
<PackageVersion Include="aliyun-net-sdk-sts" Version="3.1.3" />
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageVersion Include="AsyncKeyedLock" Version="7.1.8" />
<PackageVersion Include="Autofac" Version="8.4.0" />
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Autofac.Extras.DynamicProxy" Version="7.1.0" />
@ -31,8 +30,9 @@
<PackageVersion Include="Dapper" Version="2.1.66" />
<PackageVersion Include="Dapr.AspNetCore" Version="1.16.0" />
<PackageVersion Include="Dapr.Client" Version="1.16.0" />
<PackageVersion Include="ModelContextProtocol" Version="0.5.0-preview.1" />
<PackageVersion Include="MyCSharp.HttpUserAgentParser" Version="3.0.28" />
<PackageVersion Include="Devart.Data.Oracle.EFCore" Version="10.4.290.9" />
<PackageVersion Include="Devart.Data.Oracle.EFCore" Version="11.0.0.9" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.8" />
<PackageVersion Include="DistributedLock.Redis" Version="1.1.0" />
<PackageVersion Include="DeepL.net" Version="1.15.0" />
@ -57,63 +57,63 @@
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="14.9.1" />
<PackageVersion Include="MailKit" Version="4.13.0" />
<PackageVersion Include="Markdig.Signed" Version="0.42.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="10.0.0" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="10.0.20" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="10.0.20" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.0" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.2" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.67.1" />
<PackageVersion Include="Microsoft.SemanticKernel.Abstractions" Version="1.67.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.9.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Composite" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Composite" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
@ -141,7 +141,7 @@
<PackageVersion Include="Polly" Version="8.6.3" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
<PackageVersion Include="MySql.EntityFrameworkCore" Version="10.0.0-preview" />
<PackageVersion Include="MySql.EntityFrameworkCore" Version="10.0.1" />
<PackageVersion Include="Quartz" Version="3.15.0" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.15.0" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.15.0" />
@ -169,20 +169,20 @@
<PackageVersion Include="Spectre.Console" Version="0.51.1" />
<PackageVersion Include="StackExchange.Redis" Version="2.9.17" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.0.1" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.2" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.6.7" />
<PackageVersion Include="System.Linq.Queryable" Version="4.3.0" />
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.0" />
<PackageVersion Include="System.Security.Permissions" Version="10.0.0" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.2" />
<PackageVersion Include="System.Security.Permissions" Version="10.0.2" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.0" />
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.0" />
<PackageVersion Include="System.Text.Json" Version="10.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.2" />
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.2" />
<PackageVersion Include="System.Text.Json" Version="10.0.2" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="TencentCloudSDK.Sms" Version="3.0.1273" />
<PackageVersion Include="TimeZoneConverter" Version="7.0.0" />
<PackageVersion Include="TimeZoneConverter" Version="7.2.0" />
<PackageVersion Include="TickerQ" Version="2.5.3" />
<PackageVersion Include="TickerQ.Dashboard" Version="2.5.3" />
<PackageVersion Include="TickerQ.Utilities" Version="2.5.3" />
@ -194,6 +194,6 @@
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="Fody" Version="6.9.3" />
<PackageVersion Include="System.Management" Version="10.0.0" />
<PackageVersion Include="System.Management" Version="10.0.2"/>
</ItemGroup>
</Project>
</Project>

151
ai-rules/README.md

@ -0,0 +1,151 @@
# ABP AI Rules
This folder contains AI rules (Cursor `.mdc` format) for ABP based solutions. These rules help AI assistants understand ABP-specific patterns, conventions, and best practices when working with ABP-based applications.
## Purpose
This folder serves as a central repository for ABP-specific AI rules. The community can contribute, improve, and maintain these rules collaboratively.
When you create a new ABP solution, these rules are included in your project based on your configuration. This provides AI assistants with ABP-specific context, helping them generate code that follows ABP conventions.
> **Important**: These rules are ABP-specific. They don't cover general .NET or ASP.NET Core patterns—AI assistants already know those. Instead, they focus on ABP's unique architecture, module system, and conventions.
## How Rules Work
Large language models don't retain memory between completions. Rules provide persistent, reusable context at the prompt level.
When applied, rule contents are included at the start of the model context. This gives the AI consistent guidance for generating code, interpreting edits, or helping with workflows.
## Mini Glossary (ABP Terms)
- **Application service**: Use-case orchestration (ABP’s primary “business API” surface). Usually exposed remotely via Auto API Controllers or explicit controllers.
- **Auto API Controllers**: ABP can auto-generate HTTP endpoints from `IApplicationService` contracts.
- **Client proxy**: Generated client-side code (Angular/JS/C#) to call remote application services.
- **Integration service (microservices)**: Application-service-like contract intended for **service-to-service** communication; typically exposed separately and consumed via generated C# proxies.
- **Domain vs Application**: Domain holds business rules/invariants; Application coordinates domain + infrastructure and returns DTOs.
## File Structure
```
ai-rules/
├── README.md
├── common/ # Rules for all ABP projects
│ ├── abp-core.mdc # Core ABP conventions (alwaysApply: true)
│ ├── ddd-patterns.mdc # DDD patterns (Entity, AggregateRoot, Repository)
│ ├── application-layer.mdc # Application services, DTOs, validation
│ ├── authorization.mdc # Permissions and authorization
│ ├── multi-tenancy.mdc # Multi-tenant entities and data isolation
│ ├── infrastructure.mdc # Settings, Features, Caching, Events, Jobs
│ ├── dependency-rules.mdc # Layer dependencies and guardrails
│ ├── development-flow.mdc # Development workflow
│ └── cli-commands.mdc # ABP CLI commands reference
├── ui/ # UI-specific rules (applied by globs)
│ ├── blazor.mdc # Blazor UI patterns
│ ├── angular.mdc # Angular UI patterns
│ └── mvc.mdc # MVC/Razor Pages patterns
├── data/ # Data layer rules (applied by globs)
│ ├── ef-core.mdc # Entity Framework Core patterns
│ └── mongodb.mdc # MongoDB patterns
├── testing/ # Testing rules
│ └── patterns.mdc # Unit and integration test patterns
└── template-specific/ # Template-specific rules
├── app-nolayers.mdc # Single-layer app template
├── module.mdc # Module template
└── microservice.mdc # Microservice template
```
### Rule Format
Each rule is a markdown file with frontmatter metadata:
```markdown
---
description: "Describes when this rule should apply - used by AI to decide relevance"
globs: "src/**/*.cs"
alwaysApply: false
---
# Rule Title
Your rule content here...
```
### Frontmatter Properties
| Property | Description |
|----------|-------------|
| `description` | Brief description of what the rule covers. Used by AI to determine relevance. |
| `globs` | File patterns that trigger this rule (e.g., `**/*.cs`, `*.Domain/**`). |
| `alwaysApply` | If `true`, rule is always included. If `false`, AI decides based on context. |
### Rule Types
| Type | When Applied |
|------|--------------|
| **Always Apply** | Every chat session (`alwaysApply: true`) |
| **Apply Intelligently** | When AI decides it's relevant based on `description` |
| **Apply to Specific Files** | When file matches `globs` pattern |
| **Apply Manually** | When @-mentioned in chat (e.g., `@my-rule`) |
## Rule Categories
### Common Rules
Core ABP patterns that apply to all DDD-based templates (app, module, microservice):
- `abp-core.mdc` - Always applied, covers module system, DI conventions, base classes
- `ddd-patterns.mdc` - Entity, AggregateRoot, Repository, Domain Services
- `application-layer.mdc` - Application services, DTOs, validation, error handling
- `authorization.mdc` - Permission system and authorization
- `infrastructure.mdc` - Settings, Features, Caching, Events, Background Jobs
- `dependency-rules.mdc` - Layer dependencies and project structure
- `development-flow.mdc` - Development workflow for adding features
### UI Rules (Applied by Globs)
- `blazor.mdc` - Applied to `**/*.razor`, `**/Blazor/**/*.cs`
- `angular.mdc` - Applied to `**/angular/**/*.ts`
- `mvc.mdc` - Applied to `**/*.cshtml`, `**/Pages/**/*.cs`
### Data Rules (Applied by Globs)
- `ef-core.mdc` - Applied to `**/*.EntityFrameworkCore/**/*.cs`
- `mongodb.mdc` - Applied to `**/*.MongoDB/**/*.cs`
### Template-Specific Rules
- `app-nolayers.mdc` - For single-layer web application template
- `module.mdc` - For reusable module template
- `microservice.mdc` - For microservice template
## Best Practices
Good rules are focused, actionable, and scoped:
- **Keep rules under 500 lines** - Split large rules into multiple, composable rules
- **Provide concrete examples** - Reference actual files or include code snippets
- **Be specific, not vague** - Write rules like clear internal documentation
- **Reference files instead of copying** - This keeps rules short and prevents staleness
- **Start simple** - Add rules only when you notice AI making the same mistake repeatedly
## What to Avoid
- **Copying entire style guides**: Use a linter instead. AI already knows common style conventions.
- **Documenting every possible command**: AI knows common tools like `dotnet` and `npm`.
- **Adding instructions for edge cases that rarely apply**: Keep rules focused on patterns you use frequently.
- **Duplicating what's already in your codebase**: Point to canonical examples instead of copying code.
- **Including non-ABP patterns**: Don't add generic .NET/ASP.NET Core guidance—focus on ABP-specific conventions.
## Contributing
We welcome community contributions to improve these rules! You can open a PR to add new rules or improve existing ones.
Please review our [Contribution Guide](../CONTRIBUTING.md) and [Code of Conduct](../CODE_OF_CONDUCT.md) before contributing.
### Contribution Guidelines
- Each rule should focus on a single ABP concept or pattern
- Use clear, actionable language
- Include examples where helpful
- Test your rules by using them in a real ABP project
- Keep ABP-specific focus—don't add general .NET patterns
## Related Resources
- [Cursor Rules Documentation](https://cursor.com/docs/context/rules)
- [ABP Framework Documentation](https://abp.io/docs)

182
ai-rules/common/abp-core.mdc

@ -0,0 +1,182 @@
---
description: "Core ABP Framework conventions - module system, dependency injection, and base classes"
alwaysApply: true
---
# ABP Core Conventions
> **Documentation**: https://abp.io/docs/latest
> **API Reference**: https://abp.io/docs/api/
## Module System
Every ABP application/module has a module class that configures services:
```csharp
[DependsOn(
typeof(AbpDddDomainModule),
typeof(AbpEntityFrameworkCoreModule)
)]
public class MyAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Service registration and configuration
}
}
```
> **Note**: Middleware configuration (`OnApplicationInitialization`) should only be done in the final host application, not in reusable modules.
## Dependency Injection Conventions
### Automatic Registration
ABP automatically registers services implementing marker interfaces:
- `ITransientDependency` → Transient lifetime
- `ISingletonDependency` → Singleton lifetime
- `IScopedDependency` → Scoped lifetime
Classes inheriting from `ApplicationService`, `DomainService`, `AbpController` are also auto-registered.
### Repository Usage
You can use the generic `IRepository<TEntity, TKey>` for simple CRUD operations. Define custom repository interfaces only when you need custom query methods:
```csharp
// Simple CRUD - Generic repository is fine
public class BookAppService : ApplicationService
{
private readonly IRepository<Book, Guid> _bookRepository; // ✅ OK for simple operations
}
// Custom queries needed - Define custom interface
public interface IBookRepository : IRepository<Book, Guid>
{
Task<Book> FindByNameAsync(string name); // Custom query
}
public class BookAppService : ApplicationService
{
private readonly IBookRepository _bookRepository; // ✅ Use custom when needed
}
```
### Exposing Services
```csharp
[ExposeServices(typeof(IMyService))]
public class MyService : IMyService, ITransientDependency { }
```
## Important Base Classes
| Base Class | Purpose |
|------------|---------|
| `Entity<TKey>` | Basic entity with ID |
| `AggregateRoot<TKey>` | DDD aggregate root |
| `DomainService` | Domain business logic |
| `ApplicationService` | Use case orchestration |
| `AbpController` | REST API controller |
ABP base classes already inject commonly used services as properties. Before injecting a service, check if it's already available:
| Property | Available In | Description |
|----------|--------------|-------------|
| `GuidGenerator` | All base classes | Generate GUIDs |
| `Clock` | All base classes | Current time (use instead of `DateTime`) |
| `CurrentUser` | All base classes | Authenticated user info |
| `CurrentTenant` | All base classes | Multi-tenancy context |
| `L` (StringLocalizer) | `ApplicationService`, `AbpController` | Localization |
| `AuthorizationService` | `ApplicationService`, `AbpController` | Permission checks |
| `FeatureChecker` | `ApplicationService`, `AbpController` | Feature availability |
| `DataFilter` | All base classes | Data filtering (soft-delete, tenant) |
| `UnitOfWorkManager` | `ApplicationService`, `DomainService` | Unit of work management |
| `LoggerFactory` | All base classes | Create loggers |
| `Logger` | All base classes | Logging (auto-created) |
| `LazyServiceProvider` | All base classes | Lazy service resolution |
**Useful methods from base classes:**
- `CheckPolicyAsync()` - Check permission and throw if not granted
- `IsGrantedAsync()` - Check permission without throwing
## Async Best Practices
- Use async all the way - never use `.Result` or `.Wait()`
- All async methods should end with `Async` suffix
- ABP automatically handles `CancellationToken` in most cases (e.g., from `HttpContext.RequestAborted`)
- Only pass `CancellationToken` explicitly when implementing custom cancellation logic
## Time Handling
Never use `DateTime.Now` or `DateTime.UtcNow` directly. Use ABP's `IClock` service:
```csharp
// In classes inheriting from base classes (ApplicationService, DomainService, etc.)
public class BookAppService : ApplicationService
{
public void DoSomething()
{
var now = Clock.Now; // ✅ Already available as property
}
}
// In other services - inject IClock
public class MyService : ITransientDependency
{
private readonly IClock _clock;
public MyService(IClock clock) => _clock = clock;
public void DoSomething()
{
var now = _clock.Now; // ✅ Correct
// var now = DateTime.Now; // ❌ Wrong - not testable, ignores timezone settings
}
}
```
> **Tip**: Before injecting a service, check if it's already available as a property in your base classes.
## Business Exceptions
Use `BusinessException` for domain rule violations with namespaced error codes:
```csharp
throw new BusinessException("MyModule:BookNameAlreadyExists")
.WithData("Name", bookName);
```
Configure localization mapping:
```csharp
Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("MyModule", typeof(MyModuleResource));
});
```
## Localization
- In base classes (`ApplicationService`, `AbpController`, etc.): Use `L["Key"]` - this is the `IStringLocalizer` property
- In other services: Inject `IStringLocalizer<TResource>`
- Always localize user-facing messages and exceptions
**Localization file location**: `*.Domain.Shared/Localization/{ResourceName}/{lang}.json`
```json
// Example: MyProject.Domain.Shared/Localization/MyProject/en.json
{
"culture": "en",
"texts": {
"Menu:Home": "Home",
"Welcome": "Welcome",
"BookName": "Book Name"
}
}
```
## ❌ Never Use (ABP Anti-Patterns)
| Don't Use | Use Instead |
|-----------|-------------|
| Minimal APIs | ABP Controllers or Auto API Controllers |
| MediatR | Application Services |
| `DbContext` directly in App Services | `IRepository<T>` |
| `AddScoped/AddTransient/AddSingleton` | `ITransientDependency`, `ISingletonDependency` |
| `DateTime.Now` | `IClock` / `Clock.Now` |
| Custom UnitOfWork | ABP's `IUnitOfWorkManager` |
| Manual HTTP calls from UI | ABP client proxies (`generate-proxy`) |
| Hardcoded role checks | Permission-based authorization |
| Business logic in Controllers | Application Services |

236
ai-rules/common/application-layer.mdc

@ -0,0 +1,236 @@
---
description: "ABP Application Services, DTOs, validation, and error handling patterns"
globs:
- "**/*.Application/**/*.cs"
- "**/Application/**/*.cs"
- "**/*AppService*.cs"
- "**/*Dto*.cs"
alwaysApply: false
---
# ABP Application Layer Patterns
> **Docs**: https://abp.io/docs/latest/framework/architecture/domain-driven-design/application-services
## Application Service Structure
### Interface (Application.Contracts)
```csharp
public interface IBookAppService : IApplicationService
{
Task<BookDto> GetAsync(Guid id);
Task<PagedResultDto<BookListItemDto>> GetListAsync(GetBookListInput input);
Task<BookDto> CreateAsync(CreateBookDto input);
Task<BookDto> UpdateAsync(Guid id, UpdateBookDto input);
Task DeleteAsync(Guid id);
}
```
### Implementation (Application)
```csharp
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IBookRepository _bookRepository;
private readonly BookManager _bookManager;
private readonly BookMapper _bookMapper;
public BookAppService(
IBookRepository bookRepository,
BookManager bookManager,
BookMapper bookMapper)
{
_bookRepository = bookRepository;
_bookManager = bookManager;
_bookMapper = bookMapper;
}
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return _bookMapper.MapToDto(book);
}
[Authorize(BookStorePermissions.Books.Create)]
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
var book = await _bookManager.CreateAsync(input.Name, input.Price);
await _bookRepository.InsertAsync(book);
return _bookMapper.MapToDto(book);
}
[Authorize(BookStorePermissions.Books.Edit)]
public async Task<BookDto> UpdateAsync(Guid id, UpdateBookDto input)
{
var book = await _bookRepository.GetAsync(id);
await _bookManager.ChangeNameAsync(book, input.Name);
book.SetPrice(input.Price);
await _bookRepository.UpdateAsync(book);
return _bookMapper.MapToDto(book);
}
}
```
## Application Service Best Practices
- Don't repeat entity name in method names (`GetAsync` not `GetBookAsync`)
- Accept/return DTOs only, never entities
- ID not inside UpdateDto - pass separately
- Use custom repositories when you need custom queries, generic repository is fine for simple CRUD
- Call `UpdateAsync` explicitly (don't assume change tracking)
- Don't call other app services in same module
- Don't use `IFormFile`/`Stream` - pass `byte[]` from controllers
- Use base class properties (`Clock`, `CurrentUser`, `GuidGenerator`, `L`) instead of injecting these services
## DTO Naming Conventions
| Purpose | Convention | Example |
|---------|------------|---------|
| Query input | `Get{Entity}Input` | `GetBookInput` |
| List query input | `Get{Entity}ListInput` | `GetBookListInput` |
| Create input | `Create{Entity}Dto` | `CreateBookDto` |
| Update input | `Update{Entity}Dto` | `UpdateBookDto` |
| Single entity output | `{Entity}Dto` | `BookDto` |
| List item output | `{Entity}ListItemDto` | `BookListItemDto` |
## DTO Location
- Define DTOs in `*.Application.Contracts` project
- This allows sharing with clients (Blazor, HttpApi.Client)
## Validation
### Data Annotations
```csharp
public class CreateBookDto
{
[Required]
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
[Range(0, 999.99)]
public decimal Price { get; set; }
}
```
### Custom Validation with IValidatableObject
Before adding custom validation, decide if it's a **domain rule** or **application rule**:
- **Domain rule**: Put validation in entity constructor or domain service (enforces business invariants)
- **Application rule**: Use DTO validation (input format, required fields)
Only use `IValidatableObject` for application-level validation that can't be expressed with data annotations:
```csharp
public class CreateBookDto : IValidatableObject
{
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Name == Description)
{
yield return new ValidationResult(
"Name and Description cannot be the same!",
new[] { nameof(Name), nameof(Description) }
);
}
}
}
```
### FluentValidation
```csharp
public class CreateBookDtoValidator : AbstractValidator<CreateBookDto>
{
public CreateBookDtoValidator()
{
RuleFor(x => x.Name).NotEmpty().Length(3, 100);
RuleFor(x => x.Price).GreaterThan(0);
}
}
```
## Error Handling
### Business Exceptions
```csharp
throw new BusinessException("BookStore:010001")
.WithData("BookName", name);
```
### Entity Not Found
```csharp
var book = await _bookRepository.FindAsync(id);
if (book == null)
{
throw new EntityNotFoundException(typeof(Book), id);
}
```
### User-Friendly Exceptions
```csharp
throw new UserFriendlyException(L["BookNotAvailable"]);
```
### HTTP Status Code Mapping
Status code mapping is **configurable** in ABP (do not rely on a fixed mapping in business logic).
| Exception | Typical HTTP Status |
|-----------|-------------|
| `AbpValidationException` | 400 |
| `AbpAuthorizationException` | 401/403 |
| `EntityNotFoundException` | 404 |
| `BusinessException` | 403 (but configurable) |
| Other exceptions | 500 |
## Auto API Controllers
ABP automatically generates API controllers for application services:
- Interface must inherit `IApplicationService` (which already has `[RemoteService]` attribute)
- HTTP methods determined by method name prefix (Get, Create, Update, Delete)
- Use `[RemoteService(false)]` to disable auto API generation for specific methods
## Object Mapping (Mapperly / AutoMapper)
ABP supports **both Mapperly and AutoMapper** integrations. But the default mapping library is Mapperly. You need to first check the project's active mapping library.
- Prefer the mapping provider already used in the solution (check existing mapping files / loaded modules).
- In mixed solutions, explicitly setting the default provider may be required (see `docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md`).
### Mapperly (compile-time)
Define mappers as partial classes:
```csharp
[Mapper]
public partial class BookMapper
{
public partial BookDto MapToDto(Book book);
public partial List<BookDto> MapToDtoList(List<Book> books);
}
```
Register in module:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddSingleton<BookMapper>();
}
```
Usage in application service:
```csharp
public class BookAppService : ApplicationService
{
private readonly BookMapper _bookMapper;
public BookAppService(BookMapper bookMapper)
{
_bookMapper = bookMapper;
}
public BookDto GetBook(Book book)
{
return _bookMapper.MapToDto(book);
}
}
```
> **Note**: Mapperly generates mapping code at compile-time, providing better performance than runtime mappers.
### AutoMapper (runtime)
If the solution uses AutoMapper, mappings are typically defined in `Profile` classes and registered via ABP's AutoMapper integration.

186
ai-rules/common/authorization.mdc

@ -0,0 +1,186 @@
---
description: "ABP permission system and authorization patterns"
globs:
- "**/*Permission*.cs"
- "**/*AppService*.cs"
- "**/*Controller*.cs"
alwaysApply: false
---
# ABP Authorization
> **Docs**: https://abp.io/docs/latest/framework/fundamentals/authorization
## Permission Definition
Define permissions in `*.Application.Contracts` project:
```csharp
public static class BookStorePermissions
{
public const string GroupName = "BookStore";
public static class Books
{
public const string Default = GroupName + ".Books";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
}
```
Register in provider:
```csharp
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore"));
var booksPermission = bookStoreGroup.AddPermission(
BookStorePermissions.Books.Default,
L("Permission:Books"));
booksPermission.AddChild(
BookStorePermissions.Books.Create,
L("Permission:Books.Create"));
booksPermission.AddChild(
BookStorePermissions.Books.Edit,
L("Permission:Books.Edit"));
booksPermission.AddChild(
BookStorePermissions.Books.Delete,
L("Permission:Books.Delete"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<BookStoreResource>(name);
}
}
```
## Using Permissions
### Declarative (Attribute)
```csharp
[Authorize(BookStorePermissions.Books.Create)]
public virtual async Task<BookDto> CreateAsync(CreateBookDto input)
{
// Only users with Books.Create permission can execute
}
```
### Programmatic Check
```csharp
public class BookAppService : ApplicationService
{
public async Task DoSomethingAsync()
{
// Check and throw if not granted
await CheckPolicyAsync(BookStorePermissions.Books.Edit);
// Or check without throwing
if (await IsGrantedAsync(BookStorePermissions.Books.Delete))
{
// Has permission
}
}
}
```
### Allow Anonymous Access
```csharp
[AllowAnonymous]
public virtual async Task<BookDto> GetPublicBookAsync(Guid id)
{
// No authentication required
}
```
## Current User
Access authenticated user info via `CurrentUser` property (available in base classes like `ApplicationService`, `DomainService`, `AbpController`):
```csharp
public class BookAppService : ApplicationService
{
public async Task DoSomethingAsync()
{
// CurrentUser is available from base class - no injection needed
var userId = CurrentUser.Id;
var userName = CurrentUser.UserName;
var email = CurrentUser.Email;
var isAuthenticated = CurrentUser.IsAuthenticated;
var roles = CurrentUser.Roles;
var tenantId = CurrentUser.TenantId;
}
}
// In other services, inject ICurrentUser
public class MyService : ITransientDependency
{
private readonly ICurrentUser _currentUser;
public MyService(ICurrentUser currentUser) => _currentUser = currentUser;
}
```
### Ownership Validation
```csharp
public async Task UpdateMyBookAsync(Guid bookId, UpdateBookDto input)
{
var book = await _bookRepository.GetAsync(bookId);
if (book.CreatorId != CurrentUser.Id)
{
throw new AbpAuthorizationException();
}
// Update book...
}
```
## Multi-Tenancy Permissions
Control permission availability per tenant side:
```csharp
bookStoreGroup.AddPermission(
BookStorePermissions.Books.Default,
L("Permission:Books"),
multiTenancySide: MultiTenancySides.Tenant // Only for tenants
);
```
Options: `MultiTenancySides.Host`, `Tenant`, or `Both`
## Feature-Dependent Permissions
```csharp
booksPermission.RequireFeatures("BookStore.PremiumFeature");
```
## Permission Management
Grant/revoke permissions programmatically:
```csharp
public class MyService : ITransientDependency
{
private readonly IPermissionManager _permissionManager;
public async Task GrantPermissionToUserAsync(Guid userId, string permissionName)
{
await _permissionManager.SetForUserAsync(userId, permissionName, true);
}
public async Task GrantPermissionToRoleAsync(string roleName, string permissionName)
{
await _permissionManager.SetForRoleAsync(roleName, permissionName, true);
}
}
```
## Security Best Practices
- Never trust client input for user identity
- Use `CurrentUser` property (from base class) or inject `ICurrentUser`
- Validate ownership in application service methods
- Filter queries by current user when appropriate
- Don't expose sensitive fields in DTOs

92
ai-rules/common/cli-commands.mdc

@ -0,0 +1,92 @@
---
description: "ABP CLI commands: generate-proxy, install-libs, add-package-ref, new-module, install-module, update, clean, suite generate (CRUD pages)"
globs:
- "**/*.csproj"
- "**/appsettings*.json"
alwaysApply: false
---
# ABP CLI Commands
> **Full documentation**: https://abp.io/docs/latest/cli
> Use `abp help [command]` for detailed options.
## Generate Client Proxies
```bash
# URL flag: `-u` (short) or `--url` (long). Use whichever your team prefers, but keep it consistent.
#
# Angular (host must be running)
abp generate-proxy -t ng
# C# client proxies
abp generate-proxy -t csharp -u https://localhost:44300
# Integration services only (microservices)
abp generate-proxy -t csharp -u https://localhost:44300 -st integration
# JavaScript
abp generate-proxy -t js -u https://localhost:44300
```
## Install Client-Side Libraries
```bash
# Install NPM packages for MVC/Blazor Server
abp install-libs
```
## Add Package Reference
```bash
# Add project reference with module dependency
abp add-package-ref Acme.BookStore.Domain
abp add-package-ref Acme.BookStore.Domain -t Acme.BookStore.Application
```
## Module Operations
```bash
# Create new module in solution
abp new-module Acme.OrderManagement -t module:ddd
# Install published module
abp install-module Volo.Blogging
# Add ABP NuGet package
abp add-package Volo.Abp.Caching.StackExchangeRedis
```
## Update & Clean
```bash
abp update # Update all ABP packages
abp update --version 8.0.0 # Specific version
abp clean # Delete bin/obj folders
```
## ABP Suite (CRUD Generation)
Generate CRUD pages from entity JSON (created via Suite UI):
```bash
abp suite generate --entity .suite/entities/Book.json --solution ./Acme.BookStore.sln
```
> **Note**: Entity JSON files are created when you generate an entity via ABP Suite UI. They are stored in `.suite/entities/` folder.
> **Suite docs**: https://abp.io/docs/latest/suite
## Quick Reference
| Task | Command |
|------|---------|
| Angular proxies | `abp generate-proxy -t ng` |
| C# proxies | `abp generate-proxy -t csharp -u URL` |
| Install JS libs | `abp install-libs` |
| Add reference | `abp add-package-ref PackageName` |
| Create module | `abp new-module ModuleName` |
| Install module | `abp install-module ModuleName` |
| Update packages | `abp update` |
| Clean solution | `abp clean` |
| Suite CRUD | `abp suite generate -e entity.json -s solution.sln` |
| Get help | `abp help [command]` |

244
ai-rules/common/ddd-patterns.mdc

@ -0,0 +1,244 @@
---
description: "ABP DDD patterns - Entities, Aggregate Roots, Repositories, Domain Services"
globs:
- "**/*.Domain/**/*.cs"
- "**/Domain/**/*.cs"
- "**/Entities/**/*.cs"
alwaysApply: false
---
# ABP DDD Patterns
> **Docs**: https://abp.io/docs/latest/framework/architecture/domain-driven-design
## Rich Domain Model vs Anemic Domain Model
ABP promotes **Rich Domain Model** pattern where entities contain both data AND behavior:
| Anemic (Anti-pattern) | Rich (Recommended) |
|----------------------|-------------------|
| Entity = data only | Entity = data + behavior |
| Logic in services | Logic in entity methods |
| Public setters | Private setters with methods |
| No validation in entity | Entity enforces invariants |
**Encapsulation is key**: Protect entity state by using private setters and exposing behavior through methods.
## Entities
### Entity Example (Rich Model)
```csharp
public class OrderLine : Entity<Guid>
{
public Guid ProductId { get; private set; }
public int Count { get; private set; }
public decimal Price { get; private set; }
protected OrderLine() { } // For ORM
internal OrderLine(Guid id, Guid productId, int count, decimal price) : base(id)
{
ProductId = productId;
SetCount(count); // Validates through method
Price = price;
}
public void SetCount(int count)
{
if (count <= 0)
throw new BusinessException("Orders:InvalidCount");
Count = count;
}
}
```
## Aggregate Roots
Aggregate roots are consistency boundaries that:
- Own their child entities
- Enforce business rules
- Publish domain events
```csharp
public class Order : AggregateRoot<Guid>
{
public string OrderNumber { get; private set; }
public Guid CustomerId { get; private set; }
public OrderStatus Status { get; private set; }
public ICollection<OrderLine> Lines { get; private set; }
protected Order() { } // For ORM
public Order(Guid id, string orderNumber, Guid customerId) : base(id)
{
OrderNumber = Check.NotNullOrWhiteSpace(orderNumber, nameof(orderNumber));
CustomerId = customerId;
Status = OrderStatus.Created;
Lines = new List<OrderLine>();
}
public void AddLine(Guid lineId, Guid productId, int count, decimal price)
{
// Business rule: Can only add lines to created orders
if (Status != OrderStatus.Created)
throw new BusinessException("Orders:CannotModifyOrder");
Lines.Add(new OrderLine(lineId, productId, count, price));
}
public void Complete()
{
if (Status != OrderStatus.Created)
throw new BusinessException("Orders:CannotCompleteOrder");
Status = OrderStatus.Completed;
// Publish events for side effects
AddLocalEvent(new OrderCompletedEvent(Id)); // Same transaction
AddDistributedEvent(new OrderCompletedEto { OrderId = Id }); // Cross-service
}
}
```
### Domain Events
- `AddLocalEvent()` - Handled within same transaction, can access full entity
- `AddDistributedEvent()` - Handled asynchronously, use ETOs (Event Transfer Objects)
### Entity Best Practices
- **Encapsulation**: Private setters, public methods that enforce rules
- **Primary constructor**: Enforce invariants, accept `id` parameter
- **Protected parameterless constructor**: Required for ORM
- **Initialize collections**: In primary constructor
- **Virtual members**: For ORM proxy compatibility
- **Reference by Id**: Don't add navigation properties to other aggregates
- **Don't generate GUID in constructor**: Use `IGuidGenerator` externally
## Repository Pattern
### When to Use Custom Repository
- **Generic repository** (`IRepository<T, TKey>`): Sufficient for simple CRUD operations
- **Custom repository**: Only when you need custom query methods
### Interface (Domain Layer)
```csharp
// Define custom interface only when custom queries are needed
public interface IOrderRepository : IRepository<Order, Guid>
{
Task<Order> FindByOrderNumberAsync(string orderNumber, bool includeDetails = false);
Task<List<Order>> GetListByCustomerAsync(Guid customerId, bool includeDetails = false);
}
```
### Repository Best Practices
- **One repository per aggregate root only** - Never create repositories for child entities
- Child entities must be accessed/modified only through their aggregate root
- Creating repositories for child entities breaks data consistency (bypasses aggregate root's business rules)
- In ABP, use `AddDefaultRepositories()` without `includeAllEntities: true` to enforce this
- Define custom repository only when custom queries are needed
- ABP handles `CancellationToken` automatically; add parameter only for explicit cancellation control
- Single entity methods: `includeDetails = true` by default
- List methods: `includeDetails = false` by default
- Don't return projection classes
- Interface in Domain, implementation in data layer
```csharp
// ✅ Correct: Repository for aggregate root (Order)
public interface IOrderRepository : IRepository<Order, Guid> { }
// ❌ Wrong: Repository for child entity (OrderLine)
// OrderLine should only be accessed through Order aggregate
public interface IOrderLineRepository : IRepository<OrderLine, Guid> { } // Don't do this!
```
## Domain Services
Use domain services for business logic that:
- Spans multiple aggregates
- Requires repository queries to enforce rules
```csharp
public class OrderManager : DomainService
{
private readonly IOrderRepository _orderRepository;
private readonly IProductRepository _productRepository;
public OrderManager(
IOrderRepository orderRepository,
IProductRepository productRepository)
{
_orderRepository = orderRepository;
_productRepository = productRepository;
}
public async Task<Order> CreateAsync(string orderNumber, Guid customerId)
{
// Business rule: Order number must be unique
var existing = await _orderRepository.FindByOrderNumberAsync(orderNumber);
if (existing != null)
{
throw new BusinessException("Orders:OrderNumberAlreadyExists")
.WithData("OrderNumber", orderNumber);
}
return new Order(GuidGenerator.Create(), orderNumber, customerId);
}
public async Task AddProductAsync(Order order, Guid productId, int count)
{
var product = await _productRepository.GetAsync(productId);
order.AddLine(productId, count, product.Price);
}
}
```
### Domain Service Best Practices
- Use `*Manager` suffix naming
- No interface by default (create only if needed)
- Accept/return domain objects, not DTOs
- Don't depend on authenticated user - pass values from application layer
- Use base class properties (`GuidGenerator`, `Clock`) instead of injecting these services
## Domain Events
### Local Events
```csharp
// In aggregate
AddLocalEvent(new OrderCompletedEvent(Id));
// Handler
public class OrderCompletedEventHandler : ILocalEventHandler<OrderCompletedEvent>, ITransientDependency
{
public async Task HandleEventAsync(OrderCompletedEvent eventData)
{
// Handle within same transaction
}
}
```
### Distributed Events (ETO)
For inter-module/microservice communication:
```csharp
// In Domain.Shared
[EventName("Orders.OrderCompleted")]
public class OrderCompletedEto
{
public Guid OrderId { get; set; }
public string OrderNumber { get; set; }
}
```
## Specifications
Reusable query conditions:
```csharp
public class CompletedOrdersSpec : Specification<Order>
{
public override Expression<Func<Order, bool>> ToExpression()
{
return o => o.Status == OrderStatus.Completed;
}
}
// Usage
var orders = await _orderRepository.GetListAsync(new CompletedOrdersSpec());
```

153
ai-rules/common/dependency-rules.mdc

@ -0,0 +1,153 @@
---
description: "ABP layer dependency rules and project structure guardrails"
globs:
- "**/*.csproj"
- "**/*Module*.cs"
alwaysApply: false
---
# ABP Dependency Rules
## Core Principles (All Templates)
These principles apply regardless of solution structure:
1. **Domain logic never depends on infrastructure** (no DbContext in domain/application)
2. **Use abstractions** (interfaces) for dependencies
3. **Higher layers depend on lower layers**, never the reverse
4. **Data access through repositories**, not direct DbContext
## Layered Template Structure
> **Note**: This section applies to layered templates (app, module). Single-layer and microservice templates have different structures.
```
Domain.Shared → Constants, enums, localization keys
Domain → Entities, repository interfaces, domain services
Application.Contracts → App service interfaces, DTOs
Application → App service implementations
HttpApi → REST controllers (optional)
Host → Final application with DI and middleware
```
### Layered Dependency Direction
| Project | Can Reference | Referenced By |
|---------|---------------|---------------|
| Domain.Shared | Nothing | All |
| Domain | Domain.Shared | Application, Data layer |
| Application.Contracts | Domain.Shared | Application, HttpApi, Clients |
| Application | Domain, Contracts | Host |
| EntityFrameworkCore/MongoDB | Domain | Host only |
| HttpApi | Contracts only | Host |
## Critical Rules
### ❌ Never Do
```csharp
// Application layer accessing DbContext directly
public class BookAppService : ApplicationService
{
private readonly MyDbContext _dbContext; // ❌ WRONG
}
// Domain depending on application layer
public class BookManager : DomainService
{
private readonly IBookAppService _appService; // ❌ WRONG
}
// HttpApi depending on Application implementation
public class BookController : AbpController
{
private readonly BookAppService _bookAppService; // ❌ WRONG - Use interface
}
```
### ✅ Always Do
```csharp
// Application layer using repository abstraction
public class BookAppService : ApplicationService
{
private readonly IBookRepository _bookRepository; // ✅ CORRECT
}
// Domain service using domain abstractions
public class BookManager : DomainService
{
private readonly IBookRepository _bookRepository; // ✅ CORRECT
}
// HttpApi depending on contracts only
public class BookController : AbpController
{
private readonly IBookAppService _bookAppService; // ✅ CORRECT
}
```
## Repository Pattern Enforcement
### Interface Location
```csharp
// In Domain project
public interface IBookRepository : IRepository<Book, Guid>
{
Task<Book> FindByNameAsync(string name);
}
```
### Implementation Location
```csharp
// In EntityFrameworkCore project
public class BookRepository : EfCoreRepository<MyDbContext, Book, Guid>, IBookRepository
{
// Implementation
}
// In MongoDB project
public class BookRepository : MongoDbRepository<MyDbContext, Book, Guid>, IBookRepository
{
// Implementation
}
```
## Multi-Application Scenarios
When you have multiple applications (e.g., Admin + Public API):
### Vertical Separation
```
MyProject.Admin.Application - Admin-specific services
MyProject.Public.Application - Public-specific services
MyProject.Domain - Shared domain (both reference this)
```
### Rules
- Admin and Public application layers **MUST NOT** reference each other
- Share domain logic, not application logic
- Each vertical can have its own DTOs even if similar
## Enforcement Checklist (Layered Templates)
When adding a new feature:
1. **Entity changes?** → Domain project
2. **Constants/enums?** → Domain.Shared project
3. **Repository interface?** → Domain project (only if custom queries needed)
4. **Repository implementation?** → EntityFrameworkCore/MongoDB project
5. **DTOs and service interface?** → Application.Contracts project
6. **Service implementation?** → Application project
7. **API endpoint?** → HttpApi project (if not using auto API controllers)
## Common Violations to Watch
| Violation | Impact | Fix |
|-----------|--------|-----|
| DbContext in Application | Breaks DB independence | Use repository |
| Entity in DTO | Exposes internals | Map to DTO |
| IQueryable in interface | Breaks abstraction | Return concrete types |
| Cross-module app service call | Tight coupling | Use events or domain |

299
ai-rules/common/development-flow.mdc

@ -0,0 +1,299 @@
---
description: "ABP development workflow - adding features, entities, and migrations"
globs:
- "**/*AppService*.cs"
- "**/*Application*/**/*.cs"
- "**/*Application.Contracts*/**/*.cs"
- "**/*Dto*.cs"
- "**/*DbContext*.cs"
- "**/*.EntityFrameworkCore/**/*.cs"
- "**/*.MongoDB/**/*.cs"
- "**/*Permission*.cs"
alwaysApply: false
---
# ABP Development Workflow
> **Tutorials**: https://abp.io/docs/latest/tutorials
## Adding a New Entity (Full Flow)
### 1. Domain Layer
Create entity (location varies by template: `*.Domain/Entities/` for layered, `Entities/` for single-layer/microservice):
```csharp
public class Book : AggregateRoot<Guid>
{
public string Name { get; private set; }
public decimal Price { get; private set; }
public Guid AuthorId { get; private set; }
protected Book() { }
public Book(Guid id, string name, decimal price, Guid authorId) : base(id)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
SetPrice(price);
AuthorId = authorId;
}
public void SetPrice(decimal price)
{
Price = Check.Range(price, nameof(price), 0, 9999);
}
}
```
### 2. Domain.Shared
Add constants and enums in `*.Domain.Shared/`:
```csharp
public static class BookConsts
{
public const int MaxNameLength = 128;
}
public enum BookType
{
Novel,
Science,
Biography
}
```
### 3. Repository Interface (Optional)
Define custom repository in `*.Domain/` only if you need custom query methods. For simple CRUD, use generic `IRepository<Book, Guid>` directly:
```csharp
// Only if custom queries are needed
public interface IBookRepository : IRepository<Book, Guid>
{
Task<Book> FindByNameAsync(string name);
}
```
### 4. EF Core Configuration
In `*.EntityFrameworkCore/`:
**DbContext:**
```csharp
public DbSet<Book> Books { get; set; }
```
**OnModelCreating:**
```csharp
builder.Entity<Book>(b =>
{
b.ToTable(MyProjectConsts.DbTablePrefix + "Books", MyProjectConsts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name).IsRequired().HasMaxLength(BookConsts.MaxNameLength);
b.HasIndex(x => x.Name);
});
```
**Repository Implementation (only if custom interface defined):**
```csharp
public class BookRepository : EfCoreRepository<MyDbContext, Book, Guid>, IBookRepository
{
public BookRepository(IDbContextProvider<MyDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Book> FindByNameAsync(string name)
{
return await (await GetDbSetAsync())
.FirstOrDefaultAsync(b => b.Name == name);
}
}
```
### 5. Run Migration
```bash
cd src/MyProject.EntityFrameworkCore
# Add migration
dotnet ef migrations add Added_Book
# Apply migration (choose one):
dotnet run --project ../MyProject.DbMigrator # Recommended - also seeds data
# OR
dotnet ef database update # EF Core command only
```
### 6. Application.Contracts
Create DTOs and service interface:
```csharp
// DTOs
public class BookDto : EntityDto<Guid>
{
public string Name { get; set; }
public decimal Price { get; set; }
public Guid AuthorId { get; set; }
}
public class CreateBookDto
{
[Required]
[StringLength(BookConsts.MaxNameLength)]
public string Name { get; set; }
[Range(0, 9999)]
public decimal Price { get; set; }
[Required]
public Guid AuthorId { get; set; }
}
// Service Interface
public interface IBookAppService : IApplicationService
{
Task<BookDto> GetAsync(Guid id);
Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input);
Task<BookDto> CreateAsync(CreateBookDto input);
}
```
### 7. Object Mapping (Mapperly / AutoMapper)
ABP supports both Mapperly and AutoMapper. Prefer the provider already used in the solution.
If the solution uses **Mapperly**, create a mapper in the Application project:
```csharp
[Mapper]
public partial class BookMapper
{
public partial BookDto MapToDto(Book book);
public partial List<BookDto> MapToDtoList(List<Book> books);
}
```
Register in module:
```csharp
context.Services.AddSingleton<BookMapper>();
```
### 8. Application Service
Implement service (using generic repository - use `IBookRepository` if you defined custom interface in step 3):
```csharp
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IRepository<Book, Guid> _bookRepository; // Or IBookRepository
private readonly BookMapper _bookMapper;
public BookAppService(
IRepository<Book, Guid> bookRepository,
BookMapper bookMapper)
{
_bookRepository = bookRepository;
_bookMapper = bookMapper;
}
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return _bookMapper.MapToDto(book);
}
[Authorize(MyProjectPermissions.Books.Create)]
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
var book = new Book(
GuidGenerator.Create(),
input.Name,
input.Price,
input.AuthorId
);
await _bookRepository.InsertAsync(book);
return _bookMapper.MapToDto(book);
}
}
```
### 9. Add Localization
In `*.Domain.Shared/Localization/*/en.json`:
```json
{
"Book": "Book",
"Books": "Books",
"BookName": "Name",
"BookPrice": "Price"
}
```
### 10. Add Permissions (if needed)
```csharp
public static class MyProjectPermissions
{
public static class Books
{
public const string Default = "MyProject.Books";
public const string Create = Default + ".Create";
}
}
```
### 11. Add Tests
```csharp
public class BookAppService_Tests : MyProjectApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Create_Book()
{
var result = await _bookAppService.CreateAsync(new CreateBookDto
{
Name = "Test Book",
Price = 19.99m
});
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("Test Book");
}
}
```
## Quick Reference Commands
### Build Solution
```bash
dotnet build
```
### Run Migrations
```bash
cd src/MyProject.EntityFrameworkCore
dotnet ef migrations add MigrationName
dotnet run --project ../MyProject.DbMigrator # Apply migration + seed data
```
### Generate Angular Proxies
```bash
abp generate-proxy -t ng
```
## Checklist for New Features
- [ ] Entity created with proper constructors
- [ ] Constants in Domain.Shared
- [ ] Custom repository interface in Domain (only if custom queries needed)
- [ ] EF Core configuration added
- [ ] Custom repository implementation (only if interface defined)
- [ ] Migration generated and applied (use DbMigrator)
- [ ] Mapperly mapper created and registered
- [ ] DTOs created in Application.Contracts
- [ ] Service interface defined
- [ ] Service implementation with authorization
- [ ] Localization keys added
- [ ] Permissions defined (if applicable)
- [ ] Tests written

249
ai-rules/common/infrastructure.mdc

@ -0,0 +1,249 @@
---
description: "ABP infrastructure services - Settings, Features, Caching, Events, Background Jobs"
globs:
- "**/*Setting*.cs"
- "**/*Feature*.cs"
- "**/*Cache*.cs"
- "**/*Event*.cs"
- "**/*Job*.cs"
alwaysApply: false
---
# ABP Infrastructure Services
> **Docs**: https://abp.io/docs/latest/framework/infrastructure
## Settings
### Define Settings
```csharp
public class MySettingDefinitionProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(
new SettingDefinition("MyApp.MaxItemCount", "10"),
new SettingDefinition("MyApp.EnableFeature", "false"),
new SettingDefinition("MyApp.SecretKey", isEncrypted: true)
);
}
}
```
### Read Settings
```csharp
public class MyService : ITransientDependency
{
private readonly ISettingProvider _settingProvider;
public async Task DoSomethingAsync()
{
var maxCount = await _settingProvider.GetAsync<int>("MyApp.MaxItemCount");
var isEnabled = await _settingProvider.IsTrueAsync("MyApp.EnableFeature");
}
}
```
### Setting Value Providers (Priority Order)
1. User settings (highest)
2. Tenant settings
3. Global settings
4. Configuration (appsettings.json)
5. Default value (lowest)
## Features
### Define Features
```csharp
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var myGroup = context.AddGroup("MyApp");
myGroup.AddFeature(
"MyApp.PdfReporting",
defaultValue: "false",
valueType: new ToggleStringValueType()
);
myGroup.AddFeature(
"MyApp.MaxProductCount",
defaultValue: "10",
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000))
);
}
}
```
### Check Features
```csharp
[RequiresFeature("MyApp.PdfReporting")]
public async Task<PdfReportDto> GetPdfReportAsync()
{
// Only executes if feature is enabled
}
// Or programmatically
if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting"))
{
// Feature is enabled for current tenant
}
var maxCount = await _featureChecker.GetAsync<int>("MyApp.MaxProductCount");
```
## Distributed Caching
### Typed Cache
```csharp
public class BookService : ITransientDependency
{
private readonly IDistributedCache<BookCacheItem> _cache;
private readonly IClock _clock;
public BookService(IDistributedCache<BookCacheItem> cache, IClock clock)
{
_cache = cache;
_clock = clock;
}
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId.ToString(),
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = _clock.Now.AddHours(1)
}
);
}
}
[CacheName("Books")]
public class BookCacheItem
{
public string Name { get; set; }
public decimal Price { get; set; }
}
```
## Event Bus
### Local Events (Same Process)
```csharp
// Event class
public class OrderCreatedEvent
{
public Order Order { get; set; }
}
// Handler
public class OrderCreatedEventHandler : ILocalEventHandler<OrderCreatedEvent>, ITransientDependency
{
public async Task HandleEventAsync(OrderCreatedEvent eventData)
{
// Handle within same transaction
}
}
// Publish
await _localEventBus.PublishAsync(new OrderCreatedEvent { Order = order });
```
### Distributed Events (Cross-Service)
```csharp
// Event Transfer Object (in Domain.Shared)
[EventName("MyApp.Order.Created")]
public class OrderCreatedEto
{
public Guid OrderId { get; set; }
public string OrderNumber { get; set; }
}
// Handler
public class OrderCreatedEtoHandler : IDistributedEventHandler<OrderCreatedEto>, ITransientDependency
{
public async Task HandleEventAsync(OrderCreatedEto eventData)
{
// Handle distributed event
}
}
// Publish
await _distributedEventBus.PublishAsync(new OrderCreatedEto { ... });
```
### When to Use Which
- **Local**: Within same module/bounded context
- **Distributed**: Cross-module or microservice communication
## Background Jobs
### Define Job
```csharp
public class EmailSendingArgs
{
public string EmailAddress { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
}
public class EmailSendingJob : AsyncBackgroundJob<EmailSendingArgs>, ITransientDependency
{
private readonly IEmailSender _emailSender;
public EmailSendingJob(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public override async Task ExecuteAsync(EmailSendingArgs args)
{
await _emailSender.SendAsync(args.EmailAddress, args.Subject, args.Body);
}
}
```
### Enqueue Job
```csharp
await _backgroundJobManager.EnqueueAsync(
new EmailSendingArgs
{
EmailAddress = "user@example.com",
Subject = "Hello",
Body = "..."
},
delay: TimeSpan.FromMinutes(5) // Optional delay
);
```
## Localization
### Define Resource
```csharp
[LocalizationResourceName("MyModule")]
public class MyModuleResource { }
```
### JSON Structure
```json
{
"culture": "en",
"texts": {
"HelloWorld": "Hello World!",
"Menu:Books": "Books"
}
}
```
### Usage
- In `ApplicationService`: Use `L["Key"]` property (already available from base class)
- In other services: Inject `IStringLocalizer<MyResource>`
> **Tip**: ABP base classes already provide commonly used services as properties. Check before injecting:
> - `StringLocalizer` (L), `Clock`, `CurrentUser`, `CurrentTenant`, `GuidGenerator`
> - `AuthorizationService`, `FeatureChecker`, `DataFilter`
> - `LoggerFactory`, `Logger`
> - Methods like `CheckPolicyAsync()` for authorization checks

165
ai-rules/common/multi-tenancy.mdc

@ -0,0 +1,165 @@
---
description: "ABP Multi-Tenancy patterns - tenant-aware entities, data isolation, and tenant switching"
globs:
- "**/*Tenant*.cs"
- "**/*MultiTenant*.cs"
- "**/Entities/**/*.cs"
alwaysApply: false
---
# ABP Multi-Tenancy
> **Docs**: https://abp.io/docs/latest/framework/architecture/multi-tenancy
## Making Entities Multi-Tenant
Implement `IMultiTenant` interface to make entities tenant-aware:
```csharp
public class Product : AggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; } // Required by IMultiTenant
public string Name { get; private set; }
public decimal Price { get; private set; }
protected Product() { }
public Product(Guid id, string name, decimal price) : base(id)
{
Name = name;
Price = price;
// TenantId is automatically set from CurrentTenant.Id
}
}
```
**Key points:**
- `TenantId` is **nullable** - `null` means entity belongs to Host
- ABP **automatically filters** queries by current tenant
- ABP **automatically sets** `TenantId` when creating entities
## Accessing Current Tenant
Use `CurrentTenant` property (available in base classes) or inject `ICurrentTenant`:
```csharp
public class ProductAppService : ApplicationService
{
public async Task DoSomethingAsync()
{
// Available from base class
var tenantId = CurrentTenant.Id; // Guid? - null for host
var tenantName = CurrentTenant.Name; // string?
var isAvailable = CurrentTenant.IsAvailable; // true if Id is not null
}
}
// In other services
public class MyService : ITransientDependency
{
private readonly ICurrentTenant _currentTenant;
public MyService(ICurrentTenant currentTenant) => _currentTenant = currentTenant;
}
```
## Switching Tenant Context
Use `CurrentTenant.Change()` to temporarily switch tenant (useful in host context):
```csharp
public class ProductManager : DomainService
{
private readonly IRepository<Product, Guid> _productRepository;
public async Task<long> GetProductCountAsync(Guid? tenantId)
{
// Switch to specific tenant
using (CurrentTenant.Change(tenantId))
{
return await _productRepository.GetCountAsync();
}
// Automatically restored to previous tenant after using block
}
public async Task DoHostOperationAsync()
{
// Switch to host context
using (CurrentTenant.Change(null))
{
// Operations here are in host context
}
}
}
```
> **Important**: Always use `Change()` with a `using` statement.
## Disabling Multi-Tenant Filter
To query all tenants' data (only works with single database):
```csharp
public class ProductManager : DomainService
{
public async Task<long> GetAllProductCountAsync()
{
// DataFilter is available from base class
using (DataFilter.Disable<IMultiTenant>())
{
return await _productRepository.GetCountAsync();
// Returns count from ALL tenants
}
}
}
```
> **Note**: This doesn't work with separate databases per tenant.
## Database Architecture Options
| Approach | Description | Use Case |
|----------|-------------|----------|
| Single Database | All tenants share one database | Simple, cost-effective |
| Database per Tenant | Each tenant has dedicated database | Data isolation, compliance |
| Hybrid | Mix of shared and dedicated | Flexible, premium tenants |
Connection strings are configured per tenant in Tenant Management module.
## Best Practices
1. **Always implement `IMultiTenant`** for tenant-specific entities
2. **Never manually filter by `TenantId`** - ABP does it automatically
3. **Don't change `TenantId` after creation** - it moves entity between tenants
4. **Use `Change()` scope carefully** - nested scopes are supported
5. **Test both host and tenant contexts** - ensure proper data isolation
6. **Consider nullable `TenantId`** - entity may be host-only or shared
## Enabling Multi-Tenancy
```csharp
Configure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = true; // Enabled by default in ABP templates
});
```
Check `MultiTenancyConsts.IsEnabled` in your solution for centralized control.
## Tenant Resolution
ABP resolves current tenant from (in order):
1. Current user's claims
2. Query string (`?__tenant=...`)
3. Route (`/{__tenant}/...`)
4. HTTP header (`__tenant`)
5. Cookie (`__tenant`)
6. Domain/subdomain (if configured)
For subdomain-based resolution:
```csharp
Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.mydomain.com");
});
```

257
ai-rules/data/ef-core.mdc

@ -0,0 +1,257 @@
---
description: "ABP Entity Framework Core patterns - DbContext, migrations, repositories"
globs:
- "**/*.EntityFrameworkCore/**/*.cs"
- "**/EntityFrameworkCore/**/*.cs"
- "**/*DbContext*.cs"
alwaysApply: false
---
# ABP Entity Framework Core
> **Docs**: https://abp.io/docs/latest/framework/data/entity-framework-core
## DbContext Configuration
```csharp
[ConnectionStringName("Default")]
public class MyProjectDbContext : AbpDbContext<MyProjectDbContext>
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
public MyProjectDbContext(DbContextOptions<MyProjectDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Configure all entities
builder.ConfigureMyProject();
}
}
```
## Entity Configuration
```csharp
public static class MyProjectDbContextModelCreatingExtensions
{
public static void ConfigureMyProject(this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
builder.Entity<Book>(b =>
{
b.ToTable(MyProjectConsts.DbTablePrefix + "Books", MyProjectConsts.DbSchema);
b.ConfigureByConvention(); // ABP conventions (audit, soft-delete, etc.)
// Property configurations
b.Property(x => x.Name)
.IsRequired()
.HasMaxLength(BookConsts.MaxNameLength);
b.Property(x => x.Price)
.HasColumnType("decimal(18,2)");
// Indexes
b.HasIndex(x => x.Name);
// Relationships
b.HasOne<Author>()
.WithMany()
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.Restrict);
});
}
}
```
## Repository Implementation
```csharp
public class BookRepository : EfCoreRepository<MyProjectDbContext, Book, Guid>, IBookRepository
{
public BookRepository(IDbContextProvider<MyProjectDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Book> FindByNameAsync(
string name,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
var dbSet = await GetDbSetAsync();
return await dbSet
.IncludeDetails(includeDetails)
.FirstOrDefaultAsync(
b => b.Name == name,
GetCancellationToken(cancellationToken));
}
public async Task<List<Book>> GetListByAuthorAsync(
Guid authorId,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var dbSet = await GetDbSetAsync();
return await dbSet
.IncludeDetails(includeDetails)
.Where(b => b.AuthorId == authorId)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public override async Task<IQueryable<Book>> WithDetailsAsync()
{
return (await GetQueryableAsync())
.Include(b => b.Reviews);
}
}
```
## Extension Method for Include
```csharp
public static class BookEfCoreQueryableExtensions
{
public static IQueryable<Book> IncludeDetails(
this IQueryable<Book> queryable,
bool include = true)
{
if (!include)
{
return queryable;
}
return queryable
.Include(b => b.Reviews);
}
}
```
## Migration Commands
```bash
# Navigate to EF Core project
cd src/MyProject.EntityFrameworkCore
# Add migration
dotnet ef migrations add MigrationName
# Apply migration (choose one):
dotnet run --project ../MyProject.DbMigrator # Recommended - also seeds data
dotnet ef database update # EF Core command only
# Remove last migration (if not applied)
dotnet ef migrations remove
# Generate SQL script
dotnet ef migrations script
```
> **Note**: ABP templates include `IDesignTimeDbContextFactory` in the EF Core project, so `-s` (startup project) parameter is not needed.
## Module Configuration
```csharp
[DependsOn(typeof(AbpEntityFrameworkCoreModule))]
public class MyProjectEntityFrameworkCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<MyProjectDbContext>(options =>
{
// Add default repositories for aggregate roots only (DDD best practice)
options.AddDefaultRepositories();
// ⚠️ Avoid includeAllEntities: true - it creates repositories for child entities,
// allowing them to be modified without going through the aggregate root,
// which breaks data consistency
});
Configure<AbpDbContextOptions>(options =>
{
options.UseSqlServer(); // or UseNpgsql(), UseMySql(), etc.
});
}
}
```
## Best Practices
### Repositories for Aggregate Roots Only
Don't use `includeAllEntities: true` in `AddDefaultRepositories()`. This creates repositories for child entities, allowing direct modification without going through the aggregate root - breaking DDD data consistency rules.
```csharp
// ✅ Correct - Only aggregate roots get repositories
options.AddDefaultRepositories();
// ❌ Avoid - Creates repositories for ALL entities including child entities
options.AddDefaultRepositories(includeAllEntities: true);
```
### Always Call ConfigureByConvention
```csharp
builder.Entity<MyEntity>(b =>
{
b.ConfigureByConvention(); // Don't forget this!
// Other configurations...
});
```
### Use Table Prefix
```csharp
public static class MyProjectConsts
{
public const string DbTablePrefix = "App";
public const string DbSchema = null; // Or "myschema"
}
```
### Performance Tips
- Add explicit indexes for frequently queried fields
- Use `AsNoTracking()` for read-only queries
- Avoid N+1 queries with `.Include()` or specifications
- ABP handles cancellation automatically; use `GetCancellationToken(cancellationToken)` only in custom repository methods
- Consider query splitting for complex queries with multiple collections
### Accessing Raw DbContext
```csharp
public async Task CustomOperationAsync()
{
var dbContext = await GetDbContextAsync();
// Raw SQL
await dbContext.Database.ExecuteSqlRawAsync(
"UPDATE Books SET IsPublished = 1 WHERE AuthorId = {0}",
authorId
);
}
```
## Data Seeding
```csharp
public class MyProjectDataSeedContributor : IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() > 0)
{
return;
}
await _bookRepository.InsertAsync(
new Book(_guidGenerator.Create(), "Sample Book", 19.99m, Guid.Empty),
autoSave: true
);
}
}
```

206
ai-rules/data/mongodb.mdc

@ -0,0 +1,206 @@
---
description: "ABP MongoDB patterns - MongoDbContext and repositories"
globs:
- "**/*.MongoDB/**/*.cs"
- "**/MongoDB/**/*.cs"
- "**/*MongoDb*.cs"
alwaysApply: false
---
# ABP MongoDB
> **Docs**: https://abp.io/docs/latest/framework/data/mongodb
## MongoDbContext Configuration
```csharp
[ConnectionStringName("Default")]
public class MyProjectMongoDbContext : AbpMongoDbContext
{
public IMongoCollection<Book> Books => Collection<Book>();
public IMongoCollection<Author> Authors => Collection<Author>();
protected override void CreateModel(IMongoModelBuilder modelBuilder)
{
base.CreateModel(modelBuilder);
modelBuilder.ConfigureMyProject();
}
}
```
## Entity Configuration
```csharp
public static class MyProjectMongoDbContextExtensions
{
public static void ConfigureMyProject(this IMongoModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
builder.Entity<Book>(b =>
{
b.CollectionName = MyProjectConsts.DbTablePrefix + "Books";
});
builder.Entity<Author>(b =>
{
b.CollectionName = MyProjectConsts.DbTablePrefix + "Authors";
});
}
}
```
## Repository Implementation
```csharp
public class BookRepository : MongoDbRepository<MyProjectMongoDbContext, Book, Guid>, IBookRepository
{
public BookRepository(IMongoDbContextProvider<MyProjectMongoDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Book> FindByNameAsync(
string name,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await (await GetQueryableAsync())
.FirstOrDefaultAsync(
b => b.Name == name,
GetCancellationToken(cancellationToken));
}
public async Task<List<Book>> GetListByAuthorAsync(
Guid authorId,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await (await GetQueryableAsync())
.Where(b => b.AuthorId == authorId)
.ToListAsync(GetCancellationToken(cancellationToken));
}
}
```
## Module Configuration
```csharp
[DependsOn(typeof(AbpMongoDbModule))]
public class MyProjectMongoDbModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMongoDbContext<MyProjectMongoDbContext>(options =>
{
// Add default repositories for aggregate roots only (DDD best practice)
options.AddDefaultRepositories();
// ⚠️ Avoid includeAllEntities: true - breaks DDD data consistency
});
}
}
```
## Connection String
In `appsettings.json`:
```json
{
"ConnectionStrings": {
"Default": "mongodb://localhost:27017/MyProjectDb"
}
}
```
## Key Differences from EF Core
### No Migrations
MongoDB is schema-less; no migrations needed. Changes to entity structure are handled automatically.
### includeDetails Parameter
Often ignored in MongoDB because documents typically embed related data:
```csharp
public async Task<List<Book>> GetListAsync(
bool includeDetails = false, // Usually ignored
CancellationToken cancellationToken = default)
{
// MongoDB documents already include nested data
return await (await GetQueryableAsync())
.ToListAsync(GetCancellationToken(cancellationToken));
}
```
### Embedded Documents vs References
```csharp
// Embedded (stored in same document)
public class Order : AggregateRoot<Guid>
{
public List<OrderLine> Lines { get; set; } // Embedded
}
// Reference (separate collection, store ID only)
public class Order : AggregateRoot<Guid>
{
public Guid CustomerId { get; set; } // Reference by ID
}
```
### No Change Tracking
MongoDB doesn't track entity changes automatically:
```csharp
public async Task UpdateBookAsync(Guid id, string newName)
{
var book = await _bookRepository.GetAsync(id);
book.SetName(newName);
// Must explicitly update
await _bookRepository.UpdateAsync(book);
}
```
## Direct Collection Access
```csharp
public async Task CustomOperationAsync()
{
var collection = await GetCollectionAsync();
// Use MongoDB driver directly
var filter = Builders<Book>.Filter.Eq(b => b.AuthorId, authorId);
var update = Builders<Book>.Update.Set(b => b.IsPublished, true);
await collection.UpdateManyAsync(filter, update);
}
```
## Indexing
Configure indexes in repository or via MongoDB driver:
```csharp
public class BookRepository : MongoDbRepository<MyProjectMongoDbContext, Book, Guid>, IBookRepository
{
public override async Task<IQueryable<Book>> GetQueryableAsync()
{
var collection = await GetCollectionAsync();
// Ensure index exists
var indexKeys = Builders<Book>.IndexKeys.Ascending(b => b.Name);
await collection.Indexes.CreateOneAsync(new CreateIndexModel<Book>(indexKeys));
return await base.GetQueryableAsync();
}
}
```
## Best Practices
- Design documents for query patterns (denormalize when needed)
- Use references for frequently changing data
- Use embedding for data that's always accessed together
- Add indexes for frequently queried fields
- Use `GetCancellationToken(cancellationToken)` for proper cancellation
- Remember: ABP data filters (soft-delete, multi-tenancy) work with MongoDB too

83
ai-rules/template-specific/app-nolayers.mdc

@ -0,0 +1,83 @@
---
description: "ABP Single-Layer (No-Layers) application template specific patterns"
globs:
- "**/src/*/*Module.cs"
- "**/src/*/Entities/**/*.cs"
- "**/src/*/Services/**/*.cs"
- "**/src/*/Data/**/*.cs"
alwaysApply: false
---
# ABP Single-Layer Application Template
> **Docs**: https://abp.io/docs/latest/solution-templates/single-layer-web-application
## Solution Structure
Single project containing everything:
```
MyProject/
├── src/
│ └── MyProject/
│ ├── Data/ # DbContext, migrations
│ ├── Entities/ # Domain entities
│ ├── Services/ # Application services + DTOs
│ ├── Pages/ # Razor pages / Blazor components
│ └── MyProjectModule.cs
└── test/
└── MyProject.Tests/
```
## Key Differences from Layered
| Layered Template | Single-Layer Template |
|------------------|----------------------|
| DTOs in Application.Contracts | DTOs in Services folder (same project) |
| Repository interfaces in Domain | Use generic `IRepository<T, TKey>` directly |
| Separate Domain.Shared for constants | Constants in same project |
| Multiple module classes | Single module class |
## File Organization
Group related files by feature:
```
Services/
├── Books/
│ ├── BookAppService.cs
│ ├── BookDto.cs
│ ├── CreateBookDto.cs
│ └── IBookAppService.cs
└── Authors/
├── AuthorAppService.cs
└── ...
```
## Simplified Entity (Still keep invariants)
Single-layer templates are structurally simpler, but you may still have real business invariants.
- For **trivial CRUD** entities, public setters can be acceptable.
- For **non-trivial business rules**, still prefer encapsulation (private setters + methods) to prevent invalid states.
```csharp
public class Book : AuditedAggregateRoot<Guid>
{
public string Name { get; set; } // OK for trivial CRUD only
public decimal Price { get; set; }
}
```
## No Custom Repository Needed
Use generic repository directly - no need to define custom interfaces:
```csharp
public class BookAppService : ApplicationService
{
private readonly IRepository<Book, Guid> _bookRepository;
// Generic repository is sufficient for single-layer apps
}
```

209
ai-rules/template-specific/microservice.mdc

@ -0,0 +1,209 @@
---
description: "ABP Microservice solution template specific patterns"
alwaysApply: false
---
# ABP Microservice Solution Template
> **Docs**: https://abp.io/docs/latest/solution-templates/microservice
## Solution Structure
```
MyMicroservice/
├── apps/ # UI applications
│ ├── web/ # Web application
│ ├── public-web/ # Public website
│ └── auth-server/ # Authentication server (OpenIddict)
├── gateways/ # BFF pattern - one gateway per UI
│ └── web-gateway/ # YARP reverse proxy
├── services/ # Microservices
│ ├── administration/ # Permissions, settings, features
│ ├── identity/ # Users, roles
│ └── [your-services]/ # Your business services
└── etc/
├── docker/ # Docker compose for local infra
└── helm/ # Kubernetes deployment
```
## Microservice Structure (NOT Layered!)
Each microservice has simplified structure - everything in one project:
```
services/ordering/
├── OrderingService/ # Main project
│ ├── Entities/
│ ├── Services/
│ ├── IntegrationServices/ # For inter-service communication
│ ├── Data/ # DbContext (implements IHasEventInbox, IHasEventOutbox)
│ └── OrderingServiceModule.cs
├── OrderingService.Contracts/ # Interfaces, DTOs, ETOs (shared)
└── OrderingService.Tests/
```
## Inter-Service Communication
### 1. Integration Services (Synchronous HTTP)
For synchronous calls, use **Integration Services** - NOT regular application services.
#### Step 1: Provider Service - Create Integration Service
```csharp
// In CatalogService.Contracts project
[IntegrationService]
public interface IProductIntegrationService : IApplicationService
{
Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids);
}
// In CatalogService project
[IntegrationService]
public class ProductIntegrationService : ApplicationService, IProductIntegrationService
{
public async Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids)
{
var products = await _productRepository.GetListAsync(p => ids.Contains(p.Id));
return ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
}
}
```
#### Step 2: Provider Service - Expose Integration Services
```csharp
// In CatalogServiceModule.cs
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
```
#### Step 3: Consumer Service - Add Package Reference
Add reference to provider's Contracts project (via ABP Studio or manually):
- Right-click OrderingService → Add Package Reference → Select `CatalogService.Contracts`
#### Step 4: Consumer Service - Generate Proxies
```bash
# Run ABP CLI in consumer service folder
abp generate-proxy -t csharp -u http://localhost:44361 -m catalog --without-contracts
```
Or use ABP Studio: Right-click service → ABP CLI → Generate Proxy → C#
#### Step 5: Consumer Service - Register HTTP Client Proxies
```csharp
// In OrderingServiceModule.cs
[DependsOn(typeof(CatalogServiceContractsModule))] // Add module dependency
public class OrderingServiceModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Register static HTTP client proxies
context.Services.AddStaticHttpClientProxies(
typeof(CatalogServiceContractsModule).Assembly,
"CatalogService");
}
}
```
#### Step 6: Consumer Service - Configure Remote Service URL
```json
// appsettings.json
"RemoteServices": {
"CatalogService": {
"BaseUrl": "http://localhost:44361"
}
}
```
#### Step 7: Use Integration Service
```csharp
public class OrderAppService : ApplicationService
{
private readonly IProductIntegrationService _productIntegrationService;
public async Task<List<OrderDto>> GetListAsync()
{
var orders = await _orderRepository.GetListAsync();
var productIds = orders.Select(o => o.ProductId).Distinct().ToList();
// Call remote service via generated proxy
var products = await _productIntegrationService.GetProductsByIdsAsync(productIds);
// ...
}
}
```
> **Why Integration Services?** Application services are for UI - they have different authorization, validation, and optimization needs. Integration services are designed specifically for inter-service communication.
**When to use:** Need immediate response, data required to complete current operation (e.g., get product details to display in order list).
### 2. Distributed Events (Asynchronous)
Use RabbitMQ-based events for loose coupling.
**When to use:**
- Notifying other services about state changes (e.g., "order placed", "stock updated")
- Operations that don't need immediate response
- When services should remain independent and decoupled
```csharp
// Define ETO in Contracts project
[EventName("Product.StockChanged")]
public class StockCountChangedEto
{
public Guid ProductId { get; set; }
public int NewCount { get; set; }
}
// Publish
await _distributedEventBus.PublishAsync(new StockCountChangedEto { ... });
// Subscribe in another service
public class StockChangedHandler : IDistributedEventHandler<StockCountChangedEto>, ITransientDependency
{
public async Task HandleEventAsync(StockCountChangedEto eventData) { ... }
}
```
DbContext must implement `IHasEventInbox`, `IHasEventOutbox` for Outbox/Inbox pattern.
## Performance: Entity Cache
For frequently accessed data from other services, use Entity Cache:
```csharp
// Register
context.Services.AddEntityCache<Product, ProductDto, Guid>();
// Use - auto-invalidates on entity changes
private readonly IEntityCache<ProductDto, Guid> _productCache;
public async Task<ProductDto> GetProductAsync(Guid id)
{
return await _productCache.GetAsync(id);
}
```
## Pre-Configured Infrastructure
- **RabbitMQ** - Distributed events with Outbox/Inbox
- **Redis** - Distributed cache and locking
- **YARP** - API Gateway
- **OpenIddict** - Auth server
## Best Practices
- **Choose communication wisely** - Synchronous for queries needing immediate data, asynchronous for notifications and state changes
- **Use Integration Services** - Not application services for inter-service calls
- **Cache remote data** - Use Entity Cache or IDistributedCache for frequently accessed data
- **Share only Contracts** - Never share implementations
- **Idempotent handlers** - Events may be delivered multiple times
- **Database per service** - Each service owns its database

234
ai-rules/template-specific/module.mdc

@ -0,0 +1,234 @@
---
description: "ABP Module solution template specific patterns"
alwaysApply: false
---
# ABP Module Solution Template
> **Docs**: https://abp.io/docs/latest/solution-templates/application-module
This template is for developing reusable ABP modules. Key requirement: **extensibility** - consumers must be able to override and customize module behavior.
## Solution Structure
```
MyModule/
├── src/
│ ├── MyModule.Domain.Shared/ # Constants, enums, localization
│ ├── MyModule.Domain/ # Entities, repository interfaces, domain services
│ ├── MyModule.Application.Contracts/ # DTOs, service interfaces
│ ├── MyModule.Application/ # Service implementations
│ ├── MyModule.EntityFrameworkCore/ # EF Core implementation
│ ├── MyModule.MongoDB/ # MongoDB implementation
│ ├── MyModule.HttpApi/ # REST controllers
│ ├── MyModule.HttpApi.Client/ # Client proxies
│ ├── MyModule.Web/ # MVC/Razor Pages UI
│ └── MyModule.Blazor/ # Blazor UI
├── test/
│ └── MyModule.Tests/
└── host/
└── MyModule.HttpApi.Host/ # Test host application
```
## Database Independence
Support both EF Core and MongoDB:
### Repository Interface (Domain)
```csharp
public interface IBookRepository : IRepository<Book, Guid>
{
Task<Book> FindByNameAsync(string name);
Task<List<Book>> GetListByAuthorAsync(Guid authorId);
}
```
### EF Core Implementation
```csharp
public class BookRepository : EfCoreRepository<MyModuleDbContext, Book, Guid>, IBookRepository
{
public async Task<Book> FindByNameAsync(string name)
{
var dbSet = await GetDbSetAsync();
return await dbSet.FirstOrDefaultAsync(b => b.Name == name);
}
}
```
### MongoDB Implementation
```csharp
public class BookRepository : MongoDbRepository<MyModuleMongoDbContext, Book, Guid>, IBookRepository
{
public async Task<Book> FindByNameAsync(string name)
{
var queryable = await GetQueryableAsync();
return await queryable.FirstOrDefaultAsync(b => b.Name == name);
}
}
```
## Table/Collection Prefix
Allow customization to avoid naming conflicts:
```csharp
// Domain.Shared
public static class MyModuleDbProperties
{
public static string DbTablePrefix { get; set; } = "MyModule";
public static string DbSchema { get; set; } = null;
public const string ConnectionStringName = "MyModule";
}
```
Usage:
```csharp
builder.Entity<Book>(b =>
{
b.ToTable(MyModuleDbProperties.DbTablePrefix + "Books", MyModuleDbProperties.DbSchema);
});
```
## Module Options
Provide configuration options:
```csharp
// Domain
public class MyModuleOptions
{
public bool EnableFeatureX { get; set; } = true;
public int MaxItemCount { get; set; } = 100;
}
```
Usage in module:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<MyModuleOptions>(options =>
{
options.EnableFeatureX = true;
});
}
```
Usage in service:
```csharp
public class MyService : ITransientDependency
{
private readonly MyModuleOptions _options;
public MyService(IOptions<MyModuleOptions> options)
{
_options = options.Value;
}
}
```
## Extensibility Points
### Virtual Methods (Critical for Modules!)
When developing a reusable module, **all public and protected methods must be virtual** to allow consumers to override behavior:
```csharp
public class BookAppService : ApplicationService, IBookAppService
{
// ✅ Public methods MUST be virtual
public virtual async Task<BookDto> CreateAsync(CreateBookDto input)
{
var book = await CreateBookEntityAsync(input);
await _bookRepository.InsertAsync(book);
return _bookMapper.MapToDto(book);
}
// ✅ Use protected virtual for helper methods (not private)
protected virtual Task<Book> CreateBookEntityAsync(CreateBookDto input)
{
return Task.FromResult(new Book(
GuidGenerator.Create(),
input.Name,
input.Price
));
}
// ❌ WRONG for modules - private methods cannot be overridden
// private Book CreateBook(CreateBookDto input) { ... }
}
```
This allows module consumers to:
- Override specific methods without copying entire class
- Extend functionality while preserving base behavior
- Customize module behavior for their needs
### Entity Extension
Support object extension system:
```csharp
public class MyModuleModuleExtensionConfigurator
{
public static void Configure()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance.Modules()
.ConfigureMyModule(module =>
{
module.ConfigureBook(book =>
{
book.AddOrUpdateProperty<string>("CustomProperty");
});
});
});
}
}
```
## Localization
```csharp
// Domain.Shared
[LocalizationResourceName("MyModule")]
public class MyModuleResource
{
}
// Module configuration
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Add<MyModuleResource>("en")
.AddVirtualJson("/Localization/MyModule");
});
```
## Permission Definition
```csharp
public class MyModulePermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup(
MyModulePermissions.GroupName,
L("Permission:MyModule"));
myGroup.AddPermission(
MyModulePermissions.Books.Default,
L("Permission:Books"));
}
}
```
## Best Practices
1. **Virtual methods** - All public/protected methods must be `virtual` for extensibility
2. **Protected virtual helpers** - Use `protected virtual` instead of `private` for helper methods
3. **Database agnostic** - Support both EF Core and MongoDB
4. **Configurable** - Use options pattern for customization
5. **Localizable** - Use localization for all user-facing text
6. **Table prefix** - Allow customization to avoid conflicts
7. **Separate connection string** - Support dedicated database
8. **No dependencies on host** - Module should be self-contained
9. **Test with host app** - Include a host application for testing

274
ai-rules/testing/patterns.mdc

@ -0,0 +1,274 @@
---
description: "ABP testing patterns - unit tests and integration tests"
globs:
- "test/**/*.cs"
- "tests/**/*.cs"
- "**/*Tests*/**/*.cs"
- "**/*Test*.cs"
alwaysApply: false
---
# ABP Testing Patterns
> **Docs**: https://abp.io/docs/latest/testing
## Test Project Structure
| Project | Purpose | Base Class |
|---------|---------|------------|
| `*.Domain.Tests` | Domain logic, entities, domain services | `*DomainTestBase` |
| `*.Application.Tests` | Application services | `*ApplicationTestBase` |
| `*.EntityFrameworkCore.Tests` | Repository implementations | `*EntityFrameworkCoreTestBase` |
## Integration Test Approach
ABP recommends integration tests over unit tests:
- Tests run with real services and database (SQLite in-memory)
- No mocking of internal services
- Each test gets a fresh database instance
## Application Service Test
```csharp
public class BookAppService_Tests : MyProjectApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
// Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
// Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "Test Book");
}
[Fact]
public async Task Should_Create_Book()
{
// Arrange
var input = new CreateBookDto
{
Name = "New Book",
Price = 19.99m
};
// Act
var result = await _bookAppService.CreateAsync(input);
// Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New Book");
result.Price.ShouldBe(19.99m);
}
[Fact]
public async Task Should_Not_Create_Book_With_Invalid_Name()
{
// Arrange
var input = new CreateBookDto
{
Name = "", // Invalid
Price = 10m
};
// Act & Assert
await Should.ThrowAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(input);
});
}
}
```
## Domain Service Test
```csharp
public class BookManager_Tests : MyProjectDomainTestBase
{
private readonly BookManager _bookManager;
private readonly IBookRepository _bookRepository;
public BookManager_Tests()
{
_bookManager = GetRequiredService<BookManager>();
_bookRepository = GetRequiredService<IBookRepository>();
}
[Fact]
public async Task Should_Create_Book()
{
// Act
var book = await _bookManager.CreateAsync("Test Book", 29.99m);
// Assert
book.ShouldNotBeNull();
book.Name.ShouldBe("Test Book");
book.Price.ShouldBe(29.99m);
}
[Fact]
public async Task Should_Not_Allow_Duplicate_Book_Name()
{
// Arrange
await _bookManager.CreateAsync("Existing Book", 10m);
// Act & Assert
var exception = await Should.ThrowAsync<BusinessException>(async () =>
{
await _bookManager.CreateAsync("Existing Book", 20m);
});
exception.Code.ShouldBe("MyProject:BookNameAlreadyExists");
}
}
```
## Test Naming Convention
Use descriptive names:
```csharp
// Pattern: Should_ExpectedBehavior_When_Condition
public async Task Should_Create_Book_When_Input_Is_Valid()
public async Task Should_Throw_BusinessException_When_Name_Already_Exists()
public async Task Should_Return_Empty_List_When_No_Books_Exist()
```
## Arrange-Act-Assert (AAA)
```csharp
[Fact]
public async Task Should_Update_Book_Price()
{
// Arrange
var bookId = await CreateTestBookAsync();
var newPrice = 39.99m;
// Act
var result = await _bookAppService.UpdateAsync(bookId, new UpdateBookDto
{
Price = newPrice
});
// Assert
result.Price.ShouldBe(newPrice);
}
```
## Assertions with Shouldly
ABP uses Shouldly library:
```csharp
result.ShouldNotBeNull();
result.Name.ShouldBe("Expected Name");
result.Price.ShouldBeGreaterThan(0);
result.Items.ShouldContain(x => x.Id == expectedId);
result.Items.ShouldBeEmpty();
result.Items.Count.ShouldBe(5);
// Exception assertions
await Should.ThrowAsync<BusinessException>(async () =>
{
await _service.DoSomethingAsync();
});
var ex = await Should.ThrowAsync<BusinessException>(async () =>
{
await _service.DoSomethingAsync();
});
ex.Code.ShouldBe("MyProject:ErrorCode");
```
## Test Data Seeding
```csharp
public class MyProjectTestDataSeedContributor : IDataSeedContributor, ITransientDependency
{
public static readonly Guid TestBookId = Guid.Parse("...");
private readonly IBookRepository _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public async Task SeedAsync(DataSeedContext context)
{
await _bookRepository.InsertAsync(
new Book(TestBookId, "Test Book", 19.99m, Guid.Empty),
autoSave: true
);
}
}
```
## Disabling Authorization in Tests
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAlwaysAllowAuthorization();
}
```
## Mocking External Services
Use NSubstitute when needed:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var emailSender = Substitute.For<IEmailSender>();
emailSender.SendAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
.Returns(Task.CompletedTask);
context.Services.AddSingleton(emailSender);
}
```
## Testing with Specific User
```csharp
[Fact]
public async Task Should_Get_Current_User_Books()
{
// Login as specific user
await WithUnitOfWorkAsync(async () =>
{
using (CurrentUser.Change(TestData.UserId))
{
var result = await _bookAppService.GetMyBooksAsync();
result.Items.ShouldAllBe(b => b.CreatorId == TestData.UserId);
}
});
}
```
## Testing Multi-Tenancy
```csharp
[Fact]
public async Task Should_Filter_Books_By_Tenant()
{
using (CurrentTenant.Change(TestData.TenantId))
{
var result = await _bookAppService.GetListAsync(new GetBookListDto());
// Results should be filtered by tenant
}
}
```
## Best Practices
- Each test should be independent
- Don't share state between tests
- Use meaningful test data
- Test edge cases and error conditions
- Keep tests focused on single behavior
- Use test data seeders for common data
- Avoid testing framework internals

224
ai-rules/ui/angular.mdc

@ -0,0 +1,224 @@
---
description: "ABP Angular UI patterns and best practices"
globs:
- "**/angular/**/*.ts"
- "**/angular/**/*.html"
- "**/*.component.ts"
alwaysApply: false
---
# ABP Angular UI
> **Docs**: https://abp.io/docs/latest/framework/ui/angular/overview
## Project Structure
```
src/app/
├── proxy/ # Auto-generated service proxies
├── shared/ # Shared components, pipes, directives
├── book/ # Feature module
│ ├── book.module.ts
│ ├── book-routing.module.ts
│ ├── book-list/
│ │ ├── book-list.component.ts
│ │ ├── book-list.component.html
│ │ └── book-list.component.scss
│ └── book-detail/
```
## Generate Service Proxies
```bash
abp generate-proxy -t ng
```
This generates typed service classes in `src/app/proxy/`.
## List Component Pattern
```typescript
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html'
})
export class BookListComponent implements OnInit {
books = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
constructor(
public readonly list: ListService,
private bookService: BookService,
private confirmation: ConfirmationService
) {}
ngOnInit(): void {
this.hookToQuery();
}
private hookToQuery(): void {
this.list.hookToQuery(query =>
this.bookService.getList(query)
).subscribe(response => {
this.books = response;
});
}
create(): void {
// Open create modal
}
delete(book: BookDto): void {
this.confirmation
.warn('::AreYouSureToDelete', '::AreYouSure')
.subscribe(status => {
if (status === Confirmation.Status.confirm) {
this.bookService.delete(book.id).subscribe(() => this.list.get());
}
});
}
}
```
## Localization
```typescript
// In component
constructor(private localizationService: LocalizationService) {}
getText(): string {
return this.localizationService.instant('::Books');
}
```
```html
<!-- In template -->
<h1>{{ '::Books' | abpLocalization }}</h1>
<!-- With parameters -->
<p>{{ '::WelcomeMessage' | abpLocalization: userName }}</p>
```
## Authorization
### Permission Directive
```html
<button *abpPermission="'BookStore.Books.Create'">Create</button>
```
### Permission Guard
```typescript
const routes: Routes = [
{
path: '',
component: BookListComponent,
canActivate: [PermissionGuard],
data: {
requiredPolicy: 'BookStore.Books'
}
}
];
```
### Programmatic Check
```typescript
constructor(private permissionService: PermissionService) {}
canCreate(): boolean {
return this.permissionService.getGrantedPolicy('BookStore.Books.Create');
}
```
## Forms with Validation
```typescript
@Component({...})
export class BookFormComponent {
form: FormGroup;
constructor(private fb: FormBuilder) {
this.buildForm();
}
buildForm(): void {
this.form = this.fb.group({
name: ['', [Validators.required, Validators.maxLength(128)]],
price: [0, [Validators.required, Validators.min(0)]]
});
}
save(): void {
if (this.form.invalid) return;
this.bookService.create(this.form.value).subscribe(() => {
// Handle success
});
}
}
```
```html
<form [formGroup]="form" (ngSubmit)="save()">
<div class="form-group">
<label for="name">{{ '::Name' | abpLocalization }}</label>
<input type="text" id="name" formControlName="name" class="form-control" />
</div>
<button type="submit" class="btn btn-primary" [disabled]="form.invalid">
{{ '::Save' | abpLocalization }}
</button>
</form>
```
## Configuration API
```typescript
constructor(private configService: ConfigStateService) {}
getCurrentUser(): CurrentUserDto {
return this.configService.getOne('currentUser');
}
getSettings(): void {
const setting = this.configService.getSetting('MyApp.MaxItemCount');
}
```
## Modal Service
```typescript
constructor(private modalService: ModalService) {}
openCreateModal(): void {
const modalRef = this.modalService.open(BookFormComponent, {
size: 'lg'
});
modalRef.result.then(result => {
if (result) {
this.list.get();
}
});
}
```
## Toast Notifications
```typescript
constructor(private toaster: ToasterService) {}
showSuccess(): void {
this.toaster.success('::BookCreatedSuccessfully', '::Success');
}
showError(error: string): void {
this.toaster.error(error, '::Error');
}
```
## Lazy Loading Modules
```typescript
// app-routing.module.ts
const routes: Routes = [
{
path: 'books',
loadChildren: () => import('./book/book.module').then(m => m.BookModule)
}
];
```
## Theme & Styling
- Use Bootstrap classes
- ABP provides theme variables via CSS custom properties
- Component-specific styles in `.component.scss`

210
ai-rules/ui/blazor.mdc

@ -0,0 +1,210 @@
---
description: "ABP Blazor UI patterns and components"
globs:
- "**/*.razor"
- "**/Blazor/**/*.cs"
- "**/*.Blazor*/**/*.cs"
alwaysApply: false
---
# ABP Blazor UI
> **Docs**: https://abp.io/docs/latest/framework/ui/blazor/overall
## Component Base Classes
### Basic Component
```razor
@inherits AbpComponentBase
<h1>@L["Books"]</h1>
```
### CRUD Page
```razor
@page "/books"
@inherits AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<Card>
<CardHeader>
<Row>
<Column>
<h2>@L["Books"]</h2>
</Column>
<Column TextAlignment="TextAlignment.End">
@if (HasCreatePermission)
{
<Button Color="Color.Primary" Clicked="OpenCreateModalAsync">
@L["NewBook"]
</Button>
}
</Column>
</Row>
</CardHeader>
<CardBody>
<DataGrid TItem="BookDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridColumn Field="@nameof(BookDto.Name)" Caption="@L["Name"]" />
<DataGridColumn Field="@nameof(BookDto.Price)" Caption="@L["Price"]" />
<DataGridEntityActionsColumn TItem="BookDto">
<DisplayTemplate>
<EntityActions TItem="BookDto">
<EntityAction TItem="BookDto"
Text="@L["Edit"]"
Visible="HasUpdatePermission"
Clicked="() => OpenEditModalAsync(context)" />
<EntityAction TItem="BookDto"
Text="@L["Delete"]"
Visible="HasDeletePermission"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="() => GetDeleteConfirmationMessage(context)" />
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
</DataGridColumns>
</DataGrid>
</CardBody>
</Card>
```
## Localization
```razor
@* Using L property from base class *@
<h1>@L["PageTitle"]</h1>
@* With parameters *@
<p>@L["WelcomeMessage", CurrentUser.UserName]</p>
```
## Authorization
```razor
@* Check permission before rendering *@
@if (await AuthorizationService.IsGrantedAsync("MyPermission"))
{
<Button>Admin Action</Button>
}
@* Using policy-based authorization *@
<AuthorizeView Policy="MyPolicy">
<Authorized>
<p>You have access!</p>
</Authorized>
</AuthorizeView>
```
## Navigation & Menu
Configure in `*MenuContributor.cs`:
```csharp
public class MyMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name == StandardMenus.Main)
{
var bookMenu = new ApplicationMenuItem(
"Books",
l["Menu:Books"],
"/books",
icon: "fa fa-book"
);
if (await context.IsGrantedAsync(MyPermissions.Books.Default))
{
context.Menu.AddItem(bookMenu);
}
}
}
}
```
## Notifications & Messages
```csharp
// Success message
await Message.Success(L["BookCreatedSuccessfully"]);
// Confirmation dialog
if (await Message.Confirm(L["AreYouSure"]))
{
// User confirmed
}
// Toast notification
await Notify.Success(L["OperationCompleted"]);
```
## Forms & Validation
```razor
<Form @ref="CreateForm">
<Validations @ref="CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError />
</Feedback>
</TextEdit>
</Field>
</Validation>
</Validations>
</Form>
```
## JavaScript Interop
```csharp
@inject IJSRuntime JsRuntime
@code {
private async Task CallJavaScript()
{
await JsRuntime.InvokeVoidAsync("myFunction", arg1, arg2);
var result = await JsRuntime.InvokeAsync<string>("myFunctionWithReturn");
}
}
```
## State Management
```csharp
// Inject service proxy from HttpApi.Client
@inject IBookAppService BookAppService
@code {
private List<BookDto> Books { get; set; }
protected override async Task OnInitializedAsync()
{
var result = await BookAppService.GetListAsync(new PagedAndSortedResultRequestDto());
Books = result.Items.ToList();
}
}
```
## Code-Behind Pattern
**Books.razor:**
```razor
@page "/books"
@inherits BooksBase
```
**Books.razor.cs:**
```csharp
public partial class Books : BooksBase
{
// Component logic here
}
```
**BooksBase.cs:**
```csharp
public abstract class BooksBase : AbpComponentBase
{
[Inject]
protected IBookAppService BookAppService { get; set; }
}
```

262
ai-rules/ui/mvc.mdc

@ -0,0 +1,262 @@
---
description: "ABP MVC and Razor Pages UI patterns"
globs:
- "**/*.cshtml"
- "**/Pages/**/*.cs"
- "**/Views/**/*.cs"
- "**/Controllers/**/*.cs"
alwaysApply: false
---
# ABP MVC / Razor Pages UI
> **Docs**: https://abp.io/docs/latest/framework/ui/mvc-razor-pages/overall
## Razor Page Model
```csharp
public class IndexModel : AbpPageModel
{
private readonly IBookAppService _bookAppService;
public List<BookDto> Books { get; set; }
public IndexModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public async Task OnGetAsync()
{
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
Books = result.Items.ToList();
}
}
```
## Razor Page View
```html
@page
@model IndexModel
<abp-card>
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<h2>@L["Books"]</h2>
</abp-column>
<abp-column size-md="_6" class="text-end">
<abp-button button-type="Primary"
id="NewBookButton"
text="@L["NewBook"].Value" />
</abp-column>
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="BooksTable">
<thead>
<tr>
<th>@L["Name"]</th>
<th>@L["Price"]</th>
<th>@L["Actions"]</th>
</tr>
</thead>
<tbody>
@foreach (var book in Model.Books)
{
<tr>
<td>@book.Name</td>
<td>@book.Price</td>
<td>
<abp-button button-type="Primary" size="Small"
text="@L["Edit"].Value" />
</td>
</tr>
}
</tbody>
</abp-table>
</abp-card-body>
</abp-card>
```
## ABP Tag Helpers
### Cards
```html
<abp-card>
<abp-card-header>Header</abp-card-header>
<abp-card-body>Content</abp-card-body>
<abp-card-footer>Footer</abp-card-footer>
</abp-card>
```
### Buttons
```html
<abp-button button-type="Primary" text="@L["Save"].Value" />
<abp-button button-type="Danger" icon="fa fa-trash" />
```
### Forms
```html
<abp-dynamic-form abp-model="Book" asp-page="/Books/CreateModal">
<abp-modal>
<abp-modal-header title="@L["NewBook"].Value" />
<abp-modal-body>
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Save | AbpModalButtons.Cancel)" />
</abp-modal>
</abp-dynamic-form>
```
### Tables
```html
<abp-table striped-rows="true" hoverable-rows="true">
<!-- content -->
</abp-table>
```
## Localization
```html
@* In Razor views/pages *@
<h1>@L["Books"]</h1>
@* With parameters *@
<p>@L["WelcomeMessage", Model.UserName]</p>
```
## JavaScript API
```javascript
// Localization
var text = abp.localization.getResource('BookStore')('Books');
// Authorization
if (abp.auth.isGranted('BookStore.Books.Create')) {
// Show create button
}
// Settings
var maxCount = abp.setting.get('BookStore.MaxItemCount');
// Ajax with automatic error handling
abp.ajax({
url: '/api/app/book',
type: 'POST',
data: JSON.stringify(bookData)
}).then(function(result) {
// Success
});
// Notifications
abp.notify.success('Book created successfully!');
abp.notify.error('An error occurred!');
// Confirmation
abp.message.confirm('Are you sure?').then(function(confirmed) {
if (confirmed) {
// User confirmed
}
});
```
## DataTables Integration
```javascript
var dataTable = $('#BooksTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
ajax: abp.libs.datatables.createAjax(bookService.getList),
columnDefs: [
{
title: l('Name'),
data: 'name'
},
{
title: l('Price'),
data: 'price',
render: function(data) {
return data.toFixed(2);
}
},
{
title: l('Actions'),
rowAction: {
items: [
{
text: l('Edit'),
visible: abp.auth.isGranted('BookStore.Books.Edit'),
action: function(data) {
editModal.open({ id: data.record.id });
}
},
{
text: l('Delete'),
visible: abp.auth.isGranted('BookStore.Books.Delete'),
confirmMessage: function(data) {
return l('BookDeletionConfirmationMessage', data.record.name);
},
action: function(data) {
bookService.delete(data.record.id).then(function() {
abp.notify.success(l('SuccessfullyDeleted'));
dataTable.ajax.reload();
});
}
}
]
}
}
]
})
);
```
## Modal Pages
**CreateModal.cshtml:**
```html
@page
@model CreateModalModel
<abp-dynamic-form abp-model="Book" asp-page="/Books/CreateModal">
<abp-modal>
<abp-modal-header title="@L["NewBook"].Value" />
<abp-modal-body>
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Save | AbpModalButtons.Cancel)" />
</abp-modal>
</abp-dynamic-form>
```
**CreateModal.cshtml.cs:**
```csharp
public class CreateModalModel : AbpPageModel
{
[BindProperty]
public CreateBookDto Book { get; set; }
private readonly IBookAppService _bookAppService;
public CreateModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.CreateAsync(Book);
return NoContent();
}
}
```
## Bundle & Minification
```csharp
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles.Configure(
StandardBundles.Styles.Global,
bundle => bundle.AddFiles("/styles/my-styles.css")
);
});
```

4
common.props

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

2
docs/en/deployment/configuring-production.md

@ -113,6 +113,6 @@ ABP uses .NET's standard [Logging services](../framework/fundamentals/logging.md
ABP's startup solution templates come with [Swagger UI](https://swagger.io/) pre-installed. Swagger is a pretty standard and useful tool to discover and test your HTTP APIs on a built-in UI that is embedded into your application or service. It is typically used in development environment, but you may want to enable it on staging or production environments too.
While you will always secure your HTTP APIs with other techniques (like the [Authorization](../framework/fundamentals/authorization.md) system), allowing malicious software and people to easily discover your HTTP API endpoint details can be considered as a security problem for some systems. So, be careful while taking the decision of enabling or disabling Swagger for the production environment.
While you will always secure your HTTP APIs with other techniques (like the [Authorization](../framework/fundamentals/authorization/index.md) system), allowing malicious software and people to easily discover your HTTP API endpoint details can be considered as a security problem for some systems. So, be careful while taking the decision of enabling or disabling Swagger for the production environment.
> You may also want to see the [ABP Swagger integration](../framework/api-development/swagger.md) document.

14
docs/en/docs-nav.json

@ -333,7 +333,7 @@
"path": "studio/solution-explorer.md"
},
{
"text": "Running Applications",
"text": "Solution Runner",
"path": "studio/running-applications.md"
},
{
@ -347,6 +347,10 @@
{
"text": "Working with ABP Suite",
"path": "studio/working-with-suite.md"
},
{
"text": "Custom Commands",
"path": "studio/custom-commands.md"
}
]
},
@ -458,12 +462,16 @@
"items": [
{
"text": "Overview",
"path": "framework/fundamentals/authorization.md",
"path": "framework/fundamentals/authorization/index.md",
"isIndex": true
},
{
"text": "Dynamic Claims",
"path": "framework/fundamentals/dynamic-claims.md"
},
{
"text": "Resource Based Authorization",
"path": "framework/fundamentals/authorization/resource-based-authorization.md"
}
]
},
@ -1284,7 +1292,7 @@
},
{
"text": "LeptonX Lite",
"path": "ui-themes/lepton-x-lite/mvc.md"
"path": "ui-themes/lepton-x-lite/asp-net-core.md"
},
{
"text": "LeptonX",

2
docs/en/framework/api-development/standard-apis/configuration.md

@ -9,7 +9,7 @@
ABP provides a pre-built and standard endpoint that contains some useful information about the application/service. Here, is the list of some fundamental information at this endpoint:
* Granted [policies](../../fundamentals/authorization.md) (permissions) for the current user.
* Granted [policies](../../fundamentals/authorization/index.md) (permissions) for the current user.
* [Setting](../../infrastructure/settings.md) values for the current user.
* Info about the [current user](../../infrastructure/current-user.md) (like id and user name).
* Info about the current [tenant](../../architecture/multi-tenancy) (like id and name).

2
docs/en/framework/architecture/domain-driven-design/application-services.md

@ -218,7 +218,7 @@ See the [validation document](../../fundamentals/validation.md) for more.
It's possible to use declarative and imperative authorization for application service methods.
See the [authorization document](../../fundamentals/authorization.md) for more.
See the [authorization document](../../fundamentals/authorization/index.md) for more.
## CRUD Application Services

23
docs/en/framework/architecture/domain-driven-design/entities.md

@ -135,6 +135,29 @@ if (book1.EntityEquals(book2)) //Check equality
}
```
### `IKeyedObject` Interface
ABP entities implement the `IKeyedObject` interface, which provides a way to get the entity's primary key as a string:
```csharp
public interface IKeyedObject
{
string? GetObjectKey();
}
```
The `GetObjectKey()` method returns a string representation of the entity's primary key. For entities with a single key (like `Entity<Guid>` or `Entity<int>`), it returns the `Id` property converted to a string. For entities with composite keys, it returns the keys combined with a comma separator.
This interface is particularly useful for scenarios where you need to identify an entity by its key in a type-agnostic way, such as:
* **Resource-based authorization**: When checking or granting permissions for specific entity instances
* **Caching**: When creating cache keys based on entity identifiers
* **Logging and auditing**: When recording entity identifiers in a consistent format
Since all ABP entities implement this interface through the `IEntity` interface, you can use `GetObjectKey()` on any entity without additional implementation.
> See the [Resource-Based Authorization](../../fundamentals/authorization/resource-based-authorization.md) documentation for a practical example of using `IKeyedObject` with the permission system.
## AggregateRoot Class
"*Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an order and its line-items, these will be separate objects, but it's useful to treat the order (together with its line items) as a single aggregate.*" (see the [full description](http://martinfowler.com/bliki/DDD_Aggregate.html))

69
docs/en/framework/architecture/microservices/index.md

@ -7,6 +7,16 @@
# Microservice Architecture
````json
//[doc-nav]
{
"Next": {
"Name": "Microservice Solution Template",
"Path": "solution-templates/microservice/index"
}
}
````
*"Microservices are a software development technique—a variant of the **service-oriented architecture** (SOA) architectural style that structures an application as a collection of **loosely coupled services**. In a microservices architecture, services are **fine-grained** and the protocols are **lightweight**. The benefit of decomposing an application into different smaller services is that it improves **modularity**. This makes the application easier to understand, develop, test, and become more resilient to architecture erosion. It **parallelizes development** by enabling small autonomous teams to **develop, deploy and scale** their respective services independently. It also allows the architecture of an individual service to emerge through **continuous refactoring**. Microservices-based architectures enable **continuous delivery and deployment**."*
— [Wikipedia](https://en.wikipedia.org/wiki/Microservices)
@ -24,11 +34,49 @@ One of the major goals of the ABP is to provide a convenient infrastructure to c
* Provides a [distributed event bus](../../infrastructure/event-bus) to communicate your services.
* Provides many other services to make your daily development easier.
## ABP Studio for Microservice Development
[ABP Studio](../../../studio/overview.md) is a comprehensive desktop application that significantly simplifies microservice solution development and management. It provides powerful tools specifically designed for distributed systems:
### Solution Runner
The [Solution Runner](../../../studio/running-applications.md) allows you to run all your microservices with a single click. You can create different profiles to organize services based on your team's needs. For example, `team-1` might only need to run the *Administration* and *Identity* services, while `team-2` works with *SaaS* and *Audit Logging* services. This approach saves resources and speeds up development by allowing each team to run only the services they need.
### Kubernetes Integration
The [Kubernetes Integration](../../../studio/kubernetes.md) panel enables you to deploy your microservices to a Kubernetes cluster and manage them directly from ABP Studio. Key features include:
* **Deploy to Kubernetes**: Build Docker images and install Helm charts with a few clicks.
* **Intercept Services**: Debug and develop specific services locally while the rest of the system runs in Kubernetes. This eliminates the need to run all microservices on your local machine.
* **Redeploy Charts**: Quickly redeploy individual services after making changes.
* **Connect to Cluster Resources**: Access databases, message queues, and other infrastructure services running in the cluster.
### Application Monitoring
The [Application Monitoring](../../../studio/monitoring-applications.md) area provides a centralized view of all your running microservices:
* **HTTP Requests**: View all HTTP requests across services with detailed information including headers, payloads, and response times.
* **Distributed Events**: Monitor all distributed events sent and received by your services, making it easy to debug inter-service communication.
* **Exceptions**: Track exceptions thrown by any service in real-time.
* **Logs**: Access logs from all services in a single place with filtering capabilities.
* **Built-in Browser**: Browse and test your APIs without leaving ABP Studio.
### Creating New Microservices
ABP Studio's [Solution Explorer](../../../studio/solution-explorer.md) makes it easy to [add new microservices to your solution](../../../solution-templates/microservice/adding-new-microservices). Right-click on the `services` folder and select *Add* -> *New Module* -> *Microservice*. ABP Studio will:
* Create the microservice with proper project structure.
* Configure database connections and migrations.
* Set up authentication and authorization.
* Integrate with API gateways.
* Configure distributed event bus connections.
* Add the service to Kubernetes Helm charts.
## Microservice for New Applications
One common advise to start a new solution is **always to start with a monolith**, keep it modular and split into microservices once the monolith becomes a problem. This makes your progress fast in the beginning especially if your team is small and you don't want to deal with challenges of the microservice architecture.
One common advice to start a new solution is **always to start with a monolith**, keep it modular and split into microservices once the monolith becomes a problem. This makes your progress fast in the beginning especially if your team is small and you don't want to deal with challenges of the microservice architecture.
However, developing such a well-modular application can be a problem since it is **hard to keep modules isolated** from each other as you would do it for microservices (see [Stefan Tilkov's article](https://martinfowler.com/articles/dont-start-monolith.html) about that). Microservice architecture naturally forces you to develop well isolated services, but in a modular monolithic application it's easy to tight couple modules to each other and design **weak module boundaries** and API contracts.
However, developing such a well-modular application can be a problem since it is **hard to keep modules isolated** from each other as you would do it for microservices (see [Stefan Tilkov's article](https://martinfowler.com/articles/dont-start-monolith.html) about that). Microservice architecture naturally forces you to develop well isolated services, but in a modular monolithic application it's easy to tightly couple modules to each other and design **weak module boundaries** and API contracts.
ABP can help you in that point by offering a **microservice-compatible, strict module architecture** where your module is split into multiple layers/projects and developed in its own VS solution completely isolated and independent from other modules. Such a developed module is a natural microservice yet it can be easily plugged-in a monolithic application. See the [module development best practice guide](../best-practices) that offers a **microservice-first module design**. All [standard ABP modules](https://github.com/abpframework/abp/tree/master/modules) are developed based on this guide. So, you can use these modules by embedding into your monolithic solution or deploy them separately and use via remote APIs. They can share a single database or can have their own database based on your simple configuration.
@ -37,3 +85,20 @@ ABP can help you in that point by offering a **microservice-compatible, strict m
ABP provides a pre-architected and production-ready microservice solution template that includes multiple services, API gateways and applications well integrated with each other. This template helps you quickly start building distributed systems with common microservice patterns.
See the [Microservice Solution Template](../../../solution-templates/microservice/index.md) documentation for details.
## Tutorials
For a hands-on experience, follow the [Microservice Development Tutorial](../../../tutorials/microservice/index.md) that guides you through:
* Creating the initial microservice solution
* Adding new microservices (Catalog and Ordering services)
* Building CRUD functionality
* Implementing HTTP API calls between services
* Using distributed events for asynchronous communication
## See Also
* [Get Started: Microservice Solution](../../../get-started/microservice.md)
* [Microservice Solution Template](../../../solution-templates/microservice/index.md)
* [Microservice Development Tutorial](../../../tutorials/microservice/index.md)
* [ABP Studio Overview](../../../studio/overview.md)

2
docs/en/framework/architecture/modularity/extending/customizing-application-modules-guide.md

@ -112,4 +112,4 @@ Also, see the following documents:
* See [the localization document](../../../fundamentals/localization.md) to learn how to extend existing localization resources.
* See [the settings document](../../../infrastructure/settings.md) to learn how to change setting definitions of a depended module.
* See [the authorization document](../../../fundamentals/authorization.md) to learn how to change permission definitions of a depended module.
* See [the authorization document](../../../fundamentals/authorization/index.md) to learn how to change permission definitions of a depended module.

97
docs/en/framework/fundamentals/authorization.md → docs/en/framework/fundamentals/authorization/index.md

@ -9,13 +9,15 @@
Authorization is used to check if a user is allowed to perform some specific operations in the application.
ABP extends [ASP.NET Core Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing authorization system to be usable in the **[application services](../architecture/domain-driven-design/application-services.md)** too.
ABP extends [ASP.NET Core Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing authorization system to be usable in the **[application services](../../architecture/domain-driven-design/application-services.md)** too.
So, all the ASP.NET Core authorization features and the documentation are valid in an ABP based application. This document focuses on the features that are added on top of ASP.NET Core authorization features.
ABP supports two types of permissions: **Standard permissions** apply globally (e.g., "can create documents"), while **resource-based permissions** target specific instances (e.g., "can edit Document #123"). This document covers standard permissions; see [Resource-Based Authorization](./resource-based-authorization.md) for fine-grained, per-resource access control.
## Authorize Attribute
ASP.NET Core defines the [**Authorize**](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/simple) attribute that can be used for an action, a controller or a page. ABP allows you to use the same attribute for an [application service](../architecture/domain-driven-design/application-services.md) too.
ASP.NET Core defines the [**Authorize**](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/simple) attribute that can be used for an action, a controller or a page. ABP allows you to use the same attribute for an [application service](../../architecture/domain-driven-design/application-services.md) too.
Example:
@ -87,9 +89,11 @@ namespace Acme.BookStore.Permissions
> ABP automatically discovers this class. No additional configuration required!
> You typically define this class inside the `Application.Contracts` project of your [application](../../solution-templates/layered-web-application). The startup template already comes with an empty class named *YourProjectNamePermissionDefinitionProvider* that you can start with.
> You typically define this class inside the `Application.Contracts` project of your [application](../../../solution-templates/layered-web-application/index.md). The startup template already comes with an empty class named *YourProjectNamePermissionDefinitionProvider* that you can start with.
In the `Define` method, you first need to add a **permission group** (or get an existing group), then add **permissions** to this group using the `AddPermission` method.
In the `Define` method, you first need to add a **permission group** or get an existing group then add **permissions** to this group.
> For resource-specific fine-grained permissions, use the `AddResourcePermission` method instead. See [Resource-Based Authorization](./resource-based-authorization.md) for details.
When you define a permission, it becomes usable in the ASP.NET Core authorization system as a **policy** name. It also becomes visible in the UI. See permissions dialog for a role:
@ -100,6 +104,8 @@ When you define a permission, it becomes usable in the ASP.NET Core authorizatio
When you save the dialog, it is saved to the database and used in the authorization system.
> **Note:** Only standard (global) permissions are shown in this dialog. Resource-based permissions are managed through the [Resource Permission Management Dialog](../../../modules/permission-management.md#resource-permission-management-dialog) on individual resource instances.
> The screen above is available when you have installed the identity module, which is basically used for user and role management. Startup templates come with the identity module pre-installed.
#### Localizing the Permission Name
@ -125,15 +131,15 @@ Then you can define texts for "BookStore" and "Permission:BookStore_Author_Creat
"Permission:BookStore_Author_Create": "Creating a new author"
```
> For more information, see the [localization document](./localization.md) on the localization system.
> For more information, see the [localization document](../localization.md) on the localization system.
The localized UI will be as seen below:
![authorization-new-permission-ui-localized](../../images/authorization-new-permission-ui-localized.png)
![authorization-new-permission-ui-localized](../../../images/authorization-new-permission-ui-localized.png)
#### Multi-Tenancy
ABP supports [multi-tenancy](../architecture/multi-tenancy) as a first class citizen. You can define multi-tenancy side option while defining a new permission. It gets one of the three values defined below:
ABP supports [multi-tenancy](../../architecture/multi-tenancy/index.md) as a first class citizen. You can define multi-tenancy side option while defining a new permission. It gets one of the three values defined below:
- **Host**: The permission is available only for the host side.
- **Tenant**: The permission is available only for the tenant side.
@ -180,7 +186,7 @@ authorManagement.AddChild("Author_Management_Delete_Books");
The result on the UI is shown below (you probably want to localize permissions for your application):
![authorization-new-permission-ui-hierarcy](../../images/authorization-new-permission-ui-hierarcy.png)
![authorization-new-permission-ui-hierarcy](../../../images/authorization-new-permission-ui-hierarcy.png)
For the example code, it is assumed that a role/user with "Author_Management" permission granted may have additional permissions. Then a typical application service that checks permissions can be defined as shown below:
@ -229,7 +235,7 @@ See [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/se
### Changing Permission Definitions of a Depended Module
A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](../architecture/modularity/basics.md)) and change their definitions.
A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](../../architecture/modularity/basics.md)) and change their definitions.
Example:
@ -247,12 +253,12 @@ When you write this code inside your permission definition provider, it finds th
You may want to disable a permission based on a condition. Disabled permissions are not visible on the UI and always returns `prohibited` when you check them. There are two built-in conditional dependencies for a permission definition;
* A permission can be automatically disabled if a [Feature](../infrastructure/features.md) was disabled.
* A permission can be automatically disabled if a [Global Feature](../infrastructure/global-features.md) was disabled.
* A permission can be automatically disabled if a [Feature](../../infrastructure/features.md) was disabled.
* A permission can be automatically disabled if a [Global Feature](../../infrastructure/global-features.md) was disabled.
In addition, you can create your custom extensions.
#### Depending on a Features
#### Depending on Features
Use the `RequireFeatures` extension method on your permission definition to make the permission available only if a given feature is enabled:
@ -261,7 +267,7 @@ myGroup.AddPermission("Book_Creation")
.RequireFeatures("BookManagement");
````
#### Depending on a Global Feature
#### Depending on Global Features
Use the `RequireGlobalFeatures` extension method on your permission definition to make the permission available only if a given feature is enabled:
@ -272,13 +278,13 @@ myGroup.AddPermission("Book_Creation")
#### Creating a Custom Permission Dependency
`PermissionDefinition` supports state check, Please refer to [Simple State Checker's documentation](../infrastructure/simple-state-checker.md)
`PermissionDefinition` supports state check, please refer to [Simple State Checker's documentation](../../infrastructure/simple-state-checker.md)
## IAuthorizationService
ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject, you can use it in your code to conditionally control the authorization.
ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject it, you can use it in your code to conditionally control the authorization.
Example:
**Example:**
```csharp
public async Task CreateAsync(CreateAuthorDto input)
@ -295,7 +301,7 @@ public async Task CreateAsync(CreateAuthorDto input)
}
```
> `AuthorizationService` is available as a property when you derive from ABP's `ApplicationService` base class. Since it is widely used in application services, `ApplicationService` pre-injects it for you. Otherwise, you can directly [inject](./dependency-injection.md) it into your class.
> `AuthorizationService` is available as a property when you derive from ABP's `ApplicationService` base class. Since it is widely used in application services, `ApplicationService` pre-injects it for you. Otherwise, you can directly [inject](../dependency-injection.md) it into your class.
Since this is a typical code block, ABP provides extension methods to simplify it.
@ -320,15 +326,15 @@ public async Task CreateAsync(CreateAuthorDto input)
See the following documents to learn how to re-use the authorization system on the client side:
* [ASP.NET Core MVC / Razor Pages UI: Authorization](../ui/mvc-razor-pages/javascript-api/auth.md)
* [Angular UI Authorization](../ui/angular/authorization.md)
* [Blazor UI Authorization](../ui/blazor/authorization.md)
* [ASP.NET Core MVC / Razor Pages UI: Authorization](../../ui/mvc-razor-pages/javascript-api/auth.md)
* [Angular UI Authorization](../../ui/angular/authorization.md)
* [Blazor UI Authorization](../../ui/blazor/authorization.md)
## Permission Management
Permission management is normally done by an admin user using the permission management modal:
![authorization-new-permission-ui-localized](../../images/authorization-new-permission-ui-localized.png)
![authorization-new-permission-ui-localized](../../../images/authorization-new-permission-ui-localized.png)
If you need to manage permissions by code, inject the `IPermissionManager` and use as shown below:
@ -356,13 +362,13 @@ public class MyService : ITransientDependency
`SetForUserAsync` sets the value (true/false) for a permission of a user. There are more extension methods like `SetForRoleAsync` and `SetForClientAsync`.
`IPermissionManager` is defined by the permission management module. See the [permission management module documentation](../../modules/permission-management.md) for more information.
`IPermissionManager` is defined by the Permission Management module. For resource-based permissions, use `IResourcePermissionManager` instead. See the [Permission Management Module documentation](../../../modules/permission-management.md) for more information.
## Advanced Topics
### Permission Value Providers
Permission checking system is extensible. Any class derived from `PermissionValueProvider` (or implements `IPermissionValueProvider`) can contribute to the permission check. There are three pre-defined value providers:
The permission checking system is extensible. Any class derived from `PermissionValueProvider` (or implements `IPermissionValueProvider`) can contribute to the permission check. There are three pre-defined value providers:
- `UserPermissionValueProvider` checks if the current user is granted for the given permission. It gets user id from the current claims. User claim name is defined with the `AbpClaimTypes.UserId` static property.
- `RolePermissionValueProvider` checks if any of the roles of the current user is granted for the given permission. It gets role names from the current claims. Role claims name is defined with the `AbpClaimTypes.Role` static property.
@ -412,15 +418,35 @@ Configure<AbpPermissionOptions>(options =>
});
```
### Resource Permission Value Providers
Similar to standard permission value providers, you can extend the resource permission checking system by creating custom **resource permission value providers**. ABP provides two built-in resource permission value providers:
* `UserResourcePermissionValueProvider`: Checks permissions granted directly to users for a specific resource.
* `RoleResourcePermissionValueProvider`: Checks permissions granted to roles for a specific resource.
You can create custom providers by implementing `IResourcePermissionValueProvider` or inheriting from `ResourcePermissionValueProvider`. Register them using:
```csharp
Configure<AbpPermissionOptions>(options =>
{
options.ResourceValueProviders.Add<YourCustomResourcePermissionValueProvider>();
});
```
> See the [Permission Management Module](../../../modules/permission-management.md#resource-permission-value-providers) documentation for detailed examples.
### Permission Store
`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. The Permission Management module implements it and pre-installed in the application startup template. See the [permission management module documentation](../../modules/permission-management.md) for more information
`IPermissionStore` is the interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. The Permission Management module implements it and is pre-installed in the application startup template. See the [Permission Management Module documentation](../../../modules/permission-management.md) for more information.
For resource-based permissions, `IResourcePermissionStore` serves the same purpose, storing and retrieving permissions for specific resource instances.
### AlwaysAllowAuthorizationService
`AlwaysAllowAuthorizationService` is a class that is used to bypass the authorization service. It is generally used in integration tests where you may want to disable the authorization system.
Use `IServiceCollection.AddAlwaysAllowAuthorization()` extension method to register the `AlwaysAllowAuthorizationService` to the [dependency injection](./dependency-injection.md) system:
Use `IServiceCollection.AddAlwaysAllowAuthorization()` extension method to register the `AlwaysAllowAuthorizationService` to the [dependency injection](../../dependency-injection.md) system:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
@ -466,11 +492,24 @@ public static class CurrentUserExtensions
}
```
> If you use OpenIddict please see [Updating Claims in Access Token and ID Token](../../modules/openiddict#updating-claims-in-access_token-and-id_token).
> If you use OpenIddict please see [Updating Claims in Access Token and ID Token](../../../modules/openiddict#updating-claims-in-access_token-and-id_token).
## Resource-Based Authorization
While this document covers standard (global) permissions, ABP also supports **resource-based authorization** for fine-grained access control on specific resource instances. Resource-based authorization allows you to grant permissions for a specific document, project, or any other entity rather than granting a permission for all resources of that type.
**Example scenarios:**
* Allow users to edit **only their own** blog posts or documents
* Grant access to **specific projects** based on team membership
* Implement document sharing where **different users have different access levels** to the same document
> See the [Resource-Based Authorization](./resource-based-authorization.md) document for implementation details.
## See Also
* [Permission Management Module](../../modules/permission-management.md)
* [ASP.NET Core MVC / Razor Pages JavaScript Auth API](../ui/mvc-razor-pages/javascript-api/auth.md)
* [Permission Management in Angular UI](../ui/angular/Permission-Management.md)
* [Resource-Based Authorization](./resource-based-authorization.md)
* [Permission Management Module](../../../modules/permission-management.md)
* [ASP.NET Core MVC / Razor Pages JavaScript Auth API](../../ui/mvc-razor-pages/javascript-api/auth.md)
* [Permission Management in Angular UI](../../ui/angular/Permission-Management.md)
* [Video tutorial](https://abp.io/video-courses/essentials/authorization)

241
docs/en/framework/fundamentals/authorization/resource-based-authorization.md

@ -0,0 +1,241 @@
```json
//[doc-seo]
{
"Description": "Learn how to implement resource-based authorization in ABP Framework for fine-grained access control on specific resource instances like documents, projects, or any entity."
}
```
# Resource-Based Authorization
**Resource-Based Authorization** is a powerful feature that enables fine-grained access control based on specific resource instances. While the standard [authorization system](./index.md) grants permissions at a general level (e.g., "can edit documents"), resource-based authorization allows you to grant permissions for a **specific** document, project, or any other entity rather than granting a permission for all of them.
## When to Use Resource-Based Authorization?
Consider resource-based authorization when you need to:
* Allow users to edit **only their own blog posts or documents**
* Grant access to **specific projects** based on team membership
* Implement document sharing **where different users have different access levels to the same document**
* Control access to resources based on ownership or custom sharing rules
**Example Scenarios:**
Imagine a document management system where:
- User A can view and edit Document 1
- User B can only view Document 1
- User A has no access to Document 2
- User C can manage permissions for Document 2
This level of granular control is what resource-based authorization provides.
## Usage
Implementing resource-based authorization involves three main steps:
1. **Define** resource permissions in your `PermissionDefinitionProvider`
2. **Check** permissions using `IResourcePermissionChecker`
3. **Manage** permissions via UI or using `IResourcePermissionManager` for programmatic usages
### Defining Resource Permissions
Define resource permissions in your `PermissionDefinitionProvider` class using the `AddResourcePermission` method:
```csharp
namespace Acme.BookStore.Permissions;
public static class BookStorePermissions
{
public const string GroupName = "BookStore";
public static class Books
{
public const string Default = GroupName + ".Books";
public const string ManagePermissions = Default + ".ManagePermissions";
public static class Resources
{
public const string Name = "Acme.BookStore.Books.Book";
public const string View = Name + ".View";
public const string Edit = Name + ".Edit";
public const string Delete = Name + ".Delete";
}
}
}
```
```csharp
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
namespace Acme.BookStore.Permissions
{
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup("BookStore");
// Standard permissions
myGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books"));
// Permission to manage resource permissions (required)
myGroup.AddPermission(BookStorePermissions.Books.ManagePermissions, L("Permission:Books:ManagePermissions"));
// Resource-based permissions
context.AddResourcePermission(
name: BookStorePermissions.Books.Resources.View,
resourceName: BookStorePermissions.Books.Resources.Name,
managementPermissionName: BookStorePermissions.Books.ManagePermissions,
displayName: L("Permission:Books:View")
);
context.AddResourcePermission(
name: BookStorePermissions.Books.Resources.Edit,
resourceName: BookStorePermissions.Books.Resources.Name,
managementPermissionName: BookStorePermissions.Books.ManagePermissions,
displayName: L("Permission:Books:Edit")
);
context.AddResourcePermission(
name: BookStorePermissions.Books.Resources.Delete,
resourceName: BookStorePermissions.Books.Resources.Name,
managementPermissionName: BookStorePermissions.Books.ManagePermissions,
displayName: L("Permission:Books:Delete"),
multiTenancySide: MultiTenancySides.Host
);
}
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<BookStoreResource>(name);
}
}
```
The `AddResourcePermission` method requires the following parameters:
* `name`: A unique name for the resource permission.
* `resourceName`: An identifier for the resource type. This is typically the full name of the entity class (e.g., `Acme.BookStore.Books.Book`).
* `managementPermissionName`: A standard permission that controls who can manage resource permissions. Users with this permission can grant/revoke resource permissions for specific resources.
* `displayName`: (Optional) A localized display name shown in the UI.
* `multiTenancySide`: (Optional) Specifies on which side of a multi-tenant application this permission can be used. Accepts `MultiTenancySides.Host` (only for the host side), `MultiTenancySides.Tenant` (only for tenants), or `MultiTenancySides.Both` (default, available on both sides).
### Checking Resource Permissions
Use the `IAuthorizationService` service to check if a user/role/client has a specific permission for a resource:
```csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization.Permissions.Resources;
namespace Acme.BookStore.Books
{
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IBookRepository _bookRepository;
public BookAppService(IBookRepository bookRepository)
{
_bookRepository = bookRepository;
}
public virtual async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
// Check if the current user can view this specific book
var isGranted = await AuthorizationService.IsGrantedAsync(book, BookStorePermissions.Books.Resources.View); // AuthorizationService is a property of the ApplicationService class and will be automatically injected.
if (!isGranted)
{
throw new AbpAuthorizationException("You don't have permission to view this book.");
}
return ObjectMapper.Map<Book, BookDto>(book);
}
public virtual async Task UpdateAsync(Guid id, UpdateBookDto input)
{
var book = await _bookRepository.GetAsync(id);
// Check if the current user can edit this specific book
var isGranted = await AuthorizationService.IsGrantedAsync(book, BookStorePermissions.Books.Resources.Edit); // AuthorizationService is a property of the ApplicationService class and will be automatically injected.
if (!isGranted)
{
throw new AbpAuthorizationException("You don't have permission to edit this book.");
}
book.Title = input.Title;
book.Content = input.Content;
await _bookRepository.UpdateAsync(book);
}
}
}
```
In this example, the `BookAppService` uses `IAuthorizationService` to check if the current user has the required permission for a specific book before performing the operation. The method takes the `Book` entity object and resource permission name as parameters.
#### IKeyedObject
The `IAuthorizationService` internally uses `IResourcePermissionChecker` to check resource permissions, and gets the resource key by calling the `GetObjectKey()` method of the `IKeyedObject` interface. All ABP entities implement the `IKeyedObject` interface, so you can directly pass entity objects to the `IsGrantedAsync` method.
> See the [Entities documentation](../../architecture/domain-driven-design/entities.md) for more information about the `IKeyedObject` interface.
#### IResourcePermissionChecker
You can also directly use the `IResourcePermissionChecker` service to check resource permissions which provides more advanced features, such as checking multiple permissions at once:
> You have to pass the resource key (obtained via `GetObjectKey()`) explicitly when using `IResourcePermissionChecker`.
```csharp
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IBookRepository _bookRepository;
private readonly IResourcePermissionChecker _resourcePermissionChecker;
public BookAppService(IBookRepository bookRepository, IResourcePermissionChecker resourcePermissionChecker)
{
_bookRepository = bookRepository;
_resourcePermissionChecker = resourcePermissionChecker;
}
public async Task<BookPermissionsDto> GetPermissionsAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
var result = await _resourcePermissionChecker.IsGrantedAsync(new[]
{
BookStorePermissions.Books.Resources.View,
BookStorePermissions.Books.Resources.Edit,
BookStorePermissions.Books.Resources.Delete
},
BookStorePermissions.Books.Resources.Name,
book.GetObjectKey()!);
return new BookPermissionsDto
{
CanView = result.Result[BookStorePermissions.Books.Resources.View] == PermissionGrantResult.Granted,
CanEdit = result.Result[BookStorePermissions.Books.Resources.Edit] == PermissionGrantResult.Granted,
CanDelete = result.Result[BookStorePermissions.Books.Resources.Delete] == PermissionGrantResult.Granted
};
}
}
```
### Managing Resource Permissions
Once you have defined resource permissions, you need a way to grant or revoke them for specific users, roles, or clients. The [Permission Management Module](../../../modules/permission-management.md) provides the infrastructure for managing resource permissions:
- **UI Components**: Built-in modal dialogs for managing resource permissions on all supported UI frameworks (MVC/Razor Pages, Blazor, and Angular). These components allow administrators to grant or revoke permissions for users and roles on specific resource instances through a user-friendly interface.
- **`IResourcePermissionManager` Service**: A service for programmatically granting, revoking, and querying resource permissions at runtime. This is useful for scenarios like automatically granting permissions when a resource is created, implementing sharing functionality, or integrating with external systems.
> See the [Permission Management Module](../../../modules/permission-management.md#resource-permission-management-dialog) documentation for detailed information on using the UI components and the `IResourcePermissionManager` service.
## See Also
* [Authorization](./index.md)
* [Permission Management Module](../../../modules/permission-management.md)
* [Entities](../../architecture/domain-driven-design/entities.md)

2
docs/en/framework/fundamentals/dynamic-claims.md

@ -94,6 +94,6 @@ If you want to add your own dynamic claims contributor, you can create a class t
## See Also
* [Authorization](./authorization.md)
* [Authorization](./authorization/index.md)
* [Claims-based authorization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims)
* [Mapping, customizing, and transforming claims in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims)

2
docs/en/framework/fundamentals/exception-handling.md

@ -322,7 +322,7 @@ The `context` object contains necessary information about the exception occurred
Some exception types are automatically thrown by the framework:
- `AbpAuthorizationException` is thrown if the current user has no permission to perform the requested operation. See [authorization](./authorization.md) for more.
- `AbpAuthorizationException` is thrown if the current user has no permission to perform the requested operation. See [authorization](./authorization/index.md) for more.
- `AbpValidationException` is thrown if the input of the current request is not valid. See [validation](./validation.md) for more.
- `EntityNotFoundException` is thrown if the requested entity is not available. This is mostly thrown by [repositories](../architecture/domain-driven-design/repositories.md).

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

@ -10,7 +10,7 @@
The following documents explains the fundamental building blocks to create ABP solutions:
* [Application Startup](./application-startup.md)
* [Authorization](./authorization.md)
* [Authorization](./authorization/index.md)
* [Caching](./caching.md)
* [Configuration](./configuration.md)
* [Connection Strings](./connection-strings.md)

13
docs/en/framework/infrastructure/artificial-intelligence/microsoft-extensions-ai.md

@ -1,3 +1,10 @@
```json
//[doc-seo]
{
"Description": "Explore how to integrate AI services into your ABP Framework applications using the Microsoft.Extensions.AI library for seamless functionality."
}
```
# Microsoft.Extensions.AI
[Microsoft.Extensions.AI](https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai) is a library that provides a unified API for integrating AI services. It is a part of the Microsoft AI Extensions Library. It is used to integrate AI services into your application. This documentation is about the usage of this library with ABP Framework. Make sure you have read the [Artificial Intelligence](./index.md) documentation before reading this documentation.
@ -64,7 +71,7 @@ public class CommentSummarization
> [!NOTE]
> If you don't specify the workspace name, the full name of the class will be used as the workspace name.
You can resolve generic versions of `IChatClient` and `IChatClientAccessor` services for a specific workspace as generic arguments. If Chat Client is not configured for a workspace, you will get `null` from the accessor services. You should check the accessor before using it. This applies only for specified workspaces. Another workspace may have a configured Chat Client.
You can resolve generic versions of `IChatClient` and `IChatClientAccessor` services for a specific workspace as generic arguments. If Chat Client is not configured for a workspace, the default workspace's chat client is returned. Only if both the workspace-specific and default chat clients are not configured will you get `null` from the accessor services. You should check the accessor before using it. This applies only for specified workspaces. Another workspace may have a configured Chat Client.
`IChatClient<TWorkSpace>` or `IChatClientAccessor<TWorkSpace>` can be resolved to access a specific workspace's chat client. This is a typed chat client and can be configured separately from the default chat client.
@ -92,7 +99,7 @@ Example of resolving a typed chat client accessor:
public class MyService
{
private readonly IChatClientAccessor<CommentSummarization> _chatClientAccessor;
}
public async Task<string> GetResponseAsync(string prompt)
{
var chatClient = _chatClientAccessor.ChatClient;
@ -174,4 +181,4 @@ public class MyProjectModule : AbpModule
- [Usage of Agent Framework](./microsoft-agent-framework.md)
- [Usage of Semantic Kernel](./microsoft-semantic-kernel.md)
- [AI Samples for .NET](https://learn.microsoft.com/en-us/samples/dotnet/ai-samples/ai-samples/)
- [AI Samples for .NET](https://learn.microsoft.com/en-us/samples/dotnet/ai-samples/ai-samples/)

2
docs/en/framework/infrastructure/background-jobs/hangfire.md

@ -149,7 +149,7 @@ namespace MyProject
Hangfire Dashboard provides information about your background jobs, including method names and serialized arguments as well as gives you an opportunity to manage them by performing different actions – retry, delete, trigger, etc. So it is important to restrict access to the Dashboard.
To make it secure by default, only local requests are allowed, however you can change this by following the [official documentation](http://docs.hangfire.io/en/latest/configuration/using-dashboard.html) of Hangfire.
You can integrate the Hangfire dashboard to [ABP authorization system](../../fundamentals/authorization.md) using the **AbpHangfireAuthorizationFilter**
You can integrate the Hangfire dashboard to [ABP authorization system](../../fundamentals/authorization/index.md) using the **AbpHangfireAuthorizationFilter**
class. This class is defined in the `Volo.Abp.Hangfire` package. The following example, checks if the current user is logged in to the application:
```csharp

2
docs/en/framework/infrastructure/interceptors.md

@ -42,7 +42,7 @@ Automatically begins and commits/rolls back a database transaction when entering
Input DTOs are automatically validated against data annotation attributes and custom validation rules before executing the service logic, providing consistent validation behavior across all services.
### [Authorization](../fundamentals/authorization.md)
### [Authorization](../fundamentals/authorization/index.md)
Checks user permissions before allowing the execution of application service methods, ensuring security policies are enforced consistently.

29
docs/en/framework/infrastructure/object-to-object-mapping.md

@ -313,6 +313,34 @@ It is suggested to use the `MapExtraPropertiesAttribute` attribute if both class
Mapperly requires that properties of both source and destination objects have `setter` methods. Otherwise, the property will be ignored. You can use `protected set` or `private set` to control the visibility of the `setter` method, but each property must have a `setter` method.
### Nullable Reference Types
Mapperly respects C# nullable reference types (NRT). If your project enables NRT via `<Nullable>enable</Nullable>` in the project file, Mapperly will treat reference type properties as **non-nullable by default**.
That means:
- If a property can be `null`, declare it as nullable so Mapperly (and the compiler) understands it can be missing.
- If you declare a property as non-nullable, Mapperly assumes it is not `null`.
Otherwise, the generated mapping code may throw runtime exceptions (e.g., `NullReferenceException`) if a value is actually `null` during the mapping process.
Example:
````xml
<!-- .csproj -->
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
````
````csharp
public class PersonDto
{
public Country? Country { get; set; } // Nullable (can be null)
public City City { get; set; } = default!; // Non-nullable (cannot be null)
}
````
### Deep Cloning
By default, Mapperly does not create deep copies of objects to improve performance. If an object can be directly assigned to the target, it will do so (e.g., if the source and target type are both `List<T>`, the list and its entries will not be cloned). To create deep copies, set the `UseDeepCloning` property on the `MapperAttribute` to `true`.
@ -505,6 +533,7 @@ Each solution has its own advantages:
Choose the approach that best aligns with your application's architecture and maintainability requirements.
### More Mapperly Features
Most of Mapperly's features such as `Ignore` can be configured through its attributes. See the [Mapperly documentation](https://mapperly.riok.app/docs/intro/) for more details.

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

@ -25,26 +25,21 @@ The ABP Checkbox Component is a reusable form input component for the checkbox t
# Usage
The ABP Checkbox component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, there's no need to import it again. If not, then first import it as shown below:
The ABP Checkbox component (`AbpCheckboxComponent`) is a standalone component. You can import it directly in your component:
```ts
// my-feature.module.ts
import { ThemeSharedModule } from "@abp/ng.theme.shared";
import { CheckboxDemoComponent } from "./CheckboxDemoComponent.component";
@NgModule({
imports: [
ThemeSharedModule,
// ...
],
declarations: [CheckboxDemoComponent],
// ...
import { Component } from "@angular/core";
import { AbpCheckboxComponent } from "@abp/ng.theme.shared";
@Component({
selector: 'app-checkbox-demo',
imports: [AbpCheckboxComponent],
templateUrl: './checkbox-demo.component.html',
})
export class MyFeatureModule {}
export class CheckboxDemoComponent {}
```
Then, the `abp-checkbox` component can be used. See the example below:
Then, the `abp-checkbox` component can be used in your template. See the example below:
```html
<div class="form-check">

22
docs/en/framework/ui/angular/form-input-component.md

@ -22,23 +22,21 @@ The ABP FormInput Component is a reusable form input component for the text type
# Usage
The ABP FormInput component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, there's no need to import it again. If not, then first import it as shown below:
The ABP FormInput component (`AbpFormInputComponent`) is a standalone component. You can import it directly in your component:
```ts
import { ThemeSharedModule } from "@abp/ng.theme.shared";
import { FormInputDemoComponent } from "./FomrInputDemoComponent.component";
@NgModule({
imports: [
ThemeSharedModule,
// ...
],
declarations: [FormInputDemoComponent],
import { Component } from "@angular/core";
import { AbpFormInputComponent } from "@abp/ng.theme.shared";
@Component({
selector: 'app-form-input-demo',
imports: [AbpFormInputComponent],
templateUrl: './form-input-demo.component.html',
})
export class MyFeatureModule {}
export class FormInputDemoComponent {}
```
Then, the `abp-form-input` component can be used. See the example below:
Then, the `abp-form-input` component can be used in your template. See the example below:
```html
<div class="row">

1
docs/en/framework/ui/angular/form-validation.md

@ -303,7 +303,6 @@ import { NgxValidateCoreModule } from '@ngx-validate/core';
@Component({
selector: 'app-nested-form',
templateUrl: './nested-form.component.html',
standalone: true,
imports: [NgxValidateCoreModule],
})
export class NestedFormComponent implements OnInit {

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

@ -9,38 +9,45 @@
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.
Create a new module called `MyRolesModule`:
```bash
yarn ng generate module my-roles --module app
```
Create a new component called `MyRolesComponent`:
```bash
yarn ng generate component my-roles/my-roles --flat --export
yarn ng generate component my-roles/my-roles --flat
```
Open the generated `src/app/my-roles/my-roles.component.ts` file and replace its content with the following:
```js
import { Component, Injector, inject, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { ListService, PagedAndSortedResultRequestDto, PagedResultDto } from '@abp/ng.core';
import { ListService, PagedAndSortedResultRequestDto, PagedResultDto, LocalizationPipe } from '@abp/ng.core';
import { eIdentityComponents, RolesComponent } from '@abp/ng.identity';
import { IdentityRoleDto, IdentityRoleService } from '@abp/ng.identity/proxy';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared';
import { ePermissionManagementComponents, PermissionManagementComponent } from '@abp/ng.permission-management';
import { Confirmation, ConfirmationService, ModalComponent, ButtonComponent } from '@abp/ng.theme.shared';
import {
EXTENSIONS_IDENTIFIER,
FormPropData,
generateFormFromProps
generateFormFromProps,
PageToolbarComponent,
ExtensibleTableComponent,
ExtensibleFormComponent
} from '@abp/ng.components/extensible';
@Component({
selector: 'app-my-roles',
imports: [
ReactiveFormsModule,
LocalizationPipe,
ModalComponent,
ButtonComponent,
PageToolbarComponent,
ExtensibleTableComponent,
ExtensibleFormComponent,
PermissionManagementComponent
],
templateUrl: './my-roles.component.html',
providers: [
ListService,
@ -236,25 +243,12 @@ Open the generated `src/app/my-role/my-role.component.html` file and replace its
We have added the `abp-page-toolbar`, `abp-extensible-table`, and `abp-extensible-form` extension components to template of the `MyRolesComponent`.
You should import the required modules for the `MyRolesComponent` to `MyRolesModule`. Open the `src/my-roles/my-roles.module.ts` file and replace the content with the following:
```js
import { ExtensibleModule } from '@abp/ng.components/extensible';
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { MyRolesComponent } from './my-roles.component';
import { PermissionManagementModule } from '@abp/ng.permission-management';
@NgModule({
declarations: [MyRolesComponent],
imports: [SharedModule, ExtensibleModule, PermissionManagementModule],
exports: [MyRolesComponent],
})
export class MyRolesModule {}
```
- `ExtensionsModule` imported to be able to use the extension components in your component.
- `PermissionManagementModule` imported to be able to use the `abp-permission-*management` in your component.
Since we are using standalone components, all required imports are already defined in the component's `imports` array:
- `PageToolbarComponent`, `ExtensibleTableComponent`, `ExtensibleFormComponent` - Extension components
- `PermissionManagementComponent` - Permission management component
- `ModalComponent`, `ButtonComponent` - Theme shared components
- `LocalizationPipe` - For localization
- `ReactiveFormsModule` - For form handling
As the last step, it is needs to be replaced the `RolesComponent` with the `MyRolesComponent`. Open the `app.component.ts` and modify its content as shown below:

2
docs/en/framework/ui/angular/permission-management.md

@ -7,7 +7,7 @@
# Permission Management
A permission is a simple policy that is granted or prohibited for a particular user, role or client. You can read more about [authorization in ABP](../../fundamentals/authorization.md) document.
A permission is a simple policy that is granted or prohibited for a particular user, role or client. You can read more about [authorization in ABP](../../fundamentals/authorization/index.md) document.
You can get permission of authenticated user using `getGrantedPolicy` or `getGrantedPolicy$` method of `PermissionService`.

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

@ -1,13 +1,13 @@
```json
//[doc-seo]
{
"Description": "Learn how to set up your development environment for ABP Angular 17.3.x with this quick start guide, ensuring a smooth development experience."
"Description": "Learn how to set up your development environment for ABP Angular 21.x with this quick start guide, ensuring a smooth development experience."
}
```
# ABP Angular Quick Start
**In this version ABP uses Angular [20.0.x](https://github.com/angular/angular/tree/20.0.x) version. You don't have to install Angular CLI globally**
**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**
## How to Prepare Development Environment
@ -18,13 +18,13 @@ Please follow the steps below to prepare your development environment for Angula
3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to Angular projects. ABP project templates even contain plugin recommendations for VS Code users, which VS Code will ask you to install when you open the Angular project folder. Here is a list of recommended extensions:
- [Angular Language Service](https://marketplace.visualstudio.com/items?itemName=angular.ng-template)
- [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
- [TSLint](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-tslint-plugin)
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [Visual Studio IntelliCode](https://marketplace.visualstudio.com/items?itemName=visualstudioexptteam.vscodeintellicode)
- [Path Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-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)
- [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)
- [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)
- [indent-rainbow](https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow)

14
docs/en/framework/ui/angular/ssr-configuration.md

@ -239,7 +239,7 @@ The schematic installs `openid-client` to handle authentication on the server si
## 5. Render Modes & Hybrid Rendering
Angular 20 provides different rendering modes that you can configure per route in the `app.routes.server.ts` file to optimize performance and SEO.
Angular 21 provides different rendering modes that you can configure per route in the `app.routes.server.ts` file to optimize performance and SEO.
### 5.1. Available Render Modes
@ -352,13 +352,17 @@ currentTime = new Date();
// ✅ Good - use TransferState for consistent data
import { TransferState, makeStateKey } from '@angular/core';
const TIME_KEY = makeStateKey<string>('time');
TIME_KEY = makeStateKey<string>('time');
transferState = inject<TransferState>(TransferState);
time: string;
constructor(private transferState: TransferState) {
constructor() {
if (isPlatformServer(this.platformId)) {
this.transferState.set(TIME_KEY, new Date().toISOString());
this.time = new Date().toISOString();
this.transferState.set(this.TIME_KEY, this.time);
} else {
this.time = this.transferState.get(TIME_KEY, new Date().toISOString());
const timeFromCache = this.transferState.get(this.TIME_KEY, new Date().toISOString());
this.time = timeFromCache;
}
}
```

5
docs/en/framework/ui/angular/theming.md

@ -229,13 +229,14 @@ All of the options are shown below. You can choose either of them.
````ts
import { eUserMenuItems } from '@abp/ng.theme.basic';
import { UserMenuService } from '@abp/ng.theme.shared';
import { UserMenuService, UserMenu } from '@abp/ng.theme.shared';
import { LocalizationPipe, INJECTOR_PIPE_DATA_TOKEN } from '@abp/ng.core';
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
// make sure that you import this component in a NgModule
@Component({
selector: 'abp-current-user-test',
imports: [LocalizationPipe],
template: `
<a class="dropdown-item pointer" (click)="data.action()">
@if (data.textTemplate.icon){

4
docs/en/framework/ui/blazor/authorization.md

@ -9,7 +9,7 @@
Blazor applications can use the same authorization system and permissions defined in the server side.
> This document is only for authorizing on the Blazor UI. See the [Server Side Authorization](../../fundamentals/authorization.md) to learn how to define permissions and control the authorization system.
> This document is only for authorizing on the Blazor UI. See the [Server Side Authorization](../../fundamentals/authorization/index.md) to learn how to define permissions and control the authorization system.
## Basic Usage
@ -76,7 +76,7 @@ There are some useful extension methods for the `IAuthorizationService`:
## See Also
* [Authorization](../../fundamentals/authorization.md) (server side)
* [Authorization](../../fundamentals/authorization/index.md) (server side)
* [Blazor Security](https://docs.microsoft.com/en-us/aspnet/core/blazor/security/) (Microsoft documentation)
* [ICurrentUser Service](../../infrastructure/current-user.md)
* [Video tutorial](https://abp.io/video-courses/essentials/authorization)

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

@ -102,7 +102,7 @@ protected override async ValueTask SetToolbarItemsAsync()
#### Permissions
If your button/component should be available based on a [permission/policy](../../fundamentals/authorization.md), you can pass the permission/policy name as the `RequiredPolicyName` parameter to the `AddButton` and `AddComponent` methods.
If your button/component should be available based on a [permission/policy](../../fundamentals/authorization/index.md), you can pass the permission/policy name as the `RequiredPolicyName` parameter to the `AddButton` and `AddComponent` methods.
### Add a Page Toolbar Contributor

2
docs/en/framework/ui/mvc-razor-pages/auto-complete-select.md

@ -78,4 +78,4 @@ It'll be automatically bound to a collection of defined value type.
## Notices
If the authenticated user doesn't have permission on the given URL, the user will get an authorization error. Be careful while designing this kind of UIs.
You can create a specific, [unauthorized](../../fundamentals/authorization.md) endpoint/method to get the list of items, so the page can retrieve lookup data of dependent entity without giving the entire read permission to users.
You can create a specific, [unauthorized](../../fundamentals/authorization/index.md) endpoint/method to get the list of items, so the page can retrieve lookup data of dependent entity without giving the entire read permission to users.

2
docs/en/framework/ui/mvc-razor-pages/javascript-api/ajax.md

@ -32,7 +32,7 @@ abp.ajax({
});
````
This command logs the list of users to the console, if you've **logged in** to the application and have [permission](../../../fundamentals/authorization.md) for the user management page of the [Identity Module](../../../../modules/identity.md).
This command logs the list of users to the console, if you've **logged in** to the application and have [permission](../../../fundamentals/authorization/index.md) for the user management page of the [Identity Module](../../../../modules/identity.md).
## Error Handling

2
docs/en/framework/ui/mvc-razor-pages/javascript-api/auth.md

@ -9,7 +9,7 @@
Auth API allows you to check permissions (policies) for the current user in the client side. In this way, you can conditionally show/hide UI parts or perform your client side logic based on the current permissions.
> This document only explains the JavaScript API. See the [authorization document](../../../fundamentals/authorization.md) to understand the ABP authorization & permission system.
> This document only explains the JavaScript API. See the [authorization document](../../../fundamentals/authorization/index.md) to understand the ABP authorization & permission system.
## Basic Usage

8
docs/en/framework/ui/mvc-razor-pages/modals.md

@ -207,7 +207,7 @@ namespace MyProject.Web.Pages.Products
public class ProductCreateModalModel : AbpPageModel
{
[BindProperty]
public PoductCreationDto Product { get; set; }
public ProductCreationDto Product { get; set; }
public async Task OnGetAsync()
{
@ -227,9 +227,9 @@ namespace MyProject.Web.Pages.Products
* This is a simple `PageModal` class. The `[BindProperty]` make the form binding to the model when you post (submit) the form; The standard ASP.NET Core system.
* `OnPostAsync` returns `NoContent` (this method is defined by the base `AbpPageModel` class). Because we don't need to a return value in the client side, after the form post operation.
**PoductCreationDto:**
**ProductCreationDto:**
`ProductCreateModalModel` uses a `PoductCreationDto` class defined as shown below:
`ProductCreateModalModel` uses a `ProductCreationDto` class defined as shown below:
````csharp
using System;
@ -238,7 +238,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace MyProject.Web.Pages.Products
{
public class PoductCreationDto
public class ProductCreationDto
{
[Required]
[StringLength(128)]

2
docs/en/framework/ui/mvc-razor-pages/navigation-menu.md

@ -117,7 +117,7 @@ There are more options of a menu item (the constructor of the `ApplicationMenuIt
As seen above, a menu contributor contributes to the menu dynamically. So, you can perform any custom logic or get menu items from any source.
One use case is the [authorization](../../fundamentals/authorization.md). You typically want to add menu items by checking a permission.
One use case is the [authorization](../../fundamentals/authorization/index.md). You typically want to add menu items by checking a permission.
**Example: Check if the current user has a permission**

2
docs/en/framework/ui/mvc-razor-pages/page-toolbar-extensions.md

@ -134,7 +134,7 @@ Configure<AbpPageToolbarOptions>(options =>
#### Permissions
If your button/component should be available based on a [permission/policy](../../fundamentals/authorization.md), you can pass the permission/policy name as the `requiredPolicyName` parameter to the `AddButton` and `AddComponent` methods.
If your button/component should be available based on a [permission/policy](../../fundamentals/authorization/index.md), you can pass the permission/policy name as the `requiredPolicyName` parameter to the `AddButton` and `AddComponent` methods.
### Add a Page Toolbar Contributor

4
docs/en/framework/ui/mvc-razor-pages/tag-helpers/index.md

@ -7,7 +7,7 @@
# ABP Tag Helpers
ABP defines a set of **tag helper components** to simply the user interface development for ASP.NET Core (MVC / Razor Pages) applications.
ABP defines a set of **tag helper components** to simplify the user interface development for ASP.NET Core (MVC / Razor Pages) applications.
## Bootstrap Component Wrappers
@ -45,7 +45,7 @@ Here, the list of components those are wrapped by the ABP:
## Form Elements
**Abp Tag Helpers** add new features to standard **Asp.Net Core MVC input & select Tag Helpers** and wrap them with **Bootstrap** form controls. See [Form Elements documentation](form-elements.md) .
**Abp Tag Helpers** add new features to standard **ASP.NET Core MVC input & select Tag Helpers** and wrap them with **Bootstrap** form controls. See [Form Elements documentation](form-elements.md) .
## Dynamic Forms

2
docs/en/framework/ui/mvc-razor-pages/theming.md

@ -437,7 +437,7 @@ In this way, applications or modules can have selectors based on the current lay
### RTL
To support Right-To-Left languages, the Layout should check the current culture and add `dir="rtl"` to the `html` tag and `rtl` CSS class the the `body` tag.
To support Right-To-Left languages, the Layout should check the current culture and add `dir="rtl"` to the `html` tag and `rtl` CSS class to the `body` tag.
You can check `CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft` to understand if the current language is a RTL language.

2
docs/en/framework/ui/mvc-razor-pages/toolbars.md

@ -79,7 +79,7 @@ public class MyToolbarContributor : IToolbarContributor
}
````
You can use the [authorization](../../fundamentals/authorization.md) to decide whether to add a `ToolbarItem`.
You can use the [authorization](../../fundamentals/authorization/index.md) to decide whether to add a `ToolbarItem`.
````csharp
if (await context.IsGrantedAsync("MyPermissionName"))

6
docs/en/framework/ui/mvc-razor-pages/widgets.md

@ -12,7 +12,7 @@ ABP provides a model and infrastructure to create **reusable widgets**. Widget s
* Have **scripts & styles** dependencies for your widget.
* Create **dashboards** with widgets used inside.
* Define widgets in reusable **[modules](../../architecture/modularity/basics.md)**.
* Co-operate widgets with **[authorization](../../fundamentals/authorization.md)** and **[bundling](bundling-minification.md)** systems.
* Co-operate widgets with **[authorization](../../fundamentals/authorization/index.md)** and **[bundling](bundling-minification.md)** systems.
## Basic Widget Definition
@ -42,7 +42,7 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
Inheriting from `AbpViewComponent` is not required. You could inherit from ASP.NET Core's standard `ViewComponent`. `AbpViewComponent` only defines some base useful properties.
You can inject a service and use in the `Invoke` method to get some data from the service. You may need to make Invoke method async, like `public async Task<IViewComponentResult> InvokeAsync()`. See [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) document fore all different usages.
You can inject a service and use in the `Invoke` method to get some data from the service. You may need to make Invoke method async, like `public async Task<IViewComponentResult> InvokeAsync()`. See [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) document for all different usages.
**Default.cshtml**:
@ -482,7 +482,7 @@ Used to refresh the widget when needed. It has a filter argument that can be use
Some widgets may need to be available only for authenticated or authorized users. In this case, use the following properties of the `Widget` attribute:
* `RequiresAuthentication` (`bool`): Set to true to make this widget usable only for authentication users (user have logged in to the application).
* `RequiredPolicies` (`List<string>`): A list of policy names to authorize the user. See [the authorization document](../../fundamentals/authorization.md) for more info about policies.
* `RequiredPolicies` (`List<string>`): A list of policy names to authorize the user. See [the authorization document](../../fundamentals/authorization/index.md) for more info about policies.
Example:

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

@ -12,11 +12,11 @@
}
````
# Getting Started with the React Native
# Getting Started with React Native
> React Native mobile option is *available for* ***Team*** *or higher licenses*
> The React Native mobile option is *available for* ***Team*** *or higher licenses*
ABP platform provide basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated to 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)
@ -24,12 +24,14 @@ ABP platform provide basic [React Native](https://reactnative.dev/) startup temp
Please follow the steps below to prepare your development environment for React Native.
1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js v20.11+ installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system.
2. **[Optional] Install Yarn:** You may install Yarn v1 (not v2) following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 delivers an arguably better developer experience compared to npm v6 and below. You may skip this step and work with npm, which is built-in in Node.js, instead.
3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to React Native projects.
4. **Install an Emulator/Simulator:** React Native applications need an Android emulator or an iOS simulator to run on your OS. If you do not have Android Studio installed and configured on your system, we recommend [setting up android emulator without android studio](setting-up-android-emulator.md).
1. **Install Node.js:** Visit the [Node.js downloads page](https://nodejs.org/en/download/) and download the appropriate Node.js v20.11+ installer for your operating system. Alternatively, you can install [NVM](https://github.com/nvm-sh/nvm) to manage multiple versions of Node.js on your system.
2. **[Optional] Install Yarn:** You can install Yarn v1 (not v2) by following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 provides a better developer experience compared to npm v6 and below. You can skip this step and use npm, which is built into Node.js.
3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE that works seamlessly with TypeScript. While you can use any IDE, including Visual Studio or Rider, VS Code typically provides the best developer experience for React Native projects.
4. **[Optional] Install an Emulator/Simulator:** If you want to test on Android emulators or iOS simulators (instead of using the Web View method), you'll need to install one of the following:
- **Android Studio & Emulator:** Install [Android Studio](https://developer.android.com/studio) and set up an Android Virtual Device (AVD) through the AVD Manager. You can follow the [Android Studio Emulator guide](https://docs.expo.dev/workflow/android-studio-emulator/) on expo.io documentation.
- **Xcode & iOS Simulator:** On macOS, install [Xcode](https://developer.apple.com/xcode/) from the App Store, which includes the iOS Simulator. You can follow the [iOS Simulator guide](https://docs.expo.dev/workflow/ios-simulator/) on expo.io documentation.
If you prefer the other way, you can check the [Android Studio Emulator](https://docs.expo.dev/workflow/android-studio-emulator/) or [iOS Simulator](https://docs.expo.dev/workflow/ios-simulator/) on expo.io documentation to learn how to set up an emulator.
> **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
@ -37,34 +39,143 @@ You have multiple options to initiate a new React Native project that works with
### 1. Using ABP Studio
ABP Studio application is the most convenient and flexible way to initiate a React Native application based on ABP framework. You can 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)
### 2. Using ABP CLI
ABP CLI is another way of creating an ABP solution with a React Native application. Simply [install the ABP CLI](../../../cli) and run the following command in your terminal:
The ABP CLI is another way to create an ABP solution with a React Native application. [Install the ABP CLI](../../../cli) and run the following command in your terminal:
```shell
abp new MyCompanyName.MyProjectName -csf -u <angular or mvc> -m react-native
```
> To see further options in the CLI, please visit the [CLI manual](../../../cli).
> For more options, visit the [CLI manual](../../../cli).
This command will prepare a solution with an **Angular** or an **MVC** (depends on your choice), a **.NET Core**, and a **React Native** project in it.
This command creates a solution containing an **Angular** or **MVC** project (depending on your choice), a **.NET Core** project, and a **React Native** project.
## How to Configure & Run the Backend
## Running the React Native Application
> **Recommended:** For faster development and testing, we recommend using the **Web View** option first, as it requires fewer backend modifications. The backend configuration described in the next section is only needed if you want to test on Android emulators or iOS simulators.
Before running the React Native application, install the dependencies by running `yarn install` or `npm install` in the `react-native` directory.
### Web View (Recommended - Quickest Method)
The quickest way to test the application is by using the web view. While testing on a physical device is also supported, we recommend using [local HTTPS development](https://docs.expo.dev/guides/local-https-development/) as it requires fewer backend modifications.
Follow these steps to set up the web view:
1. Navigate to the `react-native` directory and start the application by running:
```bash
yarn web
```
2. Generate SSL certificates by running the following command in a separate directory:
```bash
mkcert localhost
```
3. Set up the local proxy by running:
```bash
yarn create:local-proxy
```
The default port is `443`. To use a different port, specify the `SOURCE_PORT` environment variable:
```bash
SOURCE_PORT=8443 yarn create:local-proxy
```
4. If you changed the port in the previous step, update the `apiUrl` in `Environment.ts` accordingly.
5. Update the mobile application settings in the database and re-run the migrations. If you specified a custom port, ensure the port is updated in the configuration as well:
```json
"OpenIddict": {
"Applications": {
"MyApplication_Mobile": {
"ClientId": "MyApplication_Mobile",
"RootUrl": "https://localhost"
}
}
}
```
### Running on Emulator/Simulator
If you prefer to test on an Android emulator or iOS simulator, you'll need to configure the backend as described in the section below. Follow these steps:
1. Make sure the [database migration is complete](../../../get-started?UI=NG&DB=EF&Tiered=No#create-the-database) and the [API is up and running](../../../get-started?UI=NG&DB=EF&Tiered=No#run-the-application).
2. Open `react-native` folder and run `yarn install` or `npm install` if you have not already.
3. Open the `Environment.ts` file in the `react-native` folder and replace the `localhost` address in the `apiUrl` and `issuer` properties with your local IP address as shown below:
{{ if Architecture == "Monolith" }}
![react native monolith environment local IP](../../../images/react-native-monolith-environment-local-ip.png)
{{ else if Architecture == "Tiered" }}
![react native tiered environment local IP](../../../images/react-native-tiered-environment-local-ip.png)
> Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.HttpApi.Host` or `.Web` project.
{{ else }}
![react native microservice environment local IP](../../../images/react-native-environment-local-ip.png)
> Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.AuthServer` project.
{{ end }}
1. Run `yarn start` or `npm start`. Wait for the Expo CLI to print the options.
> 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)
In the image above, you can start the application on an Android emulator, an iOS simulator, or a physical phone by scanning the QR code with the [Expo Client](https://expo.io/tools#client) or by choosing the corresponding option.
### Expo
![React Native login screen on iPhone 16](../../../images/rn-login-iphone.png)
### Android Studio
1. Start the emulator in **Android Studio** before running the `yarn start` or `npm start` command.
2. Press **a** to open in Android Studio.
![React Native login screen on Android Device](../../../images/rn-login-android-studio.png)
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.
## How to Configure & Run the Backend (Required for Emulator/Simulator Testing)
> React Native application does not trust the auto-generated .NET HTTPS certificate. You should use **HTTP** during the development.
A React Native application running on an Android emulator or a physical phone **can not connect to the backend** on `localhost`. To fix this problem, it is necessary to run the backend application using the `Kestrel` configuration.
To disable the HTTPS-only settings of OpenIddict, open the {{ if Architecture == "Monolith" }}`MyProjectNameHttpApiHostModule`{{ else if Architecture == "Tiered" }}`MyProjectNameAuthServerModule`{{ end }} project and add the following code block to the `PreConfigureServices` method:
```csharp
#if DEBUG
PreConfigure<OpenIddictServerBuilder>(options =>
{
options.UseAspNetCore()
.DisableTransportSecurityRequirement();
});
#endif
```
> **Important:** Before running the backend application, make sure you have completed the [database migration](../../../get-started?UI=NG&DB=EF&Tiered=No#create-the-database) if you are starting with a fresh database. The backend application requires the database to be properly initialized.
A React Native application running on an Android emulator or a physical phone **cannot connect to the backend** on `localhost`. To resolve this, you need to run the backend application using the `Kestrel` configuration.
{{ if Architecture == "Monolith" }}
![React Native monolith host project configuration](../../../images/react-native-monolith-be-config.png)
- Open the `appsettings.json` file in the `.DbMigrator` folder. Replace the `localhost` address on 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 just to test the react native application on the development environment.
- Open the `appsettings.json` file in the `.DbMigrator` folder. Replace the `localhost` address in the `RootUrl` property with your local IP address. Then, run the database migrator.
- Open the `appsettings.Development.json` file in the `.HttpApi.Host` folder. Add this configuration to accept global requests for testing the React Native application in the development environment.
```json
{
@ -82,8 +193,8 @@ A React Native application running on an Android emulator or a physical phone **
![React Native tiered project configuration](../../../images/react-native-tiered-be-config.png)
- Open the `appsettings.json` file in the `.DbMigrator` folder. Replace the `localhost` address on 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 just to test the react native application on the development environment.
- Open the `appsettings.json` file in the `.DbMigrator` folder. Replace the `localhost` address in the `RootUrl` property with your local IP address. Then, run the database migrator.
- Open the `appsettings.Development.json` file in the `.AuthServer` folder. Add this configuration to accept global requests for testing the React Native application in the development environment.
```json
{
@ -97,7 +208,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 again. In addition, you will need to introduce 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
{
@ -121,7 +232,7 @@ A React Native application running on an Android emulator or a physical phone **
![React Native microservice project configuration](../../../images/react-native-microservice-be-config.png)
- Open the `appsettings.Development.json` file in the `.AuthServer` folder. Add this configuration to accept global requests just to test the react native application on 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
{
@ -138,7 +249,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 just to test the react native application on the development environment. You should also provide the authentication server configuration. In addition, you need to apply the same process for all the services you would 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
{
@ -161,7 +272,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
{
@ -182,7 +293,7 @@ A React Native application running on an Android emulator or a physical phone **
}
```
- Lastly, update the mobile gateway configurations as following:
- Finally, update the mobile gateway configurations as follows:
```json
//gateways/mobile/MyMicroserviceProject.MobileGateway/Properties/launchSettings.json
@ -259,71 +370,4 @@ A React Native application running on an Android emulator or a physical phone **
{{ end }}
Run the backend application as described in the [getting started document](../../../get-started).
> You should turn off the "Https Restriction" if you're using OpenIddict as a central identity management solution. Because the IOS Simulator doesn't support self-signed certificates and OpenIddict is set to only work with HTTPS by default.
## How to disable the Https-only settings of OpenIddict
Open the {{ if Architecture == "Monolith" }}`MyProjectNameHttpApiHostModule`{{ else if Architecture == "Tiered" }}`MyProjectNameAuthServerModule`{{ end }} project and copy-paste the below code-block to the `PreConfigureServices` method:
```csharp
#if DEBUG
PreConfigure<OpenIddictServerBuilder>(options =>
{
options.UseAspNetCore()
.DisableTransportSecurityRequirement();
});
#endif
```
## How to Configure & Run the React Native Application
1. Make sure the [database migration is complete](../../../get-started?UI=NG&DB=EF&Tiered=No#create-the-database) and the [API is up and running](../../../get-started?UI=NG&DB=EF&Tiered=No#run-the-application).
2. Open `react-native` folder and run `yarn` or `npm install` if you have not already.
3. Open the `Environment.ts` in the `react-native` folder and replace the `localhost` address on the `apiUrl` and `issuer` properties with your local IP address as shown below:
{{ if Architecture == "Monolith" }}
![react native monolith environment local IP](../../../images/react-native-monolith-environment-local-ip.png)
{{ else if Architecture == "Tiered" }}
![react native tiered environment local IP](../../../images/react-native-tiered-environment-local-ip.png)
> Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.HttpApi.Host` or `.Web` project.
{{ else }}
![react native microservice environment local IP](../../../images/react-native-microservice-environment-local-ip.png)
> Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.AuthServer` project.
{{ end }}
1. Run `yarn start` or `npm start`. Wait for the Expo CLI to print the opitons.
> 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, while it has many features.
![expo-cli-options](../../../images/rn-options.png)
In the above image, you can start the application with 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 choosing the option.
### Expo
![React Native login screen on iPhone 16](../../../images/rn-login-iphone.png)
### Android Studio
1. Start the emulator in **Android Studio** before running the `yarn start` or `npm start` command.
2. Press **a** to open in Android Studio.
![React Native login screen on Android Device](../../../images/rn-login-android-studio.png)
Enter **admin** as the username and **1q2w3E\*** as the password to login to the application.
The application is up and running. You can continue to develop your application based on this startup template.
## See Also
- [React Native project structure](../../../solution-templates/application-module#react-native)
Run the backend application(s) as described in the [getting started document](../../../get-started).

316
docs/en/framework/ui/react-native/setting-up-android-emulator.md

@ -1,58 +1,121 @@
```json
//[doc-seo]
{
"Description": "Learn how to set up an Android emulator without Android Studio using command line tools on Windows, macOS, and Linux."
}
```
```json
//[doc-seo]
{
"Description": "Learn how to set up an Android emulator without Android Studio using command line tools on Windows, macOS, and Linux."
}
```
# Setting Up Android Emulator Without Android Studio (Windows, macOS, Linux)
This guide explains how to install and run an Android emulator **without Android Studio**, using only **Command Line Tools**.
This guide walks you through installing and running an Android emulator **without Android Studio**, using only the **Android Command Line Tools**.
---
## 1. Download Required Tools
Go to: [https://developer.android.com/studio#command-tools](https://developer.android.com/studio#command-tools)
Download the "Command line tools only" package for your OS:
Visit the [Android Command Line Tools download page](https://developer.android.com/studio#command-line-tools-only) and download the "Command line tools only" package for your operating system:
- **Windows:** `commandlinetools-win-*.zip`
- **macOS:** `commandlinetools-mac-*.zip`
- **Linux:** `commandlinetools-linux-*.zip`
> **Alternative for Windows:** If you prefer using Windows Package Manager, you can install Android Studio (which includes the command-line tools) using:
> ```powershell
> winget install --id=Google.AndroidStudio -e
> ```
> However, this guide focuses on installing only the command-line tools without the full IDE.
---
## 2. Create the Required Directory Structure
## 2. Create Directory Structure and Extract Files
The Android SDK tools require a specific directory structure. Follow the steps below for your operating system.
### Windows:
```
C:\Android\
└── cmdline-tools\
└── latest\
└── [extract all files from the zip here]
```
1. **Create the directory structure:**
```
C:\Android\
└── cmdline-tools\
└── latest\
```
2. **Extract the downloaded zip file.** The archive contains a `cmdline-tools` folder with `bin`, `lib`, and other files.
3. **Move all contents** from the extracted `cmdline-tools` folder into `C:\Android\cmdline-tools\latest\`
Your final directory structure should look like this:
```
C:\Android\
└── cmdline-tools\
└── latest\
├── bin\
├── lib\
└── [other files]
```
### macOS / Linux:
```
~/Android/
└── cmdline-tools/
└── latest/
└── [extract all files from the zip here]
```
> You need to create the `latest` folder manually.
1. **Create the directory structure:**
```bash
mkdir -p ~/Android/cmdline-tools
```
2. **Extract the downloaded zip file:**
```bash
cd ~/Downloads
unzip commandlinetools-*.zip
```
3. **Move the extracted folder to the correct location:**
```bash
mv cmdline-tools ~/Android/cmdline-tools/latest
```
Your final directory structure should look like this:
```
~/Android/
└── cmdline-tools/
└── latest/
├── bin/
├── lib/
└── [other files]
```
> **Important:** The `latest` folder must be created manually (Windows) or by renaming the extracted folder (macOS/Linux). The Android SDK tools require this exact directory structure to function properly.
---
## 3. Set Environment Variables
## 3. Configure Environment Variables
Set up environment variables so your system can locate the Android SDK tools.
### Windows (PowerShell - permanent):
Run these commands in PowerShell to set environment variables permanently:
```powershell
[System.Environment]::SetEnvironmentVariable('ANDROID_HOME', 'C:\Android', 'User')
$currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
[System.Environment]::SetEnvironmentVariable('Path', "$currentPath;C:\Android\cmdline-tools\latest\bin;C:\Android\platform-tools;C:\Android\emulator", 'User')
```
> **Note:** You may need to restart your terminal or PowerShell session for the changes to take effect.
### Windows (CMD - temporary for current session):
If you only need the environment variables for the current session, use these commands:
### Windows (temporary for CMD session):
```cmd
set PATH=C:\Android\cmdline-tools\latest\bin;C:\Android\platform-tools;C:\Android\emulator;%PATH%
set ANDROID_HOME=C:\Android
set PATH=%PATH%;C:\Android\cmdline-tools\latest\bin;C:\Android\platform-tools;C:\Android\emulator
```
> **Note:** These settings will be lost when you close the command prompt window.
### macOS / Linux:
Add the following to your `.bashrc`, `.zshrc`, or `.bash_profile` file:
Add the following environment variables to your shell configuration file (`.bashrc`, `.zshrc`, or `.bash_profile`):
```bash
export ANDROID_HOME=$HOME/Android
@ -61,62 +124,164 @@ export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/emulator
```
> Apply the changes:
After saving the file, reload your shell configuration:
```bash
source ~/.zshrc # or ~/.bashrc if you're using bash
```
**Verify Environment Variables:**
After reloading, verify the variables are set correctly:
```bash
echo $ANDROID_HOME
which sdkmanager
```
---
## 4. Install SDK Components
## 4. Accept Android SDK Licenses (macOS/Linux)
Install platform tools, emulator, and a system image:
**macOS / Linux users only:** Before installing SDK components, you must accept the Android SDK licenses:
```bash
sdkmanager --sdk_root=$ANDROID_HOME "platform-tools" "platforms;android-34" "system-images;android-34;google_apis;x86_64" "emulator"
yes | sdkmanager --licenses
```
> On Windows, replace `$ANDROID_HOME` with `--sdk_root=C:\Android`.
This command automatically accepts all licenses. Without this step, the installation will fail.
> **Note:** Windows users will be prompted to accept licenses during the installation in the next step.
---
## 5. Create an AVD (Android Virtual Device)
## 5. Install SDK Components
Use `sdkmanager` to install the required Android SDK components: platform tools, an Android platform, a system image, and the emulator.
### Windows:
```cmd
sdkmanager --sdk_root=C:\Android "platform-tools" "platforms;android-35" "system-images;android-35;google_apis;x86_64" "emulator"
```
### macOS / Linux:
**For Apple Silicon Macs (M1/M2/M3/M4):**
```bash
sdkmanager "platform-tools" "platforms;android-35" "system-images;android-35;google_apis;arm64-v8a" "emulator"
```
**For Intel-based Macs and Linux:**
```bash
sdkmanager "platform-tools" "platforms;android-35" "system-images;android-35;google_apis;x86_64" "emulator"
```
> **Note:**
> - This command installs Android 15 (API level 35), which is the current stable version required by Google Play as of 2025.
> - To see all available versions, run `sdkmanager --list` and replace `android-35` with your preferred API level if needed.
> - Use `arm64-v8a` for Apple Silicon Macs (M1/M2/M3/M4) and `x86_64` for Intel-based Macs, Windows, and most Linux systems. The architecture must match your system's processor.
---
## 6. Create an Android Virtual Device (AVD)
An AVD is a device configuration that defines the characteristics of an Android device you want to simulate.
### List Available Device Profiles (Optional):
Before creating an AVD, you can view all available device profiles (e.g., Pixel, Nexus) to choose from:
### List available devices:
```bash
avdmanager list devices
```
### Create your AVD:
### Create Your AVD:
Create a new AVD with the following command:
**Windows:**
```cmd
avdmanager create avd -n myEmu -k "system-images;android-35;google_apis;x86_64" --device "pixel_8"
```
**macOS / Linux (Apple Silicon):**
```bash
avdmanager create avd -n myEmu -k "system-images;android-34;google_apis;x86_64" --device "pixel"
avdmanager create avd -n myEmu -k "system-images;android-35;google_apis;arm64-v8a" --device "pixel_8"
```
**macOS / Linux (Intel-based):**
```bash
avdmanager create avd -n myEmu -k "system-images;android-35;google_apis;x86_64" --device "pixel_8"
```
When prompted "Do you wish to create a custom hardware profile?" type `no` and press Enter.
> **Note:**
> - Replace `myEmu` with your preferred AVD name
> - Replace `pixel_8` with a device profile from the list above if you want a different device configuration
> - The `-k` parameter specifies the system image you installed in the previous step. Make sure it matches the architecture you installed (x86_64 for Windows and Intel-based systems, arm64-v8a for Apple Silicon Macs)
---
## 6. Start the Emulator
## 7. Start the Emulator
Launch your emulator using the AVD name you created:
```bash
emulator -avd myEmu
```
The emulator window should open
Replace `myEmu` with the name you used when creating your AVD. The emulator window should open and boot up the Android system.
### Common Startup Issues (macOS/Linux):
**If you get "command not found":**
```bash
$ANDROID_HOME/emulator/emulator -avd myEmu
```
**For better performance on Apple Silicon:**
```bash
emulator -avd myEmu -gpu host
```
**For headless mode (no GUI window):**
```bash
emulator -avd myEmu -no-window
```
---
## Extra Tools and Commands
## 8. Additional Tools and Commands
### List All AVDs:
View all created virtual devices:
```bash
avdmanager list avd
```
### List Connected Devices:
Use ADB (Android Debug Bridge) to view all connected Android devices and running emulators:
### List connected devices with ADB:
```bash
adb devices
```
This is useful for verifying that your emulator is running and properly connected.
### Install an APK:
Install an Android application package (APK) to your emulator or connected device:
```bash
adb install myApp.apk
```
---
Replace `myApp.apk` with the path to your APK file.
## How to Enable Fast Refresh in React Native
@ -146,17 +311,66 @@ Focus the emulator window and press:
---
## Troubleshooting
## 9. Troubleshooting
Common issues and their solutions:
| Problem | Explanation |
|--------|-------------|
| `sdkmanager not found` | Make sure `PATH` includes the `latest/bin` directory |
| `x86_64 system image not found` | Make sure you've downloaded it using `sdkmanager` |
| `emulator not found` | Add the `emulator` directory to `PATH` |
| `setx` truncates path (Windows) | Use GUI to update environment variables manually |
| Problem | Solution |
|--------|----------|
| `sdkmanager not found` | **Windows:** Verify that your `PATH` environment variable includes `C:\Android\cmdline-tools\latest\bin`. **macOS/Linux:** Verify `PATH` includes `$ANDROID_HOME/cmdline-tools/latest/bin`. Run `source ~/.zshrc` or `source ~/.bashrc` to reload environment variables |
| `Warning: Could not create settings` | **macOS/Linux:** Run `yes \| sdkmanager --licenses` to accept all Android SDK licenses. **Windows:** Accept licenses when prompted during installation |
| System image not found | Ensure you've installed the system image using `sdkmanager` in step 5. The architecture must match: `x86_64` for Windows and Intel-based systems, `arm64-v8a` for Apple Silicon Macs. Run `sdkmanager --list` to verify available images, then run the installation command again if needed |
| `emulator not found` | **Windows:** Add the `emulator` directory to your `PATH`: `C:\Android\emulator`. **macOS/Linux:** Use full path: `$ANDROID_HOME/emulator/emulator -avd myEmu` or verify `$ANDROID_HOME/emulator` is in your PATH |
| `setx` truncates path (Windows) | If `setx` truncates your PATH variable, use the PowerShell method from step 3, or update environment variables manually through the Windows GUI (System Properties → Environment Variables) |
| Emulator won't start | **Windows:** Ensure hardware acceleration is enabled. Enable Hyper-V or use Intel HAXM. **macOS (Apple Silicon):** Try `emulator -avd myEmu -gpu host`. **Linux:** Ensure KVM is enabled: `sudo apt install qemu-kvm` and add user to kvm group |
| Permission denied errors (macOS/Linux) | Run `chmod +x $ANDROID_HOME/cmdline-tools/latest/bin/*` to make tools executable |
| Emulator extremely slow | **Windows:** Enable Hyper-V or Intel HAXM. **Linux:** Install and enable KVM (see below). **Apple Silicon:** Ensure you're using `arm64-v8a` system images for optimal performance |
### Enable Hardware Acceleration (Linux):
For better performance on Linux, install and enable KVM:
```bash
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
sudo adduser $USER kvm
```
Log out and log back in for group changes to take effect.
---
## 10. Summary
You've successfully set up an Android emulator without installing Android Studio, using only the command-line tools. This emulator can be used for React Native development or any other mobile development framework that requires Android emulation.
The emulator is now ready to use. You can start it anytime with `emulator -avd myEmu` (using your AVD name) and begin developing and testing your Android applications.
### Quick Reference Commands:
**Start the emulator:**
```bash
emulator -avd myEmu
```
**List running devices:**
```bash
adb devices
```
**Install an app:**
```bash
adb install myApp.apk
```
**List all AVDs:**
```bash
avdmanager list avd
```
---
## Summary
## Additional Notes
You can now run an Android emulator without installing Android Studio, entirely through the command line. This emulator can be used for React Native or any mobile development framework.
- **Android API Updates:** As of January 2026, Android 15 (API 35) is the current requirement for Google Play Store submissions. This guide uses API 35, but you can install older versions if needed for legacy app testing.
- **Storage Location:** All Android SDK files are stored in `C:\Android\` (Windows) or `~/Android/` (macOS/Linux) and can consume several GB of disk space.
- **Updating SDK Components:** Run `sdkmanager --update` periodically to keep your SDK tools up to date.

BIN
docs/en/get-started/images/abp-studio-background-tasks.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
docs/en/get-started/images/abp-studio-created-new-microservice-solution.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-admin-password.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 39 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 87 KiB

BIN
docs/en/get-started/images/abp-studio-open-module-folder.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/en/get-started/images/abp-studio-welcome-screen.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 152 KiB

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

@ -142,11 +142,11 @@ In this step, you can choose which languages your application will support.
* Click Add Custom Language if you want to add a language that is not listed.
You can change these settings later if needed. Thenk click the *Next* button for the *Additional Options* page:
You can change these settings later if needed. Then click the *Next* button for the *Additional Options* page:
![abp-studio-new-solution-dialog-additional-options](images/abp-studio-new-solution-dialog-additional-options_dark.png)
If you uncheck the *Kubernetes Configuration* option, the solution will not include the Kubernetes configuration files, such as Helm charts and other Kubernetes-related files. You can also specify *Social Logins*; if you uncheck this option, the solution will not be configured for social login. Lastly, you can specify the *Include Tests* option to include or exclude the test projects from the solution.
If you uncheck the *Kubernetes Configuration* option, the solution will not include the Kubernetes configuration files, which includes the Helm charts and other Kubernetes-related files. You can also specify *Social Logins*; if you uncheck this option, the solution will not be configured for social login. Lastly, you can specify the *Include Tests* option to include or exclude the test projects from the solution.
On the next screen, you can configure the modularity options for your solution:

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

@ -118,7 +118,7 @@ Click the Next button to see *Additional Options* selection:
![abp-studio-new-solution-dialog-additional-options](images/abp-studio-new-solution-dialog-additional-options-microservice.png)
If you unchecked the *Kubernetes Configuration* option, the solution will not include the Kubernetes configuration files which include the Helm charts and other Kubernetes related files. You can also specify *Social Logins*; if you uncheck this option, the solution will not be configured for social login. Lastly, you can specify the *Include Tests* option to include the test projects in the solution.
If you unchecked the *Kubernetes Configuration* option, the solution will not include the Kubernetes configuration files which includes the Helm charts and other Kubernetes-related files. You can also specify *Social Logins*; if you uncheck this option, the solution will not be configured for social login. Lastly, you can specify the *Include Tests* option to include the test projects in the solution.
Click the Next button to see *Additional Services* screen:
@ -126,6 +126,12 @@ Click the Next button to see *Additional Services* screen:
On that screen, allows you to include extra microservices in your ABP solution during the creation process. This feature lets you extend your solution with business-specific services right from the start.
Click the Next button to see *Admin Password* screen:
![abp-studio-new-solution-dialog-admin-password](images/abp-studio-new-solution-dialog-admin-password.png)
Here, you can set the initial password for the `admin` user of your application. By default, it is set to `1q2w3E*`, but you can change it to a more secure password of your choice.
Now, we are ready to allow ABP Studio to create our solution. Just click the *Create* button and let the ABP Studio do the rest for you. After clicking the *Create* button, the dialog is closed and your solution is loaded into ABP Studio:
![abp-studio-created-new-microservice-solution](images/abp-studio-created-new-microservice-solution.png)
@ -297,7 +303,7 @@ Clicking the *Connect* button will start a process that establishes the VPN conn
![abp-studio-microservice-kubernetes-services](images/abp-studio-microservice-kubernetes-services.png)
Now, you can access all the services inside the Kubernetes cluster, including the services those are not exposed out of the cluster. You can use the service name as DNS. For example, you can directly visit `http://cloudcrm-local-identity` in your Browser. You can also right-click to a service or application and select the Browse command to open it's UI in the built-in browser of ABP Studio:
Now, you can access all the services inside the Kubernetes cluster, including the services those are not exposed out of the cluster. You can use the service name as DNS. For example, you can directly visit `http://cloudcrm-local-identity` in your Browser. You can also right-click to a service or application and select the Browse command to open its UI in the built-in browser of ABP Studio:
![abp-studio-microservice-kubernetes-services-browse](images/abp-studio-microservice-kubernetes-services-browse.png)

6
docs/en/get-started/pre-requirements.md

@ -36,7 +36,7 @@ Visual Studio Code is a **free and cross-platform** lightweight code editor that
## .NET SDK
ABP is based on NET, so you need to install the .NET SDK. You can download the .NET SDK from the [.NET official website](https://dotnet.microsoft.com/en-us/download/dotnet/9.0).
ABP is based on .NET, so you need to install the .NET SDK. You can download the .NET SDK from the [.NET official website](https://dotnet.microsoft.com/en-us/download/dotnet/9.0).
> Installing Visual Studio or JetBrains Rider may automatically install the .NET SDK.
@ -56,7 +56,7 @@ dotnet tool update --global dotnet-ef
## Node.js
ABP projects include some frontend resource packages, so you need to install Node.js/NPM manage these resource packages. You can download Node.js from the [official Node.js website](https://nodejs.org/). We recommend installing version v20.11+.
ABP projects include some frontend resource packages, so you need to install Node.js/NPM to manage these resource packages. You can download Node.js from the [official Node.js website](https://nodejs.org/). We recommend installing version v20.11+.
## Yarn (Required Only for Angular Projects)
@ -89,7 +89,7 @@ ABP startup solution templates and tools use some PowerShell scripts (`*.ps1`) t
* [Install PowerShell on macOS](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos)
* [Install PowerShell on Linux](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux)
## MicroService Solution
## Microservice Solution
The following tools are only required to develop ABP's [microservice solution](../solution-templates/microservice/index.md)

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

@ -66,7 +66,7 @@ Once your configuration is done, click the *Next* button to navigate to the *UI
Here, you see all the possible UI options supported by that startup solution template. Pick the **{{ UI_Value }}**.
Notice that; Once you select a UI type, some additional options will be available under the UI Framework list. You can further configure the options or leave them as default and click the Next button for the *Database Provider* selection screen:
Notice that: Once you select a UI type, some additional options will be available under the UI Framework list. You can further configure the options or leave them as default and click the Next button for the *Database Provider* selection screen:
{{ if DB == "EF" }}
![abp-studio-new-solution-dialog-database-provider](images/abp-studio-no-layers-new-solution-dialog-database-provider-efcore_dark.png)
@ -110,7 +110,7 @@ In this step, you can choose which languages your application will support.
* Click Add Custom Language if you want to add a language that is not listed.
You can change these settings later if needed. Thenk click the *Next* button for the *Additional Options* page:
You can change these settings later if needed. Then click the *Next* button for the *Additional Options* page:
![abp-studio-no-layers-new-solution-additional-options](images/abp-studio-no-layers-new-solution-additional-options_dark.png)

BIN
docs/en/images/resource-based-permission.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

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

Loading…
Cancel
Save