Browse Source

Merge branch 'rel-10.1' into documentation/angular-dynamic-form-module

pull/24547/head
erdemcaygor 8 months ago
parent
commit
3169a65a29
  1. 8
      .claude/settings.local.json
  2. 2
      .gitignore
  3. 123
      Directory.Packages.props
  4. 151
      ai-rules/README.md
  5. 182
      ai-rules/common/abp-core.mdc
  6. 232
      ai-rules/common/application-layer.mdc
  7. 183
      ai-rules/common/authorization.mdc
  8. 90
      ai-rules/common/cli-commands.mdc
  9. 241
      ai-rules/common/ddd-patterns.mdc
  10. 151
      ai-rules/common/dependency-rules.mdc
  11. 291
      ai-rules/common/development-flow.mdc
  12. 244
      ai-rules/common/infrastructure.mdc
  13. 162
      ai-rules/common/multi-tenancy.mdc
  14. 254
      ai-rules/data/ef-core.mdc
  15. 203
      ai-rules/data/mongodb.mdc
  16. 79
      ai-rules/template-specific/app-nolayers.mdc
  17. 209
      ai-rules/template-specific/microservice.mdc
  18. 234
      ai-rules/template-specific/module.mdc
  19. 270
      ai-rules/testing/patterns.mdc
  20. 221
      ai-rules/ui/angular.mdc
  21. 207
      ai-rules/ui/blazor.mdc
  22. 258
      ai-rules/ui/mvc.mdc
  23. 4
      common.props
  24. 6
      docs/en/docs-nav.json
  25. 69
      docs/en/framework/architecture/microservices/index.md
  26. 13
      docs/en/framework/infrastructure/artificial-intelligence/microsoft-extensions-ai.md
  27. 25
      docs/en/framework/ui/angular/checkbox-component.md
  28. 22
      docs/en/framework/ui/angular/form-input-component.md
  29. 1
      docs/en/framework/ui/angular/form-validation.md
  30. 56
      docs/en/framework/ui/angular/how-replaceable-components-work-with-extensions.md
  31. 8
      docs/en/framework/ui/angular/quick-start.md
  32. 14
      docs/en/framework/ui/angular/ssr-configuration.md
  33. 5
      docs/en/framework/ui/angular/theming.md
  34. 8
      docs/en/framework/ui/mvc-razor-pages/modals.md
  35. 4
      docs/en/framework/ui/mvc-razor-pages/tag-helpers/index.md
  36. 2
      docs/en/framework/ui/mvc-razor-pages/theming.md
  37. 2
      docs/en/framework/ui/mvc-razor-pages/widgets.md
  38. 226
      docs/en/framework/ui/react-native/index.md
  39. 316
      docs/en/framework/ui/react-native/setting-up-android-emulator.md
  40. BIN
      docs/en/get-started/images/abp-studio-background-tasks.png
  41. BIN
      docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png
  42. BIN
      docs/en/get-started/images/abp-studio-created-new-microservice-solution.png
  43. BIN
      docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png
  44. BIN
      docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png
  45. BIN
      docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png
  46. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png
  47. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png
  48. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png
  49. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png
  50. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png
  51. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png
  52. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png
  53. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner.png
  54. BIN
      docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png
  55. BIN
      docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png
  56. BIN
      docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png
  57. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png
  58. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png
  59. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-admin-password.png
  60. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png
  61. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png
  62. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png
  63. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png
  64. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png
  65. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png
  66. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png
  67. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png
  68. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png
  69. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png
  70. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png
  71. BIN
      docs/en/get-started/images/abp-studio-open-module-folder.png
  72. BIN
      docs/en/get-started/images/abp-studio-welcome-screen.png
  73. 4
      docs/en/get-started/layered-web-application.md
  74. 10
      docs/en/get-started/microservice.md
  75. 6
      docs/en/get-started/pre-requirements.md
  76. 4
      docs/en/get-started/single-layer-web-application.md
  77. 409
      docs/en/modules/payment.md
  78. 137
      docs/en/release-info/index.md
  79. 52
      docs/en/release-info/migration-guides/abp-10-1.md
  80. 1
      docs/en/release-info/migration-guides/index.md
  81. 15
      docs/en/release-info/release-notes.md
  82. 20
      docs/en/release-info/road-map.md
  83. 2
      docs/en/release-info/upgrading.md
  84. 39
      docs/en/studio/concepts.md
  85. 128
      docs/en/studio/custom-commands.md
  86. BIN
      docs/en/studio/images/custom-commands/create-edit-command.png
  87. BIN
      docs/en/studio/images/custom-commands/management-window.png
  88. BIN
      docs/en/studio/images/monitoring-applications/tools-create.png
  89. 55
      docs/en/studio/kubernetes.md
  90. 271
      docs/en/studio/monitoring-applications.md
  91. 9
      docs/en/studio/release-notes.md
  92. 46
      docs/en/studio/running-applications.md
  93. 24
      docs/en/studio/solution-explorer.md
  94. 11
      docs/en/studio/working-with-suite.md
  95. 23
      docs/en/suite/generating-crud-page.md
  96. 6
      docs/en/testing/integration-tests.md
  97. 2
      docs/en/testing/overall.md
  98. 4
      docs/en/testing/unit-tests.md
  99. 8
      framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientAccessor.cs
  100. 6
      framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedChatClient.cs

8
.claude/settings.local.json

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

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

123
Directory.Packages.props

@ -30,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" />
@ -56,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" />
@ -140,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-rc" />
<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" />
@ -168,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" />
@ -193,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 |

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

@ -0,0 +1,232 @@
---
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.

183
ai-rules/common/authorization.mdc

@ -0,0 +1,183 @@
---
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

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

@ -0,0 +1,90 @@
---
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]` |

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

@ -0,0 +1,241 @@
---
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());
```

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

@ -0,0 +1,151 @@
---
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 |

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

@ -0,0 +1,291 @@
---
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

244
ai-rules/common/infrastructure.mdc

@ -0,0 +1,244 @@
---
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

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

@ -0,0 +1,162 @@
---
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");
});
```

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

@ -0,0 +1,254 @@
---
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
);
}
}
```

203
ai-rules/data/mongodb.mdc

@ -0,0 +1,203 @@
---
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

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

@ -0,0 +1,79 @@
---
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

270
ai-rules/testing/patterns.mdc

@ -0,0 +1,270 @@
---
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

221
ai-rules/ui/angular.mdc

@ -0,0 +1,221 @@
---
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`

207
ai-rules/ui/blazor.mdc

@ -0,0 +1,207 @@
---
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; }
}
```

258
ai-rules/ui/mvc.mdc

@ -0,0 +1,258 @@
---
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>

6
docs/en/docs-nav.json

@ -347,6 +347,10 @@
{
"text": "Working with ABP Suite",
"path": "studio/working-with-suite.md"
},
{
"text": "Custom Commands",
"path": "studio/custom-commands.md"
}
]
},
@ -1284,7 +1288,7 @@
},
{
"text": "LeptonX Lite",
"path": "ui-themes/lepton-x-lite/mvc.md"
"path": "ui-themes/lepton-x-lite/asp-net-core.md"
},
{
"text": "LeptonX",

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)

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/)

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:

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){

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)]

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/widgets.md

@ -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**:

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)

409
docs/en/modules/payment.md

@ -15,9 +15,9 @@ Payment module implements payment gateway integration of an application. It prov
See [the module description page](https://abp.io/modules/Volo.Payment) for an overview of the module features.
## How to install
## How to Install
Payment module is not installed in [the startup templates](../solution-templates/layered-web-application). So, it needs to be installed manually. There are two ways of installing a module into your application.
The Payment module is not installed in [the startup templates](../solution-templates/layered-web-application). So, it needs to be installed manually. There are two ways of installing a module into your application.
### Using ABP CLI
@ -30,7 +30,7 @@ abp add-module Volo.Payment
### Manual Installation
If you modified your solution structure, adding module using ABP CLI might not work for you. In such cases, payment module can be added to a solution manually.
If you modified your solution structure, adding a module using ABP CLI might not work for you. In such cases, the Payment module can be added to a solution manually.
In order to do that, add packages listed below to matching project on your solution. For example, ```Volo.Payment.Application``` package to your **{ProjectName}.Application.csproj** like below;
@ -47,7 +47,9 @@ After adding the package reference, open the module class of the project (eg: `{
)]
```
> If you are using Blazor Web App, you need to add the `Volo.Payment.Admin.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and ad the `Volo.Payment.Admin.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project.
> If you are using Blazor Web App, you need to add the `Volo.Payment.Admin.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and add the `Volo.Payment.Admin.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project.
> For Blazor UI public pages (payment gateway selection, pre-payment, and post-payment pages), see the [Blazor UI](#blazor-ui) section below for detailed installation and configuration instructions.
### Supported Gateway Packages
@ -55,7 +57,8 @@ In order to use a Payment Gateway, you need to add related NuGet packages to you
After adding packages of a payment gateway to your application, you also need to configure global payment module options and options for the payment modules you have added. See the Options section below.
### Creating a custom payment gateway
### Creating Custom Payment Gateways
If you require a different payment gateway than existing ones, you can create a custom payment gateway by your own. 2 steps are required to create a custom payment gateway. First is creating a payment gateway object that implements `IPaymentGateway`. This interface exposes core payment operations without any UI. Second step is creating UI for the payment gateway. This UI is used to redirect user to payment gateway and validate payment.
Follow the [instructions here](payment-custom-gateway) to create a custom payment gateway.
@ -66,27 +69,291 @@ This module follows the [module development best practices guide](../framework/a
You can visit [Payment module package list page](https://abp.io/packages?moduleName=Volo.Payment) to see list of packages related with this module.
## User interface
## User Interface
### Public Pages
The Payment module provides both **public pages** (for payment processing) and **admin pages** (for managing payment plans and requests). The UI is available for **MVC/Razor Pages**, **Blazor**, and **Angular** applications. See the UI-specific sections below for installation and configuration details.
#### Payment gateway selection
### MVC / Razor Pages UI
This page allows selecting a payment gateway. If there is one payment gateway configured for final application, this page will be skipped.
For MVC/Razor Pages applications, the `abp add-module Volo.Payment` command automatically adds the required packages (`Volo.Payment.Web` and gateway-specific Web packages) and the necessary `DependsOn` statements to your module. The only thing you need to do is configure `PaymentWebOptions` as explained in the [PaymentWebOptions](#paymentweboptions) section.
![payment-gateway-selection](../images/payment-gateway-selection.png)
### Blazor UI
#### PayU prepayment page
For Blazor applications, the `abp add-module Volo.Payment` command automatically adds the required packages (`Volo.Payment.Blazor.Server` or `Volo.Payment.Blazor.WebAssembly` and gateway-specific Blazor packages) and the necessary `DependsOn` statements to your module. The only thing you need to do is configure `PaymentBlazorOptions` as explained below.
This page is used to send Name, Surname and Email Address of user to PayU.
#### Installation
![payment-payu-prepayment-page](../images/payment-payu-prepayment-page.png)
> **Note:** If you used the `abp add-module Volo.Payment` command to install the Payment module, the following packages and module dependencies are automatically added to your project. You can skip to the [Gateway-Specific Blazor Packages](#gateway-specific-blazor-packages) section. The information below is provided for reference or manual installation scenarios.
To use the Payment module's public pages in a Blazor application, you need to install the core Blazor packages and the gateway-specific Blazor packages for each payment gateway you want to support.
##### Core Blazor Packages
For **Blazor Server** applications, add the following package to your **{ProjectName}.Blazor.Server.csproj** (or **{ProjectName}.Blazor.csproj** for Blazor Web App):
```json
<PackageReference Include="Volo.Payment.Blazor.Server" Version="x.x.x" />
```
For **Blazor WebAssembly** applications, add the following package to your **{ProjectName}.Blazor.csproj** (or **{ProjectName}.Blazor.Client.csproj** for Blazor Web App):
```json
<PackageReference Include="Volo.Payment.Blazor.WebAssembly" Version="x.x.x" />
```
#### Gateway-Specific Blazor Packages
Each payment gateway requires its own Blazor package. Add the packages for the gateways you want to support:
**Stripe:**
- Blazor Server: `Volo.Payment.Stripe.Blazor.Server`
- Blazor WebAssembly: `Volo.Payment.Stripe.Blazor.WebAssembly`
**PayPal:**
- Blazor Server: `Volo.Payment.PayPal.Blazor.Server`
- Blazor WebAssembly: `Volo.Payment.PayPal.Blazor.WebAssembly`
**PayU:**
- Blazor Server: `Volo.Payment.PayU.Blazor.Server`
- Blazor WebAssembly: `Volo.Payment.PayU.Blazor.WebAssembly`
**Iyzico:**
- Blazor Server: `Volo.Payment.Iyzico.Blazor.Server`
- Blazor WebAssembly: `Volo.Payment.Iyzico.Blazor.WebAssembly`
- HttpApi (required for callbacks): `Volo.Payment.Iyzico.HttpApi`
> **Important:** Iyzico requires the `Volo.Payment.Iyzico.HttpApi` package because Blazor cannot directly handle POST requests from external payment gateways. This package provides an API endpoint to receive the POST callback from Iyzico and redirect to the Blazor post-payment page.
**Alipay:**
- Blazor Server: `Volo.Payment.Alipay.Blazor.Server`
- Blazor WebAssembly: `Volo.Payment.Alipay.Blazor.WebAssembly`
**TwoCheckout:**
- Blazor Server: `Volo.Payment.TwoCheckout.Blazor.Server`
- Blazor WebAssembly: `Volo.Payment.TwoCheckout.Blazor.WebAssembly`
##### Module Dependencies
After adding the package references, add the module dependencies to your Blazor module class. For example, for a Blazor Server application with Stripe and PayPal:
```csharp
[DependsOn(
// ... other dependencies
typeof(AbpPaymentBlazorServerModule),
typeof(AbpPaymentStripeBlazorServerModule),
typeof(AbpPaymentPayPalBlazorServerModule)
)]
public class YourBlazorModule : AbpModule
{
// ...
}
```
For Blazor WebAssembly:
```csharp
[DependsOn(
// ... other dependencies
typeof(AbpPaymentBlazorWebAssemblyModule),
typeof(AbpPaymentStripeBlazorWebAssemblyModule),
typeof(AbpPaymentPayPalBlazorWebAssemblyModule)
)]
public class YourBlazorModule : AbpModule
{
// ...
}
```
#### Configuration
Configure `PaymentBlazorOptions` in your Blazor module's `ConfigureServices` method:
```csharp
Configure<PaymentBlazorOptions>(options =>
{
options.RootUrl = configuration["App:SelfUrl"];
options.CallbackUrl = configuration["App:SelfUrl"] + "/PaymentSucceed";
options.GatewaySelectionCheckoutButtonStyle = "btn btn-primary"; // Optional CSS class
});
```
You can also configure these options in your `appsettings.json` file:
```json
{
"Payment": {
"Blazor": {
"RootUrl": "https://localhost:44300",
"CallbackUrl": "https://localhost:44300/PaymentSucceed",
"GatewaySelectionCheckoutButtonStyle": "btn btn-primary"
}
}
}
```
##### Gateway-Specific Blazor Options
Each payment gateway has its own Blazor options for customizing the UI. These options can be configured in `appsettings.json`:
```json
{
"Payment": {
"Blazor": {
"Payu": {
"PrePaymentCheckoutButtonStyle": "btn btn-success",
"Recommended": true,
"ExtraInfos": ["Fast checkout", "Secure payment"]
},
"TwoCheckout": {
"Recommended": false,
"ExtraInfos": ["International payments"]
},
"PayPal": {
"Recommended": true,
"ExtraInfos": ["Pay with PayPal balance", "Buyer protection"]
},
"Stripe": {
"Recommended": true,
"ExtraInfos": ["Credit/Debit cards", "Apple Pay", "Google Pay"]
},
"Iyzico": {
"PrePaymentCheckoutButtonStyle": "btn btn-primary",
"Recommended": false,
"ExtraInfos": ["Turkish payment gateway"]
},
"Alipay": {
"PrePaymentCheckoutButtonStyle": "btn btn-info",
"Recommended": false,
"ExtraInfos": ["Chinese payment gateway", "CNY only"]
}
}
}
}
```
### Admin Pages
#### Creating Payments in Blazor
To initiate a payment in a Blazor component, inject `IPaymentRequestAppService` and `NavigationManager`, create a payment request, and navigate to the gateway selection page:
```csharp
@page "/"
@using Microsoft.AspNetCore.Components
@using Volo.Payment
@using Volo.Payment.Requests
@inject IPaymentRequestAppService PaymentRequestAppService
@inject NavigationManager NavigationManager
<button class="btn btn-primary" @onclick="StartPaymentAsync">
Pay Now
</button>
@code {
private async Task StartPaymentAsync()
{
var paymentRequest = await PaymentRequestAppService.CreateAsync(
new PaymentRequestCreateDto
{
Currency = "USD",
Products = new List<PaymentRequestProductCreateDto>
{
new PaymentRequestProductCreateDto
{
Code = "Product_01",
Name = "LEGO Super Mario",
Count = 2,
UnitPrice = 60,
TotalPrice = 120
}
}
});
NavigationManager.NavigateTo($"/Payment/GatewaySelection?paymentRequestId={paymentRequest.Id}");
}
}
```
##### Passing Extra Parameters
Some payment gateways require additional parameters. You can pass these as extra properties when creating the payment request:
```csharp
var paymentRequest = await PaymentRequestAppService.CreateAsync(
new PaymentRequestCreateDto
{
Currency = "USD",
Products = new List<PaymentRequestProductCreateDto>
{
new PaymentRequestProductCreateDto
{
Code = "Product_01",
Name = "LEGO Super Mario",
Count = 1,
UnitPrice = 60,
TotalPrice = 60
}
},
ExtraProperties = new ExtraPropertyDictionary
{
// For Iyzico - Customer information
{ "Name", "John" },
{ "Surname", "Doe" },
{ "Email", "john.doe@example.com" },
{ "Address", "123 Main St" },
{ "City", "Istanbul" },
{ "Country", "Turkey" },
{ "ZipCode", "34000" },
// For PayU - Customer information
{ "BuyerName", "John" },
{ "BuyerSurname", "Doe" },
{ "BuyerEmail", "john.doe@example.com" }
}
});
```
#### Handling the Callback (Optional)
When a user completes a payment on the external payment gateway, the following flow occurs:
1. The user is redirected to the **PostPayment page** (handled internally by the payment module)
2. The PostPayment page validates the payment with the gateway and updates the payment request status to **Completed**
3. If a `CallbackUrl` is configured in `PaymentBlazorOptions`, the user is then redirected to that URL with the `paymentRequestId` as a query parameter
Create a page to handle this callback and perform any application-specific actions:
```csharp
@page "/PaymentSucceed"
@using Microsoft.AspNetCore.WebUtilities
<h3>Payment Successful!</h3>
<p>Thank you for your purchase.</p>
<p>Payment Request ID: @PaymentRequestId</p>
@code {
[Parameter]
[SupplyParameterFromQuery]
public Guid? PaymentRequestId { get; set; }
protected override async Task OnInitializedAsync()
{
if (PaymentRequestId.HasValue)
{
// The payment is already completed at this point.
// Perform application-specific actions here:
// e.g., activate subscription, send confirmation email,
// update order status, grant access to purchased content, etc.
}
}
}
```
> **Note:** By the time the user reaches your callback page, the payment request status has already been set to **Completed** by the PostPayment page. Your callback page is for performing additional application-specific logic. It is also your responsibility to handle if a payment request is used more than once. If you have already delivered your product for a given `PaymentRequestId`, you should not deliver it again when the callback URL is visited a second time.
### Angular UI
#### Installation
For Angular applications, you need to read and apply the steps explained in the following sections:
#### Configurations
In order to configure the application to use the payment module, you first need to import `PaymentAdminConfigModule` from `@volo/abp.ng.payment/admin/config` to the root configuration. `PaymentAdminConfigModule` has a static `forRoot` method which you should call for a proper configuration:
@ -120,15 +387,35 @@ const APP_ROUTES: Routes = [
];
```
#### Payment plans page
### Pages
#### Public Pages
##### Payment Gateway Selection
This page allows selecting a payment gateway. If there is only one payment gateway configured for the application, this page will be skipped.
![payment-gateway-selection](../images/payment-gateway-selection.png)
##### PrePayment Page
Some payment gateways require additional information before redirecting to the payment gateway. For example, PayU and Iyzico require customer information (Name, Surname, Email Address, etc.) before processing the payment.
![payment-payu-prepayment-page](../images/payment-payu-prepayment-page.png)
#### Admin Pages
##### Payment Plans Page
Payment plans for subscriptions can be managed on this page. You can connect external subscriptions for each gateway to a plan.
![payment plans](../images/payment-plans.png)
![payment plans gateway plans](../images/payment-plans-gateway-plans.png)
#### Payment request list
This page lists all the payment request operations in application.
##### Payment Request List
This page lists all the payment request operations in the application.
![payment request list](../images/payment-request-list.png)
@ -171,7 +458,22 @@ Configure<PaymentOptions>(options =>
* ```PrePaymentUrl```: URL of the page before redirecting user to payment gateway for payment.
* ```PostPaymentUrl```: URL of the page when user redirected back from payment gateway to your website. This page is used to validate the payment mostly.
* ```Order```: Order of payment gateway for gateway selection page.
* ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
### PaymentBlazorOptions
```PaymentBlazorOptions``` is used to configure Blazor application related configurations. This is the Blazor equivalent of `PaymentWebOptions`.
* ```CallbackUrl```: Final callback URL for internal payment gateway modules to return. User will be redirected to this URL on your website after a successful payment.
* ```RootUrl```: Root URL of your Blazor application.
* ```GatewaySelectionCheckoutButtonStyle```: CSS style to add to the Checkout button on the gateway selection page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager.
* ```PaymentGatewayBlazorConfigurationDictionary```: Used to store Blazor related payment gateway configuration.
* ```Name```: Name of payment gateway.
* ```PrePaymentUrl```: URL of the Blazor page before redirecting user to payment gateway for payment.
* ```PostPaymentUrl```: URL of the Blazor page when user is redirected back from payment gateway to your website.
* ```Order```: Order of payment gateway for gateway selection page.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
### PayuOptions
@ -193,9 +495,17 @@ Configure<PaymentOptions>(options =>
```PayuWebOptions``` is used to configure PayU payment gateway web options.
* ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
* ```PrePaymentCheckoutButtonStyle```: Css style to add Checkout button on PayU prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager.
* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the PayU prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager.
### PayuBlazorOptions
```PayuBlazorOptions``` is used to configure PayU payment gateway Blazor options.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the PayU prepayment page.
### TwoCheckoutOptions
@ -210,7 +520,14 @@ Configure<PaymentOptions>(options =>
```TwoCheckoutWebOptions``` is used to configure TwoCheckout payment gateway web options.
* ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
### TwoCheckoutBlazorOptions
```TwoCheckoutBlazorOptions``` is used to configure TwoCheckout payment gateway Blazor options.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
### StripeOptions
@ -228,7 +545,14 @@ Configure<PaymentOptions>(options =>
```StripeWebOptions``` is used to configure Stripe payment gateway web options.
* ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
### StripeBlazorOptions
```StripeBlazorOptions``` is used to configure Stripe payment gateway Blazor options.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
### PayPalOptions
@ -245,7 +569,14 @@ Configure<PaymentOptions>(options =>
```PayPalWebOptions``` is used to configure PayPal payment gateway web options.
* ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
### PayPalBlazorOptions
```PayPalBlazorOptions``` is used to configure PayPal payment gateway Blazor options.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
### IyzicoOptions
@ -263,9 +594,17 @@ Configure<PaymentOptions>(options =>
```IyzicoWebOptions``` is used to configure Iyzico payment gateway web options.
* ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the Iyzico prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager.
### IyzicoBlazorOptions
```IyzicoBlazorOptions``` is used to configure Iyzico payment gateway Blazor options.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
* ```PrePaymentCheckoutButtonStyle```: CSS style to add Checkout button on Iyzico prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager.
* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the Iyzico prepayment page.
### AlipayOptions
@ -285,9 +624,17 @@ Configure<PaymentOptions>(options =>
#### AlipayWebOptions
* ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the Alipay prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager.
#### AlipayBlazorOptions
```AlipayBlazorOptions``` is used to configure Alipay payment gateway Blazor options.
* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page.
* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page.
* ```PrePaymentCheckoutButtonStyle```: CSS style to add Checkout button on Iyzico prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager.
* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the Alipay prepayment page.
> You can check the [Alipay document](https://opendocs.alipay.com/open/02np97) for more details.
@ -541,7 +888,7 @@ PrePayment page asks users for extra information if requested by the external pa
PostPayment page is responsible for validation of the response of the external payment gateway. When a user completes the payment, user is redirected to PostPayment page for that payment gateway and PostPayment page validates the status of the payment. If the payment is succeeded, status of the payment request is updated and user is redirected to main application.
Note: It is main application's responsibility to handle if a payment request is used more than one time. For example if PostPayment page generates a URL like https://mywebsite.com/PaymentSucceed?PaymentRequestId={PaymentRequestId}, this URL can be visited more than onec manually by end users. If you already delivered your product for a given PaymentRequestId, you shouldn't deliver it when this URL is visited second time.
Note: It is the main application's responsibility to handle if a payment request is used more than once. For example, if the PostPayment page generates a URL like https://mywebsite.com/PaymentSucceed?PaymentRequestId={PaymentRequestId}, this URL can be visited more than once manually by end users. If you have already delivered your product for a given PaymentRequestId, you shouldn't deliver it when this URL is visited a second time.
### Creating One-Time Payment
@ -563,7 +910,7 @@ public class IndexModel: PageModel
{
var paymentRequest = await _paymentRequestAppService.CreateAsync(new PaymentRequestCreateDto()
{
Currency= "USD",
Currency = "USD",
Products = new List<PaymentRequestProductCreateDto>()
{
new PaymentRequestProductCreateDto
@ -582,7 +929,7 @@ public class IndexModel: PageModel
}
```
If the payment is successful, payment module will return to configured ```PaymentWebOptions.CallbackUrl```. The main application can take necessary actions for a successful payment (Activating a user account, triggering a shipment start process etc...).
If the payment is successful, payment module will return to the configured ```PaymentWebOptions.CallbackUrl```. The main application can take necessary actions for a successful payment (activating a user account, triggering a shipment start process, etc.).
## Subscriptions

137
docs/en/release-info/index.md

@ -1,16 +1,135 @@
```json
//[doc-seo]
{
"Description": "Explore the latest ABP Framework release information, including notes, migration guides, and upgrading tips to enhance your development experience."
"Description": "Understand ABP Platform's versioning, release schedule, LTS support policy, and how we handle breaking changes to ensure smooth upgrades for your applications."
}
```
# Release Information
# Versioning & Releases
* [Release Notes](./release-notes.md)
* [Migration Guides](./migration-guides/index.md)
* [Road Map](./road-map.md)
* [Upgrading](./upgrading.md)
* [Preview Releases](./previews.md)
* [Nightly Releases](./nightly-builds.md)
* [Official Packages](https://abp.io/packages)
ABP Platform follows a predictable release cycle aligned with .NET releases. This document explains our versioning strategy, release schedule, support policy, and how we handle breaking changes.
## Our Commitment
As a framework you build upon, ABP must be both reliable and evolving — stable enough to trust for long-term projects, yet continuously improving with new features and the latest .NET advancements. To achieve this balance, we commit to:
* **Predictable releases**: Major versions annually, aligned with .NET releases
* **Long-term support**: Every major version receives 2 years of support
* **Smooth upgrades**: Comprehensive migration guides and tooling for version updates
* **Transparent communication**: Clear documentation of breaking changes and deprecations
## ABP Versioning
ABP version numbers indicate the level of changes that are introduced by the release. This use of [semantic versioning](https://semver.org/) helps you understand the potential impact of updating to a new version.
ABP version numbers have three parts: `major.minor.patch`. For example, version 10.1.2 indicates major version 10, minor version 1, and patch level 2.
The version number is incremented based on the level of change included in the release.
| Level of change | Details |
| --- | --- |
| **Major release** | Contains significant new features. Aligned with the new major .NET release. Some developer assistance is expected. You should check the [migration guide](migration-guides/index.md) and possibly refactor code to adapt to new APIs. |
| **Minor release** | Contains new features and improvements. Minor releases are generally backward-compatible; minimal developer assistance is expected, but you can optionally modify your applications to begin using new APIs and features. |
| **Patch release** | Low risk, bug fix and security patch release. No developer assistance is expected. |
> **Note:** ABP version is aligned with .NET version. For example, ABP 10.x runs on .NET 10, ABP 9.x runs on .NET 9.
### Preview Releases
We provide preview releases for each major and minor release so you can try new features before the stable release:
| Pre-release type | Details |
| --- | --- |
| **Release Candidate (RC)** | A release that is feature complete and in final testing. RC releases are indicated by a release tag appended with the `-rc` identifier, such as `10.1.0-rc.1`. |
| **Nightly builds** | The latest development builds published every weekday night. Nightly builds allow you to try the previous day's development. |
See the [Preview Releases](previews.md) and [Nightly Builds](nightly-builds.md) documents for more information.
## Release Frequency
We work toward a regular schedule of releases, so that you can plan and coordinate your updates with the continuing evolution of ABP and the .NET platform.
> **Note:** Dates are offered as general guidance and are subject to change.
In general, expect the following release cycle:
* **A major release once a year**, typically in November, following the new major .NET release
* **2-4 minor releases** for each major version, released every ~3 months after the major release
* **Patch releases** as needed, typically every 2-4 weeks for the latest minor version
This cadence of releases gives eager developers access to new features as soon as they are fully developed and tested, while maintaining the stability and reliability of the platform for production users.
### Release Schedule
| Version | Status | Released | Active Ends | LTS Ends |
| --- | --- | --- | --- | --- |
| ^10.0.0 | Active | 2025-11 | 2026-11 | 2027-11 |
| ^9.0.0 | LTS | 2024-11 | 2025-11 | 2026-11 |
See the [Release Notes](release-notes.md) for detailed information about each release.
## Support Policy and Schedule (LTS)
ABP Platform follows a **Long-Term Support (LTS)** policy to ensure your applications remain secure and stable over time.
### Support Window
Every major version has a **2-year lifecycle** with two distinct phases:
| Support Stage | Duration | Details |
| --- | --- | --- |
| **Active** | ~1 year | The version is under active development. Regularly-scheduled updates and patches are released. New features and improvements are added in minor versions. |
| **Long-Term Support (LTS)** | ~1 year | Only critical fixes and security patches are released. No new features are added. |
This means we actively develop a major version for about 1 year (until the next major .NET release), then provide LTS support for another year.
### LTS Fixes
As a general rule, a fix is considered for an LTS version if it resolves one of:
* A newly identified security vulnerability
* A critical bug that significantly impacts production applications
* A regression caused by a 3rd party change, such as a new browser version or dependency update
## Deprecation Policy
When the ABP team intends to remove an API or feature, it will be marked as *deprecated*. This occurs when an API is obsolete, superseded by another API, or otherwise discontinued. Deprecated APIs remain available through their deprecated phase, which lasts a minimum of one major version (approximately one year).
To help ensure that you have sufficient time and a clear path to update, this is our deprecation policy:
| Deprecation Stage | Details |
| --- | --- |
| **Announcement** | We announce deprecated APIs and features in the [release notes](release-notes.md) and [migration guides](migration-guides/index.md). Deprecated APIs are typically marked with `[Obsolete]` attribute in the code, which enables IDEs to provide warnings if your project depends on them. We also announce a recommended update path. |
| **Deprecation period** | When an API or feature is deprecated, it is still present in at least the next major release. After that, deprecated APIs and features are candidates for removal. A deprecation can be announced in any release, but the removal of a deprecated API or feature happens only in major releases. |
| **NuGet/NPM dependencies** | We typically make dependency updates that require changes to your applications in major releases. In minor releases, we may update dependencies by expanding the supported versions, but we try not to require projects to update these dependencies until the next major version. |
## Breaking Changes Policy
Breaking changes require you to do work because the state after the change is not backward compatible with the state before it. Examples of breaking changes include the removal of public APIs, changes to method signatures, changing the timing of events, or updating to a new version of a dependency that includes breaking changes itself.
### How We Handle Breaking Changes
To support you in case of breaking changes:
* We follow our [deprecation policy](#deprecation-policy) before we remove a public API
* We provide detailed [migration guides](migration-guides/index.md) when a version includes breaking changes
### Update Path
We recommend updating one major version at a time for a smoother upgrade experience. For example, to update from version 8.x to version 10.x:
1. Update from version 8.x to version 9.x
2. Update from version 9.x to version 10.x
See the [upgrading](upgrading.md) document for detailed instructions on how to upgrade your solutions.
## Related Documents
* [Release Notes](release-notes.md) - Detailed release notes for each version
* [Migration Guides](migration-guides/index.md) - Step-by-step guides for upgrading between versions
* [Road Map](road-map.md) - Upcoming features and planned releases
* [Upgrading](upgrading.md) - How to upgrade your ABP-based solutions
* [Preview Releases](previews.md) - Information about preview/RC releases
* [Nightly Builds](nightly-builds.md) - How to use nightly builds
* [Official Packages](https://abp.io/packages) - Browse all ABP packages

52
docs/en/release-info/migration-guides/abp-10-1.md

@ -0,0 +1,52 @@
```json
//[doc-seo]
{
"Description": "Upgrade your ABP solutions from v10.0 to v10.1 with this comprehensive migration guide, ensuring compatibility and new features with ABP v10.1."
}
```
# ABP Version 10.1 Migration Guide
This document is a guide for upgrading ABP v10.0 solutions to ABP v10.1. There are some changes in this version that may affect your applications. Please read them carefully and apply the necessary changes to your application.
## Open-Source (Framework)
### Add New EF Core Migrations for Password History/User Passkey Entities
In this version, we added password history/ user passkeys support to the [Identity PRO Module](../../modules/identity-pro.md) to enhance security compliance. A new `IdentityUserPasswordHistory` entity has been added to store previous password hashes, preventing users from reusing recent passwords. Additionally, we have introduced an `IdentityUserPasskey `entity to support passkey-based authentication.
**You need to create a new EF Core migration and apply it to your database** after upgrading to ABP 10.1.
> See [#23894](https://github.com/abpframework/abp/pull/23894) for more details.
## PRO
> Please check the **Open-Source (Framework)** section before reading this section. The listed topics might affect your application and you might need to take care of them.
If you are a paid-license owner and using the ABP's paid version, then please follow the following sections to get informed about the breaking changes and apply the necessary ones:
### AI Management Module: `Workspace` Entity Base Class Changed
In this version, the `Workspace` entity in the [AI Management Module](../../modules/ai-management/index.md) has been changed from `FullAuditedAggregateRoot` to `AuditedAggregateRoot` as the base class.
**This change removes support for soft deletion and related auditing features. If you are using the AI Management module, you need to create a new EF Core migration and apply it to your database.**
> **Important:** If you have soft-deleted Workspaces in your database, they will become visible after this update. You may need to create a migration script to clean up already deleted records before applying the migration.
### CMS Kit Pro Module: Dynamic FAQ Group Management
In this version, the FAQ group system in the [CMS Kit Pro Module](../../modules/cms-kit-pro/faq.md) has been redesigned to support dynamic group management. FAQ groups are now **first-class entities** stored in the database, replacing the previous static configuration approach.
**Key Changes:**
- A new `FaqGroup` entity has been introduced with unique names for FAQ groups
- The `FaqSection` entity now uses `GroupId` (Guid) instead of `GroupName` (string) _(GroupName is deprecated and will be removed soon. Use GroupId instead.)_
- Static FAQ group configuration (`FaqOptions.SetGroups`) has been removed
**Migration Steps:**
1. **Remove static group configuration** from your code (e.g., `Configure<FaqOptions>(options => { options.SetGroups([...]); })`)
2. **Create a new EF Core migration and apply it to your database**
3. **Run the one-time data migration seeder** to migrate existing FAQ sections to the new group entity model
> **Note:** If you have existing FAQ data, you may need to create a data seeder to migrate your existing group associations to the new entity-based model.

1
docs/en/release-info/migration-guides/index.md

@ -9,6 +9,7 @@
The following documents explain how to migrate your existing ABP applications. We write migration documents only if you need to take an action while upgrading your solution. Otherwise, you can easily upgrade your solution using the [abp update command](../upgrading.md).
- [10.0 to 10.1](abp-10-1.md)
- [9.x to 10.0](abp-10-0.md)
- [9.2 to 9.3](abp-9-3.md)
- [9.x to 9.2](abp-9-2.md)

15
docs/en/release-info/release-notes.md

@ -14,8 +14,23 @@ Also see the following notes about ABP releases:
* [ABP Studio release notes](../studio/release-notes.md)
* [Change logs for ABP pro packages](https://abp.io/pro-releases)
## 10.1 (2026-01-06)
> This is currently a RC (release-candidate) and you can see the detailed **[blog post / announcement](https://abp.io/community/announcements/announcing-abp-10-1-release-candidate-cyqui19d)** for the v10.1 release.
* Resource-Based Authorization
* Introducing the [TickerQ Background Worker Provider](../framework/infrastructure/background-workers/tickerq.md)
* Angular UI: Version Upgrade to **v21**
* [File Management Module](../modules/file-management.md): Public File Sharing Support
* [Payment Module](../modules/payment.md): Public Page Implementation for Blazor & Angular UIs
* [AI Management Module](../modules/ai-management/index.md) for Blazor & Angular UIs
* [Identity PRO Module](../modules/identity-pro.md): Password History Support
* [Account PRO Module](../modules/account-pro.md): Introducing WebAuthn Passkeys
## 10.0 (2025-11-18)
> **Note**: ABP has upgraded to .NET 10.0, so if you plan to use ABP 10.0, you’ll need to migrate your solutions to .NET 10.0. You can refer to the [Migrate from ASP.NET Core 9.0 to 10.0](https://learn.microsoft.com/en-us/aspnet/core/migration/90-to-100) documentation for guidance. However, ABP’s NuGet packages are compatible with both .NET 9 and .NET 10, allowing developers to continue using .NET 9 while still enjoying the latest features and improvements of the ABP Framework without upgrading their SDK.
See the detailed **[blog post / announcement](https://abp.io/community/announcements/abp.io-platform-10.0-final-has-been-released-spknn925)** for the v10.0 release.
* Upgraded to .NET 10.0

20
docs/en/release-info/road-map.md

@ -1,7 +1,7 @@
```json
//[doc-seo]
{
"Description": "Explore the ABP Platform Road Map for insights on upcoming features, release schedules, and improvements in version 9.1, launching January 2025."
"Description": "Explore the ABP Platform Road Map for insights on upcoming features, release schedules, and improvements in version 10.1, launching January 2026."
}
```
@ -11,33 +11,35 @@ This document provides a road map, release schedule, and planned features for th
## Next Versions
### v10.1
### v10.2
The next version will be 10.1 and planned to release the stable 10.1 version in January 2026. We will be mostly working on the following topics:
The next version will be 10.2 and planned to release the stable 10.2 version in April 2026. We will be mostly working on the following topics:
* Framework
* OpenTelemetry Protocol Support for 3rd-party Integrations
* Resource Based Authorization Integration
* Resource-Based Authorization Improvements
* Handle datetime/timezon in `AbpExtensibleDataGrid` Component
* Upgrading 3rd-party Dependencies
* Enhancements in the Core Points
* ABP Suite
* Define Navigation Properties Without Target String Property Dependency
* Creating enums on-the-fly (without needing to create manually on the code side)
* Improvements on the generated codes for nullability
* Improvements on Master-Detail Page Desing (making it more compact)
* Improvements One-To-Many Scenarios
* File Upload Modal Enhancements
* ABP Studio
* Allow to Directly Create New Solutions with ABP's RC (Release Candidate) Versions
* Integrate AI Management Module with all solution templates and UIs
* Automate More Details on New Service Creation for a Microservice Solution
* Allow to Download ABP Samples from ABP Studio
* Task Panel Enhancements (and Documentation)
* Task Panel Documentation
* Support Multiple Concurrent Kubernetes Deployment/Integration Scenarios
* Improve the Module Installation Experience / Installation Guides
* Application Modules
* Payment Module: Public Page Implementation (for Blazor & Angular UIs)
* AI Management Module: UI Implementation for Blazor & Angular UIs
* AI Management: MCP & RAG Supports
* File Management: Using Resource-Based Permission (on file-sharing and more...)
* CMS Kit: Enhancements for Some Features (Rating, Dynamic Widgets, FAQ and more...)
* UI/UX Improvements on Existing Application Modules

2
docs/en/release-info/upgrading.md

@ -21,6 +21,8 @@ Run this command in the terminal while you are in the root folder of your soluti
> If your solution has the Angular UI, you probably have `aspnet-core` and `angular` folders in the solution. Run this command in the parent folder of these two folders.
You can also specify a target version with `--version` parameter. See the [ABP CLI update command](../cli/index.md#update) for all available options.
### Database Migrations
> Warning: Be careful if you are migrating your database since you may have data loss in some cases. Carefully check the generated migration code before executing it. It is suggested to take a backup of your current database.

39
docs/en/studio/concepts.md

@ -43,6 +43,45 @@ An ABP Studio module is a sub-solution that contains zero, one or multiple packa
An ABP Studio Package typically matches to a .NET project (`csproj`).
### Metadata
Metadata is a collection of key-value pairs that provide additional information for various ABP Studio features. Metadata follows a hierarchical structure where values defined at lower levels override those at higher levels:
**Hierarchy (from highest to lowest priority):**
1. **Helm Chart Metadata** - Defined in chart properties (Kubernetes context only)
2. **Kubernetes Profile Metadata** / **Run Profile Metadata** - Defined in profile settings (context-dependent)
3. **Solution Metadata** - Defined via *Solution Explorer* → right-click solution → *Manage Metadata*
4. **Global Metadata** - Defined via *Tools* → *Global Metadata*
**Common Metadata Keys:**
| Key | Description | Used By |
|-----|-------------|---------|
| `k8ssuffix` | Appends a suffix to Kubernetes namespace (e.g., for multi-developer scenarios) | Kubernetes integration |
| `dotnetEnvironment` | Specifies the .NET environment (e.g., `Development`, `Staging`) | Helm chart installation |
| `projectPath` | Path to the project for Docker image building | Docker image build |
| `imageName` | Docker image name | Docker image build |
| `projectType` | Project type (`dotnet` or `angular`) | Docker image build |
> Metadata defined in *Global Metadata* is available for all solutions but will not be shared with team members. Metadata defined at *Solution* or *Profile* level will be shared through solution files.
### Secrets
Secrets are key-value pairs designed for storing sensitive information such as passwords, API keys, and connection strings. Unlike metadata, secrets are stored in the local file system and are not included in solution files for security reasons.
**Hierarchy (from highest to lowest priority):**
1. **Kubernetes Profile Secrets** / **Run Profile Secrets** - Defined in profile settings (context-dependent)
2. **Solution Secrets** - Defined via *Solution Explorer* → right-click solution → *Manage Secrets*
3. **Global Secrets** - Defined via *Tools* → *Global Secrets*
**Common Secret Keys:**
| Key | Description | Used By |
|-----|-------------|---------|
| `wireGuardPassword` | Password for WireGuard VPN connection to Kubernetes cluster | Kubernetes integration |
> Secrets are stored locally and are not shared with team members by default. Each developer needs to configure their own secrets.
## ABP Studio vs .NET Terms
Some ABP Studio terms may seem conflict with .NET and Visual Studio. To make them even more clear, you can use the following table.

128
docs/en/studio/custom-commands.md

@ -0,0 +1,128 @@
```json
//[doc-seo]
{
"Description": "Learn how to create and manage custom commands in ABP Studio to automate build, deployment, and other workflows."
}
```
# Custom Commands
````json
//[doc-nav]
{
"Next": {
"Name": "Working with ABP Suite",
"Path": "studio/working-with-suite"
}
}
````
Custom commands allow you to define reusable terminal commands that appear in context menus throughout ABP Studio. You can use them to automate repetitive tasks such as building Docker images, installing Helm charts, running deployment scripts, or executing any custom workflow.
> **Note:** This is an advanced feature primarily intended for teams working with Kubernetes deployments or complex build/deployment workflows. If you're developing a standard application without custom DevOps requirements, you may not need this feature.
## Opening the Management Window
To manage custom commands, right-click on the solution root in *Solution Explorer* and select *Manage Custom Commands*.
![Custom Commands Management Window](images/custom-commands/management-window.png)
The management window displays all defined commands with options to add, edit, or delete them.
## Creating a New Command
Click the *Add New Command* button to open the command editor dialog.
![Create/Edit Custom Command](images/custom-commands/create-edit-command.png)
## Command Properties
| Property | Description |
|----------|-------------|
| **Command Name** | A unique identifier for the command (used internally) |
| **Display Name** | The text shown in context menus |
| **Terminal Command** | The PowerShell command to execute. Use `&&&` to chain multiple commands |
| **Working Directory** | Optional. The directory where the command runs (relative to solution path) |
| **Condition** | Optional. A [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) expression that determines when the command is visible |
| **Require Confirmation** | When enabled, shows a confirmation dialog before execution |
| **Confirmation Text** | The message shown in the confirmation dialog |
## Trigger Targets
Trigger targets determine where your command appears in context menus. You can select multiple targets for a single command.
| Target | Location |
|--------|----------|
| **Helm Charts Root** | *Kubernetes* panel > *Helm* tab > root node |
| **Helm Main Chart** | *Kubernetes* panel > *Helm* tab > main chart |
| **Helm Sub Chart** | *Kubernetes* panel > *Helm* tab > sub chart |
| **Kubernetes Service** | *Kubernetes* panel > *Kubernetes* tab > service |
| **Solution Runner Root** | *Solution Runner* panel > profile root |
| **Solution Runner Folder** | *Solution Runner* panel > folder |
| **Solution Runner Application** | *Solution Runner* panel > application |
## Execution Targets
Execution targets define where the command actually runs. This enables cascading execution:
- When you trigger a command from a **root or parent item**, it can recursively execute on all matching children
- For example: trigger from *Helm Charts Root* with execution target *Helm Sub Chart* → the command runs on each sub chart
## Template Variables
Commands support [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) template syntax for dynamic values. Use `{%{{{variable}}}%}` to insert context-specific data.
### Available Variables by Context
**Helm Charts:**
| Variable | Description |
|----------|-------------|
| `profile.name` | Kubernetes profile name |
| `profile.namespace` | Kubernetes namespace |
| `chart.name` | Current chart name |
| `chart.path` | Chart directory path |
| `metadata.*` | Hierarchical metadata values (e.g., `metadata.imageName`) |
| `secrets.*` | Secret values (e.g., `secrets.registryPassword`) |
**Kubernetes Service:**
| Variable | Description |
|----------|-------------|
| `name` | Service name |
| `profile.name` | Kubernetes profile name |
| `profile.namespace` | Kubernetes namespace |
| `mainChart.name` | Parent main chart name |
| `chart.name` | Related sub chart name |
| `chart.metadata.*` | Chart-specific metadata |
**Solution Runner (Root, Folder, Application):**
| Variable | Description |
|----------|-------------|
| `profile.name` | Run profile name |
| `profile.path` | Profile file path |
| `application.name` | Application name (Application context only) |
| `application.baseUrl` | Application URL (Application context only) |
| `folder.name` | Folder name (Folder/Application context) |
| `metadata.*` | Profile metadata values |
| `secrets.*` | Profile secret values |
## Example: Build Docker Image
Here's an example command that builds a Docker image for Helm charts:
**Command Properties:**
- **Command Name:** `buildDockerImage`
- **Display Name:** `Build Docker Image`
- **Terminal Command:** `./build-image.ps1 -ProjectPath {%{{{metadata.projectPath}}}%} -ImageName {%{{{metadata.imageName}}}%}`
- **Working Directory:** `etc/helm`
- **Trigger Targets:** Helm Charts Root, Helm Main Chart, Helm Sub Chart
- **Execution Targets:** Helm Main Chart, Helm Sub Chart
- **Condition:** `{%{{{metadata.projectPath}}}%}`
This command:
1. Appears in the context menu of Helm charts root and all chart nodes
2. Executes on main charts and sub charts (cascading from root if triggered there)
3. Only shows for charts that have `projectPath` metadata defined
4. Runs the `build-image.ps1` script with dynamic parameters from metadata

BIN
docs/en/studio/images/custom-commands/create-edit-command.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
docs/en/studio/images/custom-commands/management-window.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/studio/images/monitoring-applications/tools-create.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

55
docs/en/studio/kubernetes.md

@ -11,8 +11,8 @@
//[doc-nav]
{
"Next": {
"Name": "Working with ABP Suite",
"Path": "studio/working-with-suite"
"Name": "Custom Commands",
"Path": "studio/custom-commands"
}
}
````
@ -100,7 +100,7 @@ It is the root of all subcharts. When you add a new main chart to the root, it i
- `Install Chart(s)`: Installs the selected chart to the current profile.
- `Uninstall Chart(s)`: Uninstalls the selected chart from the current profile.
- `Properties`: It opens the *Chart Properties* window. You can see the chart information in the *Chart Info* tab. In the *Metadata* tab, you can add metadata for the selected main chart. It overrides the metadata in the profile. In the *Kubernetes Services* tab, you can relate a Kubernetes service with the main chart; however, since the main chart usually doesn't create kubernetes service, we can leave it empty.
- `Refrest Sub Charts`: Refreshes the subcharts of the selected main chart.
- `Refresh Sub Charts`: Refreshes the subcharts of the selected main chart.
- `Open With`: You can open the selected chart with *Visual Studio Code* or *File Explorer*.
- `Remove`: Removes the selected main chart from the solution.
@ -137,6 +137,11 @@ While connected, changing the current profile is not possible. Existing applicat
![connected](./images/kubernetes/connected.png)
When connected, you can right-click on a Kubernetes service to see the following context menu options:
- `Browse`: Opens the [browser](./monitoring-applications.md#browse) and navigates to the Kubernetes service URL. This option is only visible if the service has a related Helm chart with matching *Kubernetes Services* regex pattern.
- `Enable Interception` / `Disable Interception`: Enables or disables traffic interception for the selected service. See the [Intercept a Service](#intercept-a-service) section for more details.
When you are connecting to a Kubernetes cluster, it automatically installs the WireGuard VPN to the Kubernetes cluster for a safe connection. You can specify the *wireGuardPassword* in the *Kubernetes Profile* -> *Secrets* tab or at a higher level such as *Solution Secrets* or *Global Secrets*. If you don't provide a password, it generates a random password and stores it in the *Kubernetes Profile* -> *Secrets*. However, if you try to connect to a cluster that already installed WireGuard VPN, then you should give the same password; otherwise, it won't connect. To see the random password, you can click the *eye* icon in the *Kubernetes Profile* -> *Secrets* tab.
![wireGuardPassword](./images/kubernetes/wireGuardPassword.png)
@ -205,46 +210,12 @@ When you connect to a Kubernetes cluster, it uses the selected profile for Kuber
## Advanced Topics
### Adding a Custom Command
Custom commands can be added to both the *Helm* and *Kubernetes* tabs within the *Kubernetes* panel. For instance, when [redeploy](#redeploy-a-chart) a chart, it involves building the Docker image and reinstalling it. However, if you are working with a different Kubernetes cluster than Docker Desktop, you'll need to push the Docker image to the registry before the installation process. This can be achieved by incorporating a custom command into the *Kubernetes services*. Custom commands can be added to the *Chart Root*, *Main Chart*, and *Subchart* in the *Helm* tab, as well as to the *Service* in the *Kubernetes* tab.
To do that, open the ABP Solution (*.abpsln*) file with *Visual Studio Code* it's a JSON file and you'll see the existing commands in the `commands` section. Before adding a new command, create a powershell script in the `abp-solution-path/etc/helm` folder. For example, we create a `push-image.ps1` script to push the docker image to the registry. Then, add the following command to the `commands` section.
```JSON
"kubernetesRedeployWithPushImage": {
"triggerTargets": [
"KUBERNETES_SERVICE"
],
"executionTargets": [
"KUBERNETES_SERVICE"
],
"displayName": " Redeploy with Push Image",
"workingDirectory": "etc/helm",
"terminalCommand": "./build-image.ps1 -ProjectPath {%{{{chart.metadata.projectPath}}}%} -ImageName {%{{{chart.metadata.imageName}}}%} -ProjectType {%{{{chart.metadata.projectType}}}%} &&& ./push-image.ps1 -ImageName {%{{{chart.metadata.imageName}}}%} &&& ./install.ps1 -ChartName {%{{{mainChart.name}}}%} -Namespace {%{{{profile.namespace}}}%} -ReleaseName {%{{{mainChart.name}}}%}-{%{{{profile.name}}}%} -DotnetEnvironment {%{{{mainChart.metadata.dotnetEnvironment}}}%}",
"requireConfirmation": "true",
"confirmationText": "Are you sure to redeploy with push image the related chart '{%{{{chart.name}}}%}' for the service '{%{{{name}}}%}'?",
"condition": "{%{{{chart != null && chart.metadata.projectPath != null && chart.metadata.imageName != null && chart.metadata.projectType != null}}}%}"
}
```
### Custom Commands
You can add custom commands to context menus in both the *Helm* and *Kubernetes* tabs. This is useful for automating workflows like pushing Docker images to a registry before installation, or running custom deployment scripts.
Once the command is added, reload the solution from *File* -> *Reload Solution* in the toolbar. After reloading, you will find the *Redeploy with Push Image* command in the context-menu of the service.
Custom commands can be added to the *Chart Root*, *Main Chart*, and *Sub Chart* in the *Helm* tab, as well as to the *Service* in the *Kubernetes* tab.
![redeploy-push-image](./images/kubernetes/redeploy-push-image.png)
The JSON object has the following properties:
- `triggerTargets`: Specifies the trigger targets for the command. The added command will appear in these targets. You can add one or more trigger targets, accepting values such as *HELM_CHARTS_ROOT*, *HELM_MAIN_CHART*, *HELM_SUB_CHART* and *KUBERNETES_SERVICE*.
- `executionTargets`: Specifies the execution targets for the command. When executing the command on a root item, it will recursively execute the command for all children. Acceptable values include *HELM_CHARTS_ROOT*, *HELM_MAIN_CHART*, *HELM_SUB_CHART*, and *KUBERNETES_SERVICE*.
- `displayName`: Specifies the display name of the command.
- `workingDirectory`: Specifies the working directory of the command. It's relative to the solution path.
- `terminalCommand`: Specifies the terminal command for the custom command. The `&&&` operator can be used to run multiple commands in the terminal. Utilize the [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) syntax to access input data, which varies based on the execution target.
- `requireConfirmation`: Specifies whether the command requires confirmation message before execution. Acceptable values include *true* and *false*.
- `confirmationText`: Specifies the confirmation text for the command. Utilize the [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) syntax to access input data, which varies based on the execution target.
- `condition`: Specifies the condition for the command. If the condition returns *false*, it skips the current item and attempts to execute the command for the next item or child item. Utilize the [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) syntax to access input data, which varies based on the execution target.
You can use the following variables in the scriban syntax based on the execution target:
- `HELM_CHARTS_ROOT`: *profile*, *metadata*, *secrets*
- `HELM_MAIN_CHART`: *profile*, *chart*, *metadata*, *secret*
- `HELM_SUB_CHART`: *profile*, *chart*, *metadata*, *secret*
- `KUBERNETES_SERVICE`: *name*, *profile*, *mainChart*, *chart*, *metadata*, *secret*
For detailed information on creating and managing custom commands, see the [Custom Commands](custom-commands.md) documentation.

271
docs/en/studio/monitoring-applications.md

@ -66,91 +66,318 @@ In this tab, you can view comprehensive overall information. You have the option
![overall](./images/monitoring-applications/overall.png)
In the data grid, details for each application are displayed. It's possible to sort rows by columns. When selecting a row, you can right-click to access the context menu, offering various actions. This menu allows for opening related tabs that are filtered by the selected application.
In the data grid, details for each application are displayed. It's possible to sort rows by columns.
- `Name`: The name of the application.
- `State`: The state of the application. It can take on several values such as *Scheduled*, *Starting*, *Started*, *Stopping* and *Stopped*. In the event of an application crash during its starting, the state is mark as *Scheduled*, we can cancel the starting process at that stage.
- `Health` : The health state of the application. Clicking on the icon shows the latest health check response. Displays `N/A` if the application is not running or health check is not configured for the application.
- `Instances`: Indicates the count of running instances for the application. This value is particularly helpful when scaling the application within a Kubernetes, providing visibility into the number of currently active instances.
- `State`: The state of the application. It can take on several values such as *Scheduled*, *Starting*, *Started*, *Stopping* and *Stopped*. The *Scheduled* state indicates the application is waiting for an automatic restart (e.g., after a crash or when watch mode detects changes). You can cancel the scheduled restart at this stage.
- `Health`: The health state of the application. The icon indicates the current health status: *Healthy* (green), *Unhealthy* (red), *Degraded* (yellow), or *Unknown* (gray). Clicking on the icon shows the latest health check response in JSON format. Displays `N/A` if the application is not running or health check is not configured for the application.
- `Instances`: Indicates the count of running instances for the application. This value is particularly helpful when scaling the application within a Kubernetes cluster, providing visibility into the number of currently active pods.
- `Uptime`: The time elapsed since the application started.
- `Requests`: The number of HTTP requests received by the application.
- `Events (R/S)`: The number of [Distributed Event](../framework/infrastructure/event-bus/distributed) received or sent by the application.
- `Events (R/S)`: The number of [Distributed Events](../framework/infrastructure/event-bus/distributed) received or sent by the application.
- `Exceptions`: The number of exceptions thrown by the application.
- `Actions`: The actions that can be performed on the application. You can start and stop the application.
> For the events system, you can exclusively view the [Distributed Events](../framework/infrastructure/event-bus/distributed). Generally, the [Local Events](../framework/infrastructure/event-bus/distributed) is not included.
### Context Menu Actions
When selecting a row, you can right-click to access the context menu with the following actions:
| Action | Description |
|--------|-------------|
| **Start / Stop** | Start or stop the selected application. |
| **Restart** | Restart the application (available when the application is running). |
| **Build** | Build options including *Build*, *Graph Build*, *Restore*, and *Clean* (available for C# applications when stopped). |
| **Browse** | Open the application in the [Browse](#browse) tab (available when a Launch URL is configured). |
| **Health Status** | Submenu with *Browse Health UI* and *Show Latest Health Check Response* options. |
| **Requests** | Open the [HTTP Requests](#http-requests) tab filtered by this application. |
| **Exceptions** | Open the [Exceptions](#exceptions) tab filtered by this application. |
| **Logs** | Open the [Logs](#logs) tab with this application selected. |
| **Copy Url** | Copy the application's URL to the clipboard. |
| **Properties** | Open the application properties dialog. |
> For the events system, you can exclusively view the [Distributed Events](../framework/infrastructure/event-bus/distributed). Generally, the [Local Events](../framework/infrastructure/event-bus/local) are not included.
## Browse
ABP Studio includes a browser tool that allows access to websites and running applications. You can open new tabs to browse different websites or view active applications. It's a convenient utility to access websites and applications without leaving ABP Studio. Clicking the *Browse* tab displays the running applications and an *Open new tab* button.
ABP Studio includes a built-in browser that allows access to websites and running applications. You can open new tabs to browse different websites or view active applications. It's a convenient utility to access websites and applications without leaving ABP Studio. Clicking the *Browse* tab displays the running applications and an *Open new tab* button.
![browse](./images/monitoring-applications/browse.png)
You can open the *Browse* tabs as many times as you want. It's possible to open the same application in several tabs simultaneously. To open an application, navigate through *Solution Runner* -> *C# or CLI Application* -> *Browse*. This option is only visible when there is a [Launch URL](./running-applications.md#properties). Additionally, you can access any URL by entering it into the address bar.
You can open the *Browse* tabs as many times as you want. It's possible to open the same application in several tabs simultaneously. To open an application, you can:
- Double-click on an application in the *Solution Runner* tree.
- Right-click on an application and select *Browse* from the context menu.
- Click on a running application in the application list shown in the *Browse* tab.
These options are only available when the application has a [Launch URL](./running-applications.md#properties) configured. Additionally, you can access any URL by entering it into the address bar.
![browse-2](./images/monitoring-applications/browse-2.png)
When you click the *Dev Tools* button it opens the [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools) for the selected tab.
### Browser Toolbar
The browser toolbar provides the following controls:
| Control | Description |
|---------|-------------|
| **Back / Forward** | Navigate through your browsing history within the tab. |
| **Refresh** | Reload the current page. |
| **Address Bar** | Enter any URL to navigate directly. The address bar shows the current URL and allows you to navigate to any website. |
| **Dev Tools** | Opens the [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools) for the selected tab, allowing you to inspect elements, debug JavaScript, and analyze network requests. |
| **Clear Cookies** | Clears all cookies for the currently selected tab, useful for testing authentication flows or resetting session state. |
| **Open in External Browser** | Opens the current URL in your system's default web browser. |
![dev-tools](./images/monitoring-applications/dev-tools.png)
### Default Credentials Notification
When browsing certain applications (such as AppHost dashboards), ABP Studio displays a notification bar with default credentials. This helps you quickly log in during development. You can dismiss this notification permanently by clicking the *Don't show again* option.
## HTTP Requests
Within this tab, you can view all *HTTP Requests* received by your C# applications. You have the option to filter requests based on URLs by using the search textbox or by selecting a particular application from the combobox. The *Clear Requests* button removes all received requests. Moreover, you have the ability to sort requests by columns.
![http-requests](./images/monitoring-applications/http-requests.png)
Clicking on a row enables you to view the details of each HTTP request; `URL`, `Method`, `Status Code`, `Timestamp`, `Headers (Request, Response)`, `Request (Payload)` and `Response`.
### Request List Columns
The request list displays the following information:
| Column | Description |
|--------|-------------|
| **Timestamp** | When the request was received (displayed as HH:mm:ss). |
| **Application** | The name of the application that received the request. |
| **Path** | The request URL path and query string. |
| **Method** | The HTTP method (GET, POST, PUT, DELETE, etc.). |
| **Status Code** | The HTTP response status code. |
| **Duration** | The time taken to process the request in milliseconds. |
| **Request Body Size** | The size of the request payload. |
| **Response Body Size** | The size of the response payload. |
### Request Details
Clicking on a row enables you to view the details of each HTTP request:
- **URL**: The full request URL.
- **Method**: The HTTP method used.
- **Status Code**: The response status code.
- **Duration**: Request processing time in milliseconds.
- **Timestamp**: When the request was received.
- **Headers (Request)**: All request headers sent by the client.
- **Headers (Response)**: All response headers returned by the server.
- **Request (Payload)**: The request body with size information.
- **Response**: The response body with size information.
![http-requests-details](./images/monitoring-applications/http-requests-details.png)
You can format the JSON content by clicking the *Format* button.
### Formatting JSON Content
Both request and response payloads have a *Format* button that formats JSON content for better readability. This is available when the content type is `application/json`.
![http-requests-details-json](./images/monitoring-applications/http-requests-details-json.png)
Furthermore, by clicking the gear icon in the *HTTP Requests* tab, you can access the *Solution Runner HTTP Requests Options* window. Within the *Ignored URLs* tab, you have the ability to exclude particular URLs by applying a regex pattern. Excluded URLs won't be visible in the *HTTP Requests* tab. By default, the metrics URL is already ignored. You can add or remove items as needed.
### Quick URL Filter
When viewing request details, you can right-click and select *Filter Selected URL* to quickly apply the current URL as a filter. This is useful when you want to see all requests to a specific endpoint.
### Configuring Ignored URLs
By clicking the gear icon in the *HTTP Requests* tab, you can access the *Solution Runner HTTP Requests Options* window. Within the *Ignored URLs* tab, you have the ability to exclude particular URLs by applying a regex pattern. Excluded URLs won't be visible in the *HTTP Requests* tab. By default, the metrics URL is already ignored. You can add or remove items as needed.
![http-requests-options](./images/monitoring-applications/http-requests-options.png)
> After adding a new URL, it will only affect subsequent requests.
> After adding a new URL pattern, it will only affect subsequent requests. Existing requests will remain visible.
## Events
In this tab, you can view all [Distributed Events](../framework/infrastructure/event-bus/distributed) sent or received by your C# applications. You can filter them by [Event Name](../framework/infrastructure/event-bus/distributed#event-name) using the search textbox or by selecting a specific application. Additionally, you can choose the *Direction* (Received/Send) and *Source* (Direct/Inbox/Outbox) of events. The *Clear Events* button removes all events.
In this tab, you can view all [Distributed Events](../framework/infrastructure/event-bus/distributed) sent or received by your C# applications. You can filter them by [Event Name](../framework/infrastructure/event-bus/distributed#event-name) using the search textbox or by selecting a specific application. Additionally, you can choose the *Direction* (Received/Sent) and *Source* (Direct/Inbox/Outbox) of events. The *Clear Events* button removes all events.
![events](./images/monitoring-applications/events.png)
> In the *Direction* section, there are two options: *Received*, indicating events received by the application, and *Sent*, indicating events sent by the application. Within the *Source* section, three options are available, and their significance comes into play when utilizing the [Inbox/Outbox pattern](../framework/infrastructure/event-bus/distributed#outbox-inbox-for-transactional-events). *Inbox* refers to events received by the application, *Outbox* refers to events sent by the application and *Direct* signifies events sent or received by the application without involving Inbox/Outbox pattern.
### Direction and Source Filters
| Filter | Options | Description |
|--------|---------|-------------|
| **Direction** | *Received*, *Sent* | Filter by whether events were received or sent by the application. |
| **Source** | *Direct*, *Inbox*, *Outbox* | Filter by event source. Relevant when using the [Inbox/Outbox pattern](../framework/infrastructure/event-bus/distributed#outbox-inbox-for-transactional-events). |
- **Direct**: Events sent or received without using the Inbox/Outbox pattern.
- **Inbox**: Events received through the transactional inbox.
- **Outbox**: Events sent through the transactional outbox.
Clicking on a row enables you to view the details of each event; `Application`, `Event Name`, `Direction`, `Source`, `Timestamp` and `Event Data`.
### Event Details
Clicking on a row enables you to view the details of each event:
- **Application**: The application that sent or received the event.
- **Event Name**: The full event type name.
- **Direction**: Whether the event was received or sent.
- **Source**: The event source (Direct, Inbox, or Outbox).
- **Timestamp**: When the event was processed.
- **Event Data**: The event payload in JSON format.
![event-details](./images/monitoring-applications/event-details.png)
### Formatting Event Data
The *Event Data* section includes a *Format* button that formats the JSON content for better readability. This makes it easier to inspect complex event payloads.
> ABP Studio automatically decodes Base64-encoded event data. If your event bus uses Base64 encoding for message transport, the data will be displayed in its decoded, readable form.
## Exceptions
This tab displays all exceptions by your C# applications. You can apply filters using the search textbox based on *Message*, *Source*, *ExceptionType*, and *StackTrace* or by choosing a specific application. Additionally, you have the option to select the [Log Level](../framework/fundamentals/exception-handling.md#log-level) for adding a filter. To clear all exceptions, use the *Clear Exceptions* button.
This tab displays all exceptions thrown by your C# applications. You can apply filters using the search textbox based on *Message*, *Source*, *ExceptionType*, and *StackTrace* or by choosing a specific application. Additionally, you have the option to select the [Log Level](../framework/fundamentals/exception-handling.md#log-level) for filtering. To clear all exceptions, use the *Clear Exceptions* button.
![exceptions](./images/monitoring-applications/exceptions.png)
Click on a row to inspect the details of each exception; `Application`, `Exception Type`, `Source`, `Timestamp`, `Level`, `Message` and `StackTrace`.
### Exception List
The exception list shows a summary of each exception:
- **Exception Type**: The short exception type name (e.g., `NullReferenceException` instead of `System.NullReferenceException`).
- **Message**: A truncated version of the exception message.
- **Timestamp**: When the exception occurred.
- **Level**: The log level (Error, Warning, etc.).
### Exception Details
Click on a row to inspect the full details of each exception:
- **Application**: The application where the exception occurred.
- **Exception Type**: The full exception type name including namespace.
- **Source**: The source file or component where the exception originated.
- **Timestamp**: When the exception was thrown.
- **Level**: The log level associated with this exception.
- **Message**: The complete exception message.
- **StackTrace**: The full stack trace showing the call hierarchy.
![exception-details](./images/monitoring-applications/exception-details.png)
### Inner Exceptions
If an exception contains inner exceptions, they are displayed hierarchically in the details panel. This allows you to trace the root cause of wrapped exceptions, which is common in scenarios like database errors wrapped in application-level exceptions.
## Logs
The *Logs* tab allows you to view all logs for both CLI and C# applications. To access logs, simply select an application. You can also apply filters using the search textbox by log text or by selecting a specific *Log Level*. When you select a *Log Level* it shows selected log level and higher log levels. For example, if you select *Warning* it shows *Warning*, *Error* and *Critical* logs. To clear selected application logs, use the *Clear Logs* button. If *Auto Scroll* is checked, the display automatically scrolls when new logs are received.
The *Logs* tab allows you to view all logs for both CLI and C# applications. To access logs, simply select an application from the dropdown. You can also apply filters using the search textbox by log text or by selecting a specific *Log Level*.
![logs](./images/monitoring-applications/logs.png)
### Log Level Filtering
When you select a *Log Level*, it shows the selected level and all higher severity levels:
| Selected Level | Shows |
|---------------|-------|
| **Trace** | Trace, Debug, Information, Warning, Error, Critical |
| **Debug** | Debug, Information, Warning, Error, Critical |
| **Information** | Information, Warning, Error, Critical |
| **Warning** | Warning, Error, Critical |
| **Error** | Error, Critical |
| **Critical** | Critical only |
| **None** | All logs (no filtering) |
### Log Display Features
- **Color Coding**: Log entries are color-coded by level for quick visual identification. Errors and critical logs stand out with distinct colors.
- **Auto Scroll**: When enabled, the display automatically scrolls to show new logs as they arrive. This is useful for real-time monitoring.
- **Clear Logs**: Clears the logs for the currently selected application only. Other applications' logs remain intact.
- **Text Filter**: Search within log messages using the search textbox. The filter is applied in real-time with a slight delay for performance.
## Tools
The *Tools* tab allows you to easily access to the user interfaces of the tools you are using. A *tool* may be related with a docker container, or independent. If it is related with a container (ex: *grafana*), the tool is opened when the container is up. If the tool is independent, it will be always opened.
The *Tools* tab provides quick access to web-based management interfaces for infrastructure services like Grafana, RabbitMQ, pgAdmin, and Redis Commander. Each tool opens in a dedicated browser tab within ABP Studio, eliminating the need to switch between external browser windows.
![tools](./images/monitoring-applications/tools-overview.png)
The microservice template comes with pre-defined tools to display related container user interfaces. You can edit existing tools, add new tools or delete existing tools.
The microservice template includes pre-configured tools for common infrastructure services. You can customize these tools or add new ones based on your project requirements.
In the example below, a new tool named `My Application Status` will be added to the tools and it will display the URL in the input:
### Adding a New Tool
To add a new tool, click the *+* button in the *Tools* tab. This opens the *Create Tool* dialog where you can configure the tool properties.
![tools-create](./images/monitoring-applications/tools-create.png)
### Tool Properties
Each tool has the following configurable properties:
| Property | Required | Description |
|----------|----------|-------------|
| **Name** | Yes | A unique identifier displayed as the tab header. |
| **URL** | Yes | The web interface URL (e.g., `http://localhost:3000`). |
| **Related Container** | No | Docker container name. When set, the tool activates only when this container is running. |
| **Related Kubernetes Service** | No | A regex pattern to match Kubernetes service names for automatic URL switching. |
| **Related Kubernetes Service Port** | No | The port to use when connecting via Kubernetes service. |
### Editing and Removing Tools
- **Edit**: Right-click on a tool tab and select *Edit* to modify its properties.
- **Remove**: Right-click on a tool tab and select *Close* to remove it from the profile.
- **Clear Cookies**: Right-click on a tool tab and select *Clear Cookies* to reset the browser session for that tool.
### Tool Activation States
Tools can be in different activation states depending on their configuration:
| State | Condition | Behavior |
|-------|-----------|----------|
| **Always Active** | No *Related Container* specified | Tool is always accessible regardless of container state. |
| **Container-Dependent** | *Related Container* specified | Tool activates only when the specified Docker container is running. |
| **Kubernetes-Aware** | *Related Kubernetes Service* specified | Tool URL switches between local and Kubernetes endpoints automatically. |
### Kubernetes Integration
When you specify a *Related Kubernetes Service*, the tool gains the ability to seamlessly switch between local and Kubernetes environments. This is particularly useful for microservice development where you run some services locally while others remain in a Kubernetes cluster.
**Automatic URL Switching:**
1. **Local Mode**: When the *Related Container* is running, the tool uses the configured *URL* (e.g., `http://localhost:3000`).
2. **Kubernetes Mode**: When the container stops and you're [connected to a Kubernetes cluster](./kubernetes.md#connecting-to-a-kubernetes-cluster), the tool automatically redirects to the matching Kubernetes service.
3. **Pattern Matching**: The *Related Kubernetes Service* accepts regex patterns. For example, `.*-grafana` matches any service name ending with `-grafana`.
> This automatic switching eliminates the need to manually update URLs when transitioning between local development and Kubernetes-based testing.
### Run Profile Configuration
Tools are persisted in the Run Profile file (`.abprun.json`). Below is an example configuration with common infrastructure tools:
```json
{
"tools": {
"grafana": {
"url": "http://localhost:3000",
"relatedContainer": "grafana",
"relatedKubernetesService": ".*-grafana",
"relatedKubernetesServicePort": 3000
},
"rabbitmq": {
"url": "http://localhost:15672",
"relatedContainer": "rabbitmq",
"relatedKubernetesService": ".*-rabbitmq",
"relatedKubernetesServicePort": 15672
},
"redis-commander": {
"url": "http://localhost:8081",
"relatedContainer": "redis-commander"
},
"pgadmin": {
"url": "http://localhost:5050",
"relatedContainer": "pgadmin"
},
"seq": {
"url": "http://localhost:5341"
}
}
}
```
### Default Credentials
Some tools display a notification bar with default credentials when opened for the first time:
| Tool | Username | Password |
|------|----------|----------|
| Grafana | `admin` | `admin` |
| RabbitMQ | `guest` | `guest` |
> You can dismiss this notification permanently by clicking the *Don't show again* option.

9
docs/en/studio/release-notes.md

@ -9,7 +9,14 @@
This document contains **brief release notes** for each ABP Studio release. Release notes only include **major features** and **visible enhancements**. Therefore, they don't include all the development done in the related version.
## 2.1.3 (2025-12-15) Latest
## 2.1.4 (2025-12-30) Latest
* Fixed books sample for blazor-webapp tiered solution.
* Fixed K8s cluster deployment issues for microservices.
* Fixed docker build problem on microservice template.
* Showed logs of the executed tasks.
## 2.1.3 (2025-12-15)
* Updated `createCommand` and CLI help for multi-tenancy.
* Fixed `BookController` templating problem.

46
docs/en/studio/running-applications.md

@ -77,7 +77,9 @@ We can use common [dotnet](https://learn.microsoft.com/en-us/dotnet/core/tools)
### Add
We can add 3 different item type to *Profile* for defining the tree structure. Those options are `C# Application`, `CLI Application` and `Folder`.
We can add 4 different item types to *Profile* for defining the tree structure. Those options are `C# Application`, `CLI Application`, `Docker Container`, and `Folder`.
> Note: The `Docker Container` option is only available when right-clicking the profile root. When right-clicking a folder, only `C# Application`, `CLI Application`, and `Folder` options are available.
![profile-root-context-menu-add](images/solution-runner/profile-root-context-menu-add.png)
@ -181,13 +183,15 @@ You can see the context menu by right-clicking *Folder*. It will start/stop all
## C# Application
The .NET icon indicates that the application is a C# project. After we [add](#c-application) the C# applications to the root of the tree or folder, we can go to any C# application and right-click to view the context menu; `Start`, `Build`, `Browse`, `Requests`, `Exceptions`, `Logs`, `Copy URL`, `Properties`, `Remove`.
The .NET icon indicates that the application is a C# project. After we [add](#c-application) the C# applications to the root of the tree or folder, we can go to any C# application and right-click to view the context menu; `Start`, `Stop`, `Restart`, `Build`, `Browse`, `Health Status`, `Requests`, `Exceptions`, `Logs`, `Copy URL`, `Properties`, `Remove`, and `Open with`.
![csharp-application-context-menu](images/solution-runner/csharp-application-context-menu.png)
### Start
### Start / Stop / Restart
Starts the selected application. Once it is started, *Stop* and *Restart* options will be available.
- **Start**: Starts the selected application.
- **Stop**: Stops the running application.
- **Restart**: Restarts the running application. This option is only available when the application is started.
> When you start the C# application, you should see a *chain* icon next to the application name, that means the started application connected to ABP Studio. C# applications can connect to ABP Studio even when running from outside the ABP Studio environment, for example debugging with Visual Studio. If the application is run from outside the ABP Studio environment, it will display *(external)* information next to the chain icon.
@ -199,14 +203,17 @@ It's the [similar](#build) options like root of the tree options. The only diffe
### Monitoring
When the C# application is connected to ABP Studio, it starts sending telemetry information to see in one place. We can easily click these options to see the detail; `Browse`, `Requests`, `Exceptions` and `Logs`.
When the C# application is connected to ABP Studio, it starts sending telemetry information to see in one place. We can easily click these options to see the detail; `Browse`, `Health Status`, `Requests`, `Exceptions`, `Events` and `Logs`.
![csharp-application-context-menu-monitor](images/solution-runner/csharp-application-context-menu-monitor.png)
- `Browse`: ABP Studio includes a browser tool for accessing websites and running applications. You can click this option to view the application in the ABP Studio browser. However, this option is only accessible if the application is started.
- Health Status : If Health Check endpoints are defined, it allows you to browse Health UI and see the latest health check response.
- `Health Status`: A submenu that provides health monitoring options when Health Check endpoints are defined:
- **Browse Health UI**: Opens the Health UI page of the application in the built-in browser.
- **Show Latest Health Check Response**: Displays the latest health check response in JSON format.
- `Requests`: It opens the *HTTP Requests* tab with adding the selected application filter. You can view all *HTTP Requests* received by your applications.
- `Exceptions`: We can display all exceptions on this tab. It opens the *Exceptions* tab with selected application.
- `Events`: Opens the *Events* tab filtered by this application. You can view all [Distributed Events](../framework/infrastructure/event-bus/distributed) sent or received by this application.
- `Logs`: Clicking this option opens the *Logs* tab with adding the selected application filter.
### Properties
@ -215,16 +222,35 @@ We can open the *Application Properties* window to change *Launch url*, *Health
![solutioın-runner-properties](images/solution-runner/solutioın-runner-properties.png)
- **Health check endpoint**: Endpoint for controlling the health status of the application periodically. If the application doesn't have a endpoint for health check, you can enter `/` to use the home page of the application as health check endpoint.
- **Launch URL**: The URL used when browsing the application. This is the address where the application is accessible.
- **Kubernetes service**: A regex pattern to match Kubernetes service names. When connected to a Kubernetes cluster, ABP Studio uses this pattern to find the corresponding Kubernetes service and uses its URL instead of the Launch URL. This applies to *Browse*, *Copy URL*, and *Health UI* features. For example, if your Helm chart creates a service named `bookstore-identity-service`, you can use `.*-identity-service` or `bookstore-identity.*` as the pattern. For [microservice](../get-started/microservice.md) templates, this is pre-configured.
- **Health check endpoint**: Endpoint for controlling the health status of the application periodically. If the application doesn't have an endpoint for health check, you can enter `/` to use the home page of the application as health check endpoint.
- **Health UI endpoint**: Endpoint of the Health UI page of the application.
- **Skip build before starting**: When enabled, application is started without build and it makes starting faster. This is useful when you are working on a single application out of multiple, so you don't need to build others everytime they start.
- **Watch changes while running**: When enabled, you should see an *eye* icon next to the application name.
- **Skip build before starting**: When enabled, the application is started without building, making startup faster. This is useful when you are working on a single application out of multiple, so you don't need to build others every time they start.
- **Watch changes while running**: When enabled, changes in your code are watched and dotnet hot-reloads the application or restarts it if needed. You should see an *eye* icon next to the application name when this is enabled.
- **Open browser on start**: When enabled, the application is automatically opened in the Browse tab when it starts.
- **Auto refresh browser on restart**: When enabled, browser tabs showing this application are automatically refreshed when the application restarts.
- **Runnable**: Controls whether the application can be started from Solution Runner. When disabled, the application will not be included in batch start operations.
![csharp-application-context-menu-run-connection](images/solution-runner/csharp-application-context-menu-run-connection.png)
### Open with
The *Open with* submenu provides options to open the application project in external tools:
- **Visual Studio**: Opens the project in Visual Studio (available if installed).
- **Visual Studio Code**: Opens the project folder in Visual Studio Code (available if installed).
- **JetBrains Rider**: Opens the project in JetBrains Rider (available if installed).
- **Terminal**: Opens a terminal window in the project directory.
- **Explorer / Finder**: Opens the project folder in the system file explorer.
### Custom Commands
You can add custom commands that appear in the context menu of Solution Runner items (root, folders, and applications). These commands allow you to automate custom workflows and scripts. For details on creating and managing custom commands, see the [Custom Commands](custom-commands.md) documentation.
### Miscellaneous
- We can copy the selected application *Browse* URL with *Copy URL*. It copies the *Browse* URL instead *Launch URL* since we could connected to *Kubernetes* service.
- We can copy the selected application *Browse* URL with *Copy URL*. It copies the *Browse* URL instead of *Launch URL* since we could be connected to a *Kubernetes* service.
- You can change the target framework by right-click the selected application and change the *Target Framework* option. This option visible if the project has multiple target framework such as MAUI applications.
- To remove an application from the tree, open the context menu by right-clicking the application and selecting *Remove*.

24
docs/en/studio/solution-explorer.md

@ -33,6 +33,9 @@ It is the main solution that you can open with ABP Studio, an ABP solution can c
- `New Folder`: Creates a new folder within the ABP Solution, allowing you to organize your modules.
- `New Module`: Allows you to create a new module.
- `Existing Module`: You can add existing module to your solution.
- `Analyze`: Analyzes all modules and packages in the solution to extract information like aggregate roots, application services, permissions, etc.
- `Analyze`: Analyzes all modules and packages using existing build outputs.
- `Build & Analyze`: Builds the solution first, then analyzes all modules and packages.
- `Rename`: Renames the solution.
- `Manage Secrets`: You can edit your solution secrets.
- `Manage Metadata`: You can edit your solution metadata.
@ -52,6 +55,9 @@ It is the main solution that you can open with ABP Studio, an ABP solution can c
- `Clean`: Cleans the output of the previous build for modules.
- `Restore`: Restores the dependencies for modules.
- `Open With`
- `Visual Studio`: Opens the solution in Visual Studio. This option is only available if you have Visual Studio installed.
- `Visual Studio Code`: Opens the solution in Visual Studio Code. This option is only available if you have Visual Studio Code installed.
- `JetBrains Rider`: Opens the solution in JetBrains Rider. This option is only available if you have JetBrains Rider installed.
- `Terminal`: Opens the terminal in the solution directory.
- `Explorer`: Opens the file explorer in the solution directory.
- `Solution Configuration`: You can see the project creation options in this menu. It opens the *Solution Configuration* window.
@ -73,6 +79,9 @@ You can click the *OK* button to add the folder to the solution. When you right-
- `New Folder`: Creates a new nested folder within the selected folder, allowing you to organize your modules.
- `New Module`: Allows you to create a new module to selected folder.
- `Existing Module`: You can add existing module to your selected folder.
- `Analyze`: Analyzes all modules and packages in the selected folder.
- `Analyze`: Analyzes all modules and packages using existing build outputs.
- `Build & Analyze`: Builds first, then analyzes all modules and packages in the folder.
- `Rename`: Renames the selected folder.
- `Delete`: Deletes the selected folder and all child items from the solution.
- `ABP CLI`
@ -102,6 +111,9 @@ A [module](./concepts.md#module) is a sub-solution that can contains zero, one o
- `New Package`: Creates a new package within the selected module.
- `Existing Package`: You can add existing package to your selected module.
- `Folder`: Creates a new folder within the selected module, allowing you to organize your packages.
- `Analyze`: Analyzes all packages in the selected module.
- `Analyze`: Analyzes all packages using existing build outputs.
- `Build & Analyze`: Builds first, then analyzes all packages in the module.
- `Import Module`: This option allows you to import an existing module from *Solution*, *Local*, or *NuGet* into your selected module.
- `Rename`: Renames the selected module.
- `Remove`: Removes the selected module and all child items from the solution.
@ -126,6 +138,7 @@ A [module](./concepts.md#module) is a sub-solution that can contains zero, one o
- `JetBrains Rider`: Opens the module in JetBrains Rider. This option is only available if you have JetBrains Rider installed.
- `Terminal`: Opens the terminal in the module directory.
- `Explorer`: Opens the file explorer in the module directory.
- `Open Readme`: Opens the README file in the module if available. This option is only visible if the module has a README file.
- `Upgrade to Pro`: This will be visible only when you purchased a license but still using the modules came with open-source (free) license. For more details, check out [Migrating from Open Source Templates](../guides/migrating-from-open-source.md) document. This is not shown in the screenshot above.
### Adding a New Empty Module
@ -238,10 +251,18 @@ A [package](./concepts.md#package) is a project that can be added to a module, a
![package-context-menu](./images/solution-explorer/package-context-menu.png)
- `Add Package Reference`: This option allows you to add a package reference to the selected package.
- `Add`: This menu is only visible for Host projects (except Blazor WebAssembly).
- `NPM Package`: Adds an NPM package reference to the selected package.
- `Add Package Reference`: This option allows you to add a package reference to the selected package. This option is only visible for non-Host projects.
- `Analyze`: Analyzes the selected package to extract information like aggregate roots, application services, permissions, etc. This menu is only visible for analyzable package types.
- `Analyze`: Analyzes the package using existing build outputs.
- `Build & Analyze`: Builds first, then analyzes the package.
- `Reload`: Reloads the selected package.
- `Remove`: Removes the selected package from the module.
- `ABP CLI`
- `Generate Proxy`: Generates client-side proxy code for the selected package.
- `Javascript`: Generates JavaScript proxy code.
- `C#`: Generates C# proxy code.
- `Install Libs`: Install NPM Packages for UI projects in your selected package.
- `Upgrade ABP Packages`: Update all the ABP related NuGet and NPM packages in your selected package.
- `Switch to`: It switches your selected package to the specified version of the ABP.
@ -264,6 +285,7 @@ A [package](./concepts.md#package) is a project that can be added to a module, a
- `JetBrains Rider`: Opens the package in JetBrains Rider. This option is only available if you have JetBrains Rider installed.
- `Terminal`: Opens the terminal in the package directory.
- `Explorer`: Opens the file explorer in the package directory.
- `Open Readme`: Opens the README file in the package if available. This option is only visible if the package has a README file.
### Adding a New Package

11
docs/en/studio/working-with-suite.md

@ -39,7 +39,10 @@ If There are more than one module which is openable via Suite, Studio will ask y
### Opening from context menu
Alternatively, you can right click to a module in [Solution Explorer](solution-explorer.md) and click `ABP Suite` to open it with Suite. Suite will automatically open `Crud Page Generation` screen with the selected module.
You can right-click on the solution root or a module in [Solution Explorer](solution-explorer.md) and click `ABP Suite` to open it with Suite.
- **Solution root**: If you right-click on the solution root and select `ABP Suite`, Studio will ask you to pick a module if there are multiple modules. Suite will then open with the selected module.
- **Module**: If you right-click on a specific module and select `ABP Suite`, Suite will automatically open `Crud Page Generation` screen with that module.
![suite-context-menu](./images/suite/suite-context-menu.png)
@ -51,10 +54,14 @@ Standard application solutions (`App` & `App-nolayers`) generated by Studio are
You can generate code on the services of the microservice solution. UI code generation is not supported at the moment. It is on the roadmap.
## Managing Installed Version
## Managing Installed Version
You can update or downgrade the version of `ABP Suite` by `Change Version` button in `ABP Suite` menu.
### Automatic Version Matching
When you open ABP Suite for a solution, Studio checks if the installed Suite version matches the solution's ABP version. If they differ, Studio will prompt you to update or downgrade Suite to match your solution's version. This ensures compatibility between Suite and your project.
![suite-change-version-button-main-page](./images/suite/suite-change-version-button-main-page.png)
![suite-change-version-window](./images/suite/suite-change-version-window.png)

23
docs/en/suite/generating-crud-page.md

@ -270,6 +270,29 @@ In the example above, the `IdentityUser` entity is selected as the navigation pr
> **Note:** Ensure that your solution is built properly before establishing relationship between your own entity and a module entity because ABP Suite scans assemblies and finds which ABP modules you are using and lists their entities in the navigation property model if you have checked the **Include entities from ABP modules** checkbox.
#### Extending with Custom Module Entities
If you want to extend ABP Suite's system to list entities from your own custom modules (not just ABP's built-in modules), you can configure the `module-entity-extension.json` file. This file is located in the `.suite` folder at the root of your solution (`/.suite/module-entity-extension.json`).
Here is the default sample file content:
```json
{
"Modules": [
{
"DomainProjectDllFileName": "MySampleModule.MyProject.Domain.dll"
}
]
}
```
By defining the `DomainProjectDllFileName` property, ABP Suite will scan the specified module's **.dll** and list its entities in the navigation property model. This allows you to create navigation properties that reference entities from your custom modules.
> **Important:** When extending with custom module entities, ensure that:
> - Your current solution properly depends on the related module.
> - All module references are correctly configured.
> - The solution is built successfully before attempting to establish relationships.
#### Adding An Existing Entity as a Navigation Property
Alternatively, you can add `IdentityUser` entity (or any other entity) as a navigation property to an entity by manually entering the required information. See the screenshot below:

6
docs/en/testing/integration-tests.md

@ -34,13 +34,13 @@ The startup template is configured to use **in-memory SQLite** database for the
Using in-memory SQLite database has two main advantages;
* It is faster compared to an external DBMS.
* It create a **new fresh database** for each test case, so tests doesn't affect each other.
* It creates a **new fresh database** for each test case, so tests don't affect each other.
> **Tip**: Do not use EF Core's In-Memory database for advanced integration tests. It is not a real DBMS and has many differences in details. For example, it doesn't support transaction and rollback scenarios, so you can't truly test the failing scenarios. On the other hand, In-Memory SQLite is a real DBMS and supports the fundamental SQL database features.
## The Seed Data
Writing tests against an empty database is not practical. In most cases, you need to some initial data in the database. For example, if you write a test class that query, update and delete the products, it would be helpful to have a few products in the database before executing the test case.
Writing tests against an empty database is not practical. In most cases, you need some initial data in the database. For example, if you write a test class that queries, updates and deletes the products, it would be helpful to have a few products in the database before executing the test case.
ABP's [Data Seeding](../framework/infrastructure/data-seeding.md) system is a powerful way to seed the initial data. The application startup template has a *YourProject*TestDataSeedContributor class in the `.TestBase` project. You can fill it to have an initial data that you can use for each test method.
@ -401,7 +401,7 @@ public class EfCoreIssueAppService_Tests : IssueAppService_Tests<MyProjectEntity
}
````
> By deriving from the related abstract classes, now we can see the all tests in the test explorers and run them.
> By deriving from the related abstract classes, now we can see all the tests in the test explorers and run them.
![unitest-efcore-mongodb](../images/unitest-efcore-mongodb.png)

2
docs/en/testing/overall.md

@ -69,7 +69,7 @@ The startup solution has the following libraries already installed;
* [NSubstitute](https://nsubstitute.github.io/) as the mocking library.
* [Shouldly](https://github.com/shouldly/shouldly) as the assertion library.
While you are free to replace them with your favorite tools, this document and examples will be base on these tooling.
While you are free to replace them with your favorite tools, this document and examples will be based on these tooling.
## The Test Explorer

4
docs/en/testing/unit-tests.md

@ -73,7 +73,7 @@ namespace MyProject.Issues
Notice that the `IsClosed` and `CloseDate` properties have private setters to force some business rules by using the `Open()` and `Close()` methods:
* Whenever you close an issue, the `CloseDate` should be set to the [current time](../framework/infrastructure/virtual-file-system.md).
* Whenever you close an issue, the `CloseDate` should be set to the current time.
* An issue can not be re-opened if it is locked. And if it is re-opened, the `CloseDate` should be set to `null`.
Since the `Issue` entity is a part of the Domain Layer, we should test it in the `Domain.Tests` project. Create an `Issue_Tests` class inside the `Domain.Tests` project:
@ -160,7 +160,7 @@ public void Should_Not_Allow_To_ReOpen_A_Locked_Issue()
`Assert.Throws` checks if the executed code throws a matching exception.
> See the [xUnit](https://xunit.net/#documentation) & [Shoudly](https://docs.shouldly.org/) documentation to learn more about these libraries.
> See the [xUnit](https://xunit.net/#documentation) & [Shouldly](https://docs.shouldly.org/) documentation to learn more about these libraries.
## Classes With Dependencies

8
framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientAccessor.cs

@ -29,5 +29,13 @@ public class ChatClientAccessor<TWorkSpace> : IChatClientAccessor<TWorkSpace>
ChatClient = serviceProvider.GetKeyedService<IChatClient>(
AbpAIWorkspaceOptions.GetChatClientServiceKeyName(
WorkspaceNameAttribute.GetWorkspaceName<TWorkSpace>()));
// Fallback to default chat client if not configured for the workspace.
if (ChatClient is null)
{
ChatClient = serviceProvider.GetKeyedService<IChatClient>(
AbpAIWorkspaceOptions.GetChatClientServiceKeyName(
AbpAIModule.DefaultWorkspaceName));
}
}
}

6
framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedChatClient.cs

@ -9,9 +9,13 @@ public class TypedChatClient<TWorkSpace> : DelegatingChatClient, IChatClient<TWo
{
public TypedChatClient(IServiceProvider serviceProvider)
: base(
serviceProvider.GetRequiredKeyedService<IChatClient>(
serviceProvider.GetKeyedService<IChatClient>(
AbpAIWorkspaceOptions.GetChatClientServiceKeyName(
WorkspaceNameAttribute.GetWorkspaceName<TWorkSpace>()))
??
serviceProvider.GetRequiredKeyedService<IChatClient>(
AbpAIWorkspaceOptions.GetChatClientServiceKeyName(
AbpAIModule.DefaultWorkspaceName))
)
{
}

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

Loading…
Cancel
Save