Browse Source

Merge branch 'dev' into pr/15539

pull/15539/head
maliming 4 years ago
parent
commit
fe21142b18
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 14
      .github/pull_request_template.md
  2. 16
      .github/workflows/auto-pr.yml
  3. 1
      .github/workflows/image-compression.yml
  4. 11
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  5. 20
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  6. 1
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  7. 1
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  8. 17
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  9. 2
      common.props
  10. 207
      docs/en/Blog-Posts/2023-02-08 v7_1_Preview/POST.md
  11. BIN
      docs/en/Blog-Posts/2023-02-08 v7_1_Preview/community-talks-conver-image.png
  12. BIN
      docs/en/Blog-Posts/2023-02-08 v7_1_Preview/cover-image.png
  13. BIN
      docs/en/Blog-Posts/2023-02-08 v7_1_Preview/ndc-london.png
  14. BIN
      docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-1.png
  15. BIN
      docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-2.png
  16. BIN
      docs/en/Blog-Posts/2023-02-08 v7_1_Preview/suite-blazor-wasm-nolayers.png
  17. 1
      docs/en/CLI.md
  18. 105
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/how-can-I-contribute-to-open-source-projects-on-github.md
  19. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/branch-image.png
  20. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/clone-image.png
  21. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/commit-image.png
  22. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/developer-list.png
  23. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/fork-project-image.png
  24. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/fork-project-profile.png
  25. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/git-push-image.png
  26. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/github-desktop-change.png
  27. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/github-desktop-pull-request.png
  28. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/open-pull-request-image.png
  29. BIN
      docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/pull-request-image.png
  30. 86
      docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/POST.md
  31. BIN
      docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/new.gif
  32. BIN
      docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/old.gif
  33. 10
      docs/en/Dependency-Injection.md
  34. 20
      docs/en/Entity-Framework-Core.md
  35. 25
      docs/en/Migration-Guides/Abp-7_0.md
  36. 19
      docs/en/Migration-Guides/Abp-7_1.md
  37. 1
      docs/en/Migration-Guides/Index.md
  38. 6
      docs/en/Modules/Cms-Kit/Blogging.md
  39. 8
      docs/en/Modules/Cms-Kit/Comments.md
  40. 6
      docs/en/Modules/Cms-Kit/Global-Resources.md
  41. 7
      docs/en/Modules/Cms-Kit/Index.md
  42. 18
      docs/en/Modules/Cms-Kit/Menus.md
  43. 8
      docs/en/Modules/Cms-Kit/Pages.md
  44. 8
      docs/en/Modules/Cms-Kit/Ratings.md
  45. 8
      docs/en/Modules/Cms-Kit/Reactions.md
  46. 8
      docs/en/Modules/Cms-Kit/Tags.md
  47. 575
      docs/en/Modules/Database-Tables.md
  48. 20
      docs/en/MongoDB.md
  49. 8
      docs/en/Repositories.md
  50. 5
      docs/en/Startup-Templates/Application-Single-Layer.md
  51. 8
      docs/en/Themes/LeptonXLite/Angular.md
  52. 116
      docs/en/Tutorials/Todo/Single-Layer/Index.md
  53. 12
      docs/en/UI/Angular/HTTP-Requests.md
  54. 2
      docs/en/UI/Angular/Modal.md
  55. 2
      docs/en/UI/AspNetCore/Navigation-Menu.md
  56. 21
      docs/en/UI/Blazor/Basic-Theme.md
  57. 2
      docs/en/UI/Blazor/Overall.md
  58. 2
      docs/en/UI/Blazor/Theming.md
  59. 9
      docs/en/docs-nav.json
  60. 1
      docs/zh-Hans/CLI.md
  61. 10
      docs/zh-Hans/Dependency-Injection.md
  62. 15
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/ApplicationConfigurationChangedService.cs
  63. 20
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs
  64. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelper.cs
  65. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelperService.cs
  66. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs
  67. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs
  68. 28
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs
  69. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs
  70. 2
      framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs
  71. 3
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs
  72. 2
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs
  73. 4
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs
  74. 2
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs
  75. 8
      framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj
  76. 20
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs
  77. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/IAuthService.cs
  78. 30
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs
  79. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs
  80. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
  81. 94
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SuiteAppSettingsService.cs
  82. 65
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs
  83. 13
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/AppNoLayersMigrateDatabaseChangeStep.cs
  84. 57
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/AppNoLayersMoveProjectsStep.cs
  85. 50
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
  86. 45
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppNoLayersTemplateBase.cs
  87. 17
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
  88. 12
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularSourceCodeAdder.cs
  89. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs
  90. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs
  91. 4
      framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionRegistrationActionExtensions.cs
  92. 2
      framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs
  93. 17
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs
  94. 10
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs
  95. 34
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/MultiTenantDbContextType.cs
  96. 11
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/ReplaceDbContextAttribute.cs
  97. 7
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs
  98. 23
      framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs
  99. 17
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs
  100. 23
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreDbContextTypeProvider.cs

14
.github/pull_request_template.md

@ -0,0 +1,14 @@
### Description
Resolves #xxxx (write the related issue number if available)
TODO: Describe what this PR has changed, add screenshot or animated GIF if available, write if it is a breaking change, and how to fix the breaking changes for existing applications if so.
### Checklist
- [ ] I fully tested it as developer / designer and created unit / integration tests
- [ ] I documented it (or no need to document or I will create a separate documentation issue)
### How to test it?
Please describe how this can be tested by the test engineers if it is not already explicit - or remove this section if no need to description.

16
.github/workflows/auto-pr.yml

@ -1,13 +1,13 @@
name: Merge branch dev with rel-7.0 name: Merge branch dev with rel-7.1
on: on:
push: push:
branches: branches:
- rel-7.0 - rel-7.1
permissions: permissions:
contents: read contents: read
jobs: jobs:
merge-dev-with-rel-6-0: merge-dev-with-rel-7-1:
permissions: permissions:
contents: write # for peter-evans/create-pull-request to create branch contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR pull-requests: write # for peter-evans/create-pull-request to create a PR
@ -18,13 +18,13 @@ jobs:
ref: dev ref: dev
- name: Reset promotion branch - name: Reset promotion branch
run: | run: |
git fetch origin rel-7.0:rel-7.0 git fetch origin rel-7.1:rel-7.1
git reset --hard rel-7.0 git reset --hard rel-7.1
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@v3 uses: peter-evans/create-pull-request@v3
with: with:
branch: auto-merge/rel-7-0/${{github.run_number}} branch: auto-merge/rel-7-1/${{github.run_number}}
title: Merge branch dev with rel-7.0 title: Merge branch dev with rel-7.1
body: This PR generated automatically to merge dev with rel-7.0. Please review the changed files before merging to prevent any errors that may occur. body: This PR generated automatically to merge dev with rel-7.1. Please review the changed files before merging to prevent any errors that may occur.
reviewers: ${{github.actor}} reviewers: ${{github.actor}}
token: ${{ github.token }} token: ${{ github.token }}

1
.github/workflows/image-compression.yml

@ -6,6 +6,7 @@ on:
- "**.jpeg" - "**.jpeg"
- "**.png" - "**.png"
- "**.webp" - "**.webp"
- "**.gif"
types: types:
- opened - opened
- synchronize - synchronize

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

@ -421,7 +421,7 @@
"ContentCacheSlidingExpirationByDay": "Content Cache Sliding Expiration By Day", "ContentCacheSlidingExpirationByDay": "Content Cache Sliding Expiration By Day",
"MaxDaysForCaching": "Max Days For Caching", "MaxDaysForCaching": "Max Days For Caching",
"Enabled": "Enabled", "Enabled": "Enabled",
"Menu:NugetPackagesContentCache": "NuGet Packages Content Cache", "Menu:NugetPackagesContentCache": "NuGet Cache",
"NugetPackagesContentCache": "NuGet Content Cache", "NugetPackagesContentCache": "NuGet Content Cache",
"SlidingExpritionByDayInfo": "Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed. This will not extend the entry lifetime beyond the absolute expiration.", "SlidingExpritionByDayInfo": "Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed. This will not extend the entry lifetime beyond the absolute expiration.",
"MaxDaysForCachingInfo": "Gets or sets an absolute expiration time, relative to now.", "MaxDaysForCachingInfo": "Gets or sets an absolute expiration time, relative to now.",
@ -440,13 +440,18 @@
"VersionHistoryDeletionConfirmationMessage": "Are you sure you want to delete this version?", "VersionHistoryDeletionConfirmationMessage": "Are you sure you want to delete this version?",
"CreateAbpConsultantLogoInfo": "Maximum file size: <b>1MB</b> <br/> Supported file types: <b>jpg, jpeg, png, SVG, WebP</b>", "CreateAbpConsultantLogoInfo": "Maximum file size: <b>1MB</b> <br/> Supported file types: <b>jpg, jpeg, png, SVG, WebP</b>",
"UrlCode": "Url Code", "UrlCode": "Url Code",
"Icon": "Icon",
"Clear": "Clear", "Clear": "Clear",
"Permission:AbpConsultant": "ABP Consultant", "Permission:AbpConsultant": "ABP Consultant",
"Menu:AbpConsultants": "ABP Consultants", "Menu:AbpConsultants": "ABP Consultants",
"CreateAbpConsultant": "Create ABP Consultant", "CreateAbpConsultant": "Create ABP Consultant",
"UrlCodeIsNotAvailable": "Url code is used by another ABP Consultant.", "UrlCodeIsNotAvailable": "Url code is used by another ABP Consultant.",
"AbpConsultants": "ABP Consultants", "AbpConsultants": "ABP Consultants",
"AbpConsultant": "ABP Consultant" "AbpConsultant": "ABP Consultant",
"AbpConsultantEdit": "Edit ABP Consultant",
"AbpConsultantCreate": "Create ABP Consultant",
"WhoWeAreItem": "Who We Are Item",
"FieldIsRequired": "{0} is required.",
"FieldIsNotValid": " {0} is not valid.",
"InterestedLicenseType": "Interested License Type"
} }
} }

20
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json

@ -187,6 +187,24 @@
"SaveUpTo": "<span>SAVE</span> UP TO<strong>${0}K</strong>", "SaveUpTo": "<span>SAVE</span> UP TO<strong>${0}K</strong>",
"ImplementingDDD": "Implementing Domain Driven Design", "ImplementingDDD": "Implementing Domain Driven Design",
"ExploreTheEBook": "Explore the E-Book", "ExploreTheEBook": "Explore the E-Book",
"ExploreTheBook": "Explore the Book" "ExploreTheBook": "Explore the Book",
"ConsultantType": "Consultancy Type",
"Expert": "ABP Expert",
"Partner": "ABP Partner",
"Industry": "Industry",
"Location": "Location",
"Contact": "Contact",
"Partner_Year": "Partnership Year",
"Info": "Info",
"SpokenLanguages": "Spoken Languages",
"SocialMedia": "Social Media",
"Activity": "Activity",
"Type": "Type",
"Contribution": "Contribution",
"WhoWeAre": "Who We Are",
"Icons": "Icons",
"Url": "Url",
"Icon": "Icon",
"RecentActivities": "Recent Activities"
} }
} }

1
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json

@ -369,7 +369,6 @@
"FreeTrial": "Free Trial", "FreeTrial": "Free Trial",
"AcceptsMarketingCommunications": " Yes, I`d like to receive ABP Commercial marketing communications.", "AcceptsMarketingCommunications": " Yes, I`d like to receive ABP Commercial marketing communications.",
"PurposeOfUsage": "Purpose of usage", "PurposeOfUsage": "Purpose of usage",
"Industry": "Industry",
"Choose": "- Choose -", "Choose": "- Choose -",
"CompanyOrganizationName": "Company / Organization name", "CompanyOrganizationName": "Company / Organization name",
"CompanySize": "Company size", "CompanySize": "Company size",

1
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -13,7 +13,6 @@
"Status": "Status", "Status": "Status",
"ContentSource": "Content Source", "ContentSource": "Content Source",
"Details": "Details", "Details": "Details",
"Url": "Url",
"Title": "Title", "Title": "Title",
"CreationTime": "Creation time", "CreationTime": "Creation time",
"Save": "Save", "Save": "Save",

17
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json

@ -409,26 +409,13 @@
"SeeTheScreenshot": "See the screenshot", "SeeTheScreenshot": "See the screenshot",
"ApplicationModuleExplanation1": "Creates a reusable, fully layered application module solution.", "ApplicationModuleExplanation1": "Creates a reusable, fully layered application module solution.",
"ApplicationModuleExplanation2": "You can use this option to create modules for your modular application.", "ApplicationModuleExplanation2": "You can use this option to create modules for your modular application.",
"Expert": "ABP Expert",
"Expert_": "Expert", "Expert_": "Expert",
"Partner": "ABP Partner",
"Partner_": "Partnership", "Partner_": "Partnership",
"WebSite": "Web Site", "WebSite": "Web Site",
"Industry": "Industry",
"Location": "Location",
"Contact": "Contact",
"ConsultantType": "Consultancy Type",
"Expert_Year": "Expertise Year", "Expert_Year": "Expertise Year",
"Partner_Year": "Partnership Year",
"SpokenLanguages": "Spoken Languages",
"SocialMedia": "Social Media",
"CompanyInfo": "Company Info", "CompanyInfo": "Company Info",
"WhoWeAre": "Who We Are",
"RecentActivities": "Recent Activities",
"Date": "Date", "Date": "Date",
"Activity": "Activity", "WhoWeAre_Partner": "Who We Are",
"Type": "Type", "WhoWeAre_Expert": "About Me"
"Contribution": "Contribution",
"Info": "Info"
} }
} }

2
common.props

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Version>7.1.0</Version> <Version>7.2.0</Version>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn> <NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl> <PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl> <PackageProjectUrl>https://abp.io/</PackageProjectUrl>

207
docs/en/Blog-Posts/2023-02-08 v7_1_Preview/POST.md

@ -0,0 +1,207 @@
# ABP.IO Platform 7.1 RC Has Been Released
Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.1 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version.
Try this version and provide feedback for a more stable version of ABP v7.1! Thanks to all of you.
## Get Started with the 7.1 RC
Follow the steps below to try version 7.1.0 RC today:
1) **Upgrade** the ABP CLI to version `7.1.0-rc.1` using a command line terminal:
````bash
dotnet tool update Volo.Abp.Cli -g --version 7.1.0-rc.1
````
**or install** it if you haven't before:
````bash
dotnet tool install Volo.Abp.Cli -g --version 7.1.0-rc.1
````
2) Create a **new application** with the `--preview` option:
````bash
abp new BookStore --preview
````
See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options.
> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application.
You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/).
## Migrating to 7.1
This version doesn't introduce any breaking changes. However, Entity Framework developers may need to add a new code-first database migration to their projects since we made some improvements to the existing entities of some application modules.
## What's New with ABP Framework 7.1?
In this section, I will introduce some major features released in this version. In addition to these features, so many enhancements have been made in this version too.
Here is a brief list of the titles explained in the next sections:
* Blazor WASM option added to Application Single Layer Startup Template
* Introducing the `IHasEntityVersion` interface and `EntitySynchronizer` base class
* Introducing the `DeleteDirectAsync` method for the `IRepository` interface
* Introducing the `IAbpHostEnvironment` interface
* Improvements on the eShopOnAbp project
* Others
### Blazor WASM option added to Application Single Layer Startup Template
We've created the [Application (Single Layer) Startup Template](https://docs.abp.io/en/abp/7.1/Startup-Templates/Application-Single-Layer) in v5.2 with three UI types: Angular, Blazor Server and MVC. At the moment, we didn't provide UI option for Blazor, because it required 3 projects at least (server-side, client-side and shared library between these two projects).
In this version, we've added the Blazor WASM option to the **Application (Single Layer) Startup Template**. It still contains three projects (`blazor`, `host`, and `contracts`) but hosted by a single `host` project.
You can use the following CLI command to create an `app-nolayers` template with the Blazor UI as the UI option:
```bash
abp new TodoApp -t app-nolayers -u blazor --version 7.1.0-rc.1
```
> You can check the [Quick Start documentation](https://docs.abp.io/en/abp/7.1/Tutorials/Todo/Single-Layer/Index?UI=Blazor&DB=EF) for a quick start with this template.
### Introducing the `IHasEntityVersion` interface and `EntitySynchronizer` base class
Entity synchronization is an important concept, especially in distributed applications and module development. If we have an entity that is related to other modules, we need to align/sync their data once the entity changes and versioning entity changes can also be good, so we can know whether they're synced or not.
In this version, [@gdlcf88](https://github.com/gdlcf88) made a great contribution to the ABP Framework and introduced the `IHasEntityVersion` interface which adds **auto-versioning** to entity classes and `EntitySynchronizer` base class to **automatically sync an entity's properties from a source entity**.
You can check the issue and documentation from the following links for more info:
- [Issue: Entity synchronizers and a new EntityVersion audit property](https://github.com/abpframework/abp/issues/14196)
- [Versioning Entities](https://docs.abp.io/en/abp/7.1/Entities#versioning-entities)
- [Distributed Event Bus - Entity Synchronizer](https://docs.abp.io/en/abp/7.1/Distributed-Event-Bus#entity-synchronizer)
> Note: The entities of some modules from the ABP Framework have implemented the `IHasEntityVersion` interface. Therefore, if you are upgrading your application from an earlier version, you need to create a new migration and apply it to your database.
### Introducing the `DeleteDirectAsync` method for the `IRepository` interface
EF 7 introduced a new [`ExecuteDeleteAsync`](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) method that deletes entities without involving the change tracker into the process. Therefore, it's much faster.
We've added the `DeleteDirectAsync` method to the `IRepository<>` interface to take the full power of EF 7. It deletes all entities that fit the given predicate. It directly deletes entities from the database, without fetching them. Therefore, some features (like **soft-delete**, **multi-tenancy**, and **audit logging)** won't work, so use this method carefully when you need it. And use the `DeleteAsync` method if you need those features.
### Introducing the `IAbpHostEnvironment` interface
Sometimes, while creating an application, we need to get the current hosting environment and take actions according to that. In such cases, we can use some services such as [IWebHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.iwebhostenvironment?view=aspnetcore-7.0) or [IWebAssemblyHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.webassembly.hosting.iwebassemblyhostenvironment) provided by .NET, in the final application.
However, we can not use these services in a class library, which is used by the final application. ABP Framework provides the `IAbpHostEnvironment` service, which allows you to get the current environment name whenever you want. `IAbpHostEnvironment` is used by the ABP Framework in several places to perform specific actions by the environment. For example, ABP Framework reduces the cache duration on the **Development** environment for some services.
**Usage:**
```csharp
public class MyService
{
private readonly IAbpHostEnvironment _abpHostEnvironment;
public MyService(IAbpHostEnvironment abpHostEnvironment)
{
_abpHostEnvironment = abpHostEnvironment;
}
public void MyMethod()
{
//getting the current environment name
var environmentName = _abpHostEnvironment.EnvironmentName;
//check for the current environment
if (_abpHostEnvironment.IsDevelopment()) { /* ... */ }
}
}
```
You can inject the `IAbpHostEnvironment` into your service and get the current environment by using its `EnvironmentName` property. You can also check the current environment by using its extension methods such as `IsDevelopment()`.
> Check the [ABP Application Startup](https://docs.abp.io/en/abp/7.1/Application-Startup) documentation for more information.
### Improvements on the eShopOnAbp project
K8s and Docker configurations have been made within this version (Dockerfiles and helm-charts have been added and image build scripts have been updated). See [#14083](https://github.com/abpframework/abp/issues/14083) for more information.
### Others
* Referral Links have been added to the CMS Kit Comment Feature (optional). You can specify common referral links (such as "nofollow" and "noreferrer") for links in the comments. See [#15458](https://github.com/abpframework/abp/issues/15458) for more information.
* ReCaptcha verification has been added to the CMS Kit Comment Feature (optional). You can enable ReCaptcha support to enable protection against bots. See the [documentation](https://docs.abp.io/en/abp/7.1/Modules/Cms-Kit/Comments) for more information.
* In the development environment, it is a must to reduce cache durations for some points. We typically don't have to invalidate the cache manually or wait on it for a certain time to be invalidated. For that purpose, we have reduced the cache durations for some points on the development environment. See [#14842](https://github.com/abpframework/abp/pull/14842) for more information.
## What's New with ABP Commercial 7.1?
We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the new features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 7.1.
### Blazor WASM option added to Application Single Layer Pro Startup Template
The [**Application (Single Layer) Startup Template**](https://docs.abp.io/en/commercial/latest/startup-templates/application-single-layer/index) with Blazor UI is also available for ABP Commercial customers with this version as explained above.
You can use the following CLI command to create an `app-nolayers-pro` template with Blazor UI as the UI option:
```bash
abp new TodoApp -t app-nolayers-pro -u blazor --version 7.1.0-rc.1
```
You can also create an `app-nolayers-pro` template with Blazor UI via ABP Suite:
![](suite-blazor-wasm-nolayers.png)
### Suite - MAUI Blazor Code Generation
We provided a new UI option "MAUI Blazor" for the `app-pro` template in the previous version and it's possible to create a `maui-blazor` application with both ABP CLI and ABP Suite.
You can create an `app-pro` template with the MAUI Blazor as the UI option with the following ABP CLI command:
```bash
abp new Acme.BookStore -t app-pro -u maui-blazor
```
In this version, we implemented the code generation for MAUI Blazor. You can create and generate CRUD pages for this new UI option as you do in other UI types.
> Note: MAUI Blazor is currently only available with the `app-pro` template.
### SaaS Module - Allowing entering a username while impersonating the tenant
In the previous versions, we were able to impersonate a tenant from the [SaaS Module's Tenant Management UI](https://docs.abp.io/en/commercial/7.1/modules/saas#tenant-management). There was a constraint in this approach, which forced us to only impersonate the "admin" user. However, the tenant might change the admin user's username, or we may want to impersonate another user of the tenant.
Thus, with this version, we decided to allow the impersonation of the tenant by the specified username.
*You can click on the "Login with this tenant" action button:*
![](saas-impersonation-1.png)
*Then, Specify the admin name of the tenant:*
![](saas-impersonation-2.png)
## Community News
### New ABP Community Posts
* [Sergei Gorlovetsky](https://community.abp.io/members/Sergei.Gorlovetsky) has created two new community articles:
* [Why ABP Framework is one of the best tools for migration from legacy MS Access systems to latest Web app](https://community.abp.io/posts/why-abp-framework-is-one-of-the-best-tools-for-migration-from-legacy-ms-access-systems-to-latest-web-app-7l39eof0)
* [ABP Framework — 5 steps Go No Go Decision Tree](https://community.abp.io/posts/abp-framework-5-steps-go-no-go-decision-tree-2sy6r2st)
* [Onur Pıçakcı](https://github.com/onurpicakci) has created his first ABP community article that explains how to contribute to ABP Framework. You can read it 👉 [here](https://community.abp.io/posts/how-to-contribute-to-abp-framework-46dvzzvj).
* [Maliming](https://github.com/maliming) has created a new community article to show how to convert create/edit modals to a page. You can read it 👉 [here](https://community.abp.io/posts/converting-createedit-modal-to-page-4ps5v60m).
We thank you all. We thank all the authors for contributing to the [ABP Community platform](https://community.abp.io/).
### Volosoft Attended NDC London 2023
![](ndc-london.png)
Core team members of the ABP Framework, [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) and [Alper Ebicoglu](https://twitter.com/alperebicoglu) attended [NDC London 2023](https://ndclondon.com/) from the 23rd to the 27th of January.
### Community Talks 2023.1: LeptonX Customization
![](community-talks-conver-image.png)
In this episode of ABP Community Talks, 2023.1; we'll talk about **LeptonX Customization**. We will dive into the details and show you how to customize the [LeptonX Theme](https://leptontheme.com/) with examples.
The event will be live on Thursday, February 16, 2023 (20:00 - 21:00 UTC).
> Register to listen and ask your questions now 👉 https://kommunity.com/volosoft/events/abp-community-talks-20231-leptonx-customization-03f9fd8c.
## Conclusion
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.1/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try the ABP v7.1 RC and provide feedback to help us release a more stable version.
Thanks for being a part of this community!

BIN
docs/en/Blog-Posts/2023-02-08 v7_1_Preview/community-talks-conver-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
docs/en/Blog-Posts/2023-02-08 v7_1_Preview/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Blog-Posts/2023-02-08 v7_1_Preview/ndc-london.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/en/Blog-Posts/2023-02-08 v7_1_Preview/suite-blazor-wasm-nolayers.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

1
docs/en/CLI.md

@ -138,6 +138,7 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample
* `--ui` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: * `--ui` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks:
* `mvc`: ASP.NET Core MVC. * `mvc`: ASP.NET Core MVC.
* `angular`: Angular UI. * `angular`: Angular UI.
* `blazor`: Blazor UI.
* `blazor-server`: Blazor Server UI. * `blazor-server`: Blazor Server UI.
* `none`: Without UI. * `none`: Without UI.
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:

105
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/how-can-I-contribute-to-open-source-projects-on-github.md

@ -0,0 +1,105 @@
# How to Contribute to ABP Framework
## Introduction
In this article I will explain how you can contribute to the open source ABP Framework. You will not only learn about the ABP Framework, but also how to contribute to an open source project, what are the standard rules, some git operations, etc.
## What is Open Source?
Open source software is code designed to be publicly available. Anyone can view, use, modify and distribute the project and code. The fact that the code is open source makes it a natural community and open for improvement. This enables ideas and thoughts to spread rapidly.
## What is ABP Framework?
ABP Framework is a complete infrastructure for building modern web applications following the best practices and guidelines of software development. ABP Framework is completely free, [open source](https://github.com/abpframework) and community driven. ABP is a modular framework and Application Modules provide pre-built application functionalities.
## Before Contribution
Before making any changes and trying to push them to the target repository we need to create a new [issue](https://github.com/abpframework/abp/issues) if there are no issues with the work. If there is an existing issue, you can proceed through this issue. This way, no other developer will work on the same issue and your PR will have a better chance to be accepted.
Previous ABP Community Talk on this topic can be found [here](https://www.youtube.com/watch?v=Wz4Z-O-YoPg).
## GitHub Issues
You may want to fix a known bug or work on a planned enhancement. See the [issue list](https://github.com/abpframework/abp/issues) on GitHub.
## Feature Requests
If you have a feature idea for the framework or modules, create an issue on GitHub or attend an existing discussion. Then you can implement it if it's embraced by the community.
## How to Contribute to an Open Source Software?
There are some steps to contribute to OSS projects. You can follow the steps below.
## Step 1: Fork the Project
The first thing we need to do now is to fork the open source project. Forking will create a copy of the project in your own GitHub account. This will allow users to make changes to the code without affecting the original repository. Just press the fork key in the project.
![fork-image](images/fork-project-image.png)
After forking, it will create a new repo in your own GitHub profile.
![fork-image-profile](images/fork-project-profile.png)
## Step 2: Clone the Project
In order to develop the project, you need to clone it to your local. After clicking on the code button, select your preferred cloning method and copy the link. You can run the copied link on your local machine with the `git clone` command, but we will use GitHub Desktop. Press `Open with GitHub Desktop` and the repo will be installed on your local machine.
or alternatively use the `git clone https://github.com/username/abp.git` command
![clone-image](images/clone-image.png)
## Step 3: Create a New Branch
In this step, you need to create a new branch of your own before you start developing it. Open the repo on GitHub Desktop and create a new branch. When creating a new branch, be careful which branch you create it on.
or alternatively use the `git checkout -b new-branch` command
![branch-image](images/branch-image.png)
## Step 4: Development
Choose a suitable IDE to develop on the new branch you created. In order not to complicate things, we will create a `Developers.md` file and process it. Let's enter a sample text in the Developers file.
![developer-list](images/developer-list.png)
As you can see, all changes made to the repo are reflected directly on GitHub Desktop.
![github-desktop-change](images/github-desktop-change.png)
## Step 5: Commit
The commit operation is used to save the changes you have made. It is useful to commit after certain operations are done in the project. It is useful to write a short sentence describing what you've done for the changes made in each commit. Press the `Commit to <branch-name>` button to commit.
or alternatively use the `git add .` and `git commit -m "Added the Developer List"` command
![commit-image](images/commit-image.png)
## Step 6: Publish the Changes
The changes you have made so far are only visible on your local machine. You need to publish these changes to submit them to your forked repository. Please press the publish branch button to publish.
or alternatively use the `git push origin new-branch` command
![push-image](images/git-push-image.png)
## Step 7: Create a Pull Request
After the push, the pull request `Create Pull Request` button will appear on GitHub Desktop. Click it and create a pull request.
![github-desktop-pull-request](images/github-desktop-pull-request.png)
You can also make a pull request from the repo in your GitHub profile.
![compare-pull-request](images/pull-request-image.png)
Before creating the pull request, make sure that the branch you created is making changes to the correct branch. After briefly describing your changes in the title and description, click the `Create pull request` button. This will send a pull request to the original repository. If the pull request is approved and merged by the community, your changes will also appear in the main repository.
![open-pull-request](images/open-pull-request-image.png)
That's it! You have contributed your development to an open source project.
## Conclusion
In this article, I showed you how you could contribute to the ABP Framework, an open source and community driven project. Thank you for reading the article, I hope it was useful. See you soon!
## References
- https://opensource.guide/how-to-contribute/
- https://docs.abp.io/en/abp/latest/Contribution/Index

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/branch-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/clone-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/commit-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/developer-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/fork-project-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/fork-project-profile.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/git-push-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/github-desktop-change.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/github-desktop-pull-request.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/open-pull-request-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
docs/en/Community-Articles/2023-01-30-How-To-Contribute-To-a-Open-Source-Software/images/pull-request-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

86
docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/POST.md

@ -0,0 +1,86 @@
# Converting Create/Edit Modal to Page
In this document we will explain how to convert BookStore's `Books` create & edit modals to regular razor pages.
## Before
![before](images/old.gif)
## Now
![after](images/new.gif)
## Index page
Repalce `abp-button(NewBookButton)` buttom with `<a class="btn btn-primary" href="/Books/CreateModal"><i class="fa fa-plus"></i> @L["NewBook"].Value</a>`.
## Index js file
Remove the related codes of `createModal` and `editModal`.
Change the `Edit row action` with `location.href = "/Books/EditModal?id=" + data.record.id;`
## Create/Edit Book page
Remove `Layout = null;` and add some custom style and javascript code to `CreateModal.cshtml` & `EditModal.cshtml`.
```csharp
@section styles {
<style>
.abp-view-modal .modal {
position: static;
display: block;
opacity: inherit !important;
}
.abp-view-modal .modal.fade .modal-dialog {
transition: inherit !important;
transform: inherit !important;;
}
.abp-view-modal .modal-header .btn-close {
display: none;
}
</style>
}
@section scripts {
<script>
$(".abp-view-modal form").abpAjaxForm().on('abp-ajax-success', function () {
location.href = "/Books";
});
</script>
}
```
Add a `div` element with `abp-view-modal` class to wrap the `abp-dynamic-form`, Set size of `abp-modal` to `ExtraLarge` and remove the `AbpModalButtons.Cancel` button from `abp-modal-footer`.
### CreateModal
```csharp
<div class="abp-view-modal">
<abp-dynamic-form abp-model="Book" asp-page="/Books/CreateModal">
<abp-modal static="true" size="ExtraLarge">
<abp-modal-header title="@L["NewBook"].Value"></abp-modal-header>
<abp-modal-body>
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
</div>
```
### EditModal
```csharp
<div class="abp-view-modal">
<abp-dynamic-form abp-model="Book" asp-page="/Books/EditModal">
<abp-modal size="ExtraLarge">
<abp-modal-header title="@L["Update"].Value"></abp-modal-header>
<abp-modal-body>
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
</div>
```
You can check this Git commit for details.
https://github.com/abpframework/abp-samples/commit/f3014e0ec422cb2d8816d0e00dd6ab9cc1adfc21

BIN
docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/new.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

BIN
docs/en/Community-Articles/2023-02-06-Converting-Create-Edit-Modal-To-Page/images/old.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

10
docs/en/Dependency-Injection.md

@ -440,16 +440,16 @@ Use `ICachedServiceProvider` (instead of `ITransientCachedServiceProvider`) unle
## Advanced Features ## Advanced Features
### IServiceCollection.OnRegistred Event ### IServiceCollection.OnRegistered Event
You may want to perform an action for every service registered to the dependency injection. In the `PreConfigureServices` method of your module, register a callback using the `OnRegistred` method as shown below: You may want to perform an action for every service registered to the dependency injection. In the `PreConfigureServices` method of your module, register a callback using the `OnRegistered` method as shown below:
````csharp ````csharp
public class AppModule : AbpModule public class AppModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.OnRegistred(ctx => context.Services.OnRegistered(ctx =>
{ {
var type = ctx.ImplementationType; var type = ctx.ImplementationType;
//... //...
@ -465,7 +465,7 @@ public class AppModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.OnRegistred(ctx => context.Services.OnRegistered(ctx =>
{ {
if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true)) if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true))
{ {
@ -478,7 +478,7 @@ public class AppModule : AbpModule
This example simply checks if the service class has `MyLogAttribute` attribute and adds `MyLogInterceptor` to the interceptor list if so. This example simply checks if the service class has `MyLogAttribute` attribute and adds `MyLogInterceptor` to the interceptor list if so.
> Notice that `OnRegistred` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](Dynamic-Proxying-Interceptors.md) of dynamic proxying / interceptors. > Notice that `OnRegistered` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](Dynamic-Proxying-Interceptors.md) of dynamic proxying / interceptors.
## 3rd-Party Providers ## 3rd-Party Providers

20
docs/en/Entity-Framework-Core.md

@ -834,7 +834,7 @@ One advantage of using an interface for a DbContext is then it will be replaceab
Once you properly define and use an interface for DbContext, then any other implementation can use the following ways to replace it: Once you properly define and use an interface for DbContext, then any other implementation can use the following ways to replace it:
**ReplaceDbContextAttribute** #### ReplaceDbContext Attribute
```csharp ```csharp
[ReplaceDbContext(typeof(IBookStoreDbContext))] [ReplaceDbContext(typeof(IBookStoreDbContext))]
@ -844,7 +844,7 @@ public class OtherDbContext : AbpDbContext<OtherDbContext>, IBookStoreDbContext
} }
``` ```
**ReplaceDbContext option** #### ReplaceDbContext Option
````csharp ````csharp
context.Services.AddAbpDbContext<OtherDbContext>(options => context.Services.AddAbpDbContext<OtherDbContext>(options =>
@ -856,6 +856,22 @@ context.Services.AddAbpDbContext<OtherDbContext>(options =>
In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime. In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime.
#### Replacing with Multi-Tenancy
It is also possible to replace a DbContext based on the [multi-tenancy](Multi-Tenancy.md) side. `ReplaceDbContext` attribute and `ReplaceDbContext` method can get a `MultiTenancySides` option with a default value of `MultiTenancySides.Both`.
**Example:** Replace DbContext only for tenants, using the `ReplaceDbContext` attribute
````csharp
[ReplaceDbContext(typeof(IBookStoreDbContext), MultiTenancySides.Tenant)]
````
**Example:** Replace DbContext only for the host side, using the `ReplaceDbContext` method
````csharp
options.ReplaceDbContext<IBookStoreDbContext>(MultiTenancySides.Host);
````
### Split Queries ### Split Queries
ABP enables [split queries](https://docs.microsoft.com/en-us/ef/core/querying/single-split-queries) globally by default for better performance. You can change it as needed. ABP enables [split queries](https://docs.microsoft.com/en-us/ef/core/querying/single-split-queries) globally by default for better performance. You can change it as needed.

25
docs/en/Migration-Guides/Abp-7_0.md

@ -110,6 +110,31 @@ See https://github.com/abpframework/abp/pull/13845 for more info.
> You can ignore this if you don't use CMS Kit Module. > You can ignore this if you don't use CMS Kit Module.
## Data migration environment
Please call `AddDataMigrationEnvironment` method in the migration project.
```cs
using (var application = await AbpApplicationFactory.CreateAsync<MyMigratorModule>(options =>
{
//...
options.AddDataMigrationEnvironment();
}))
{
//...
}
```
```cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDataMigrationEnvironment();
// Call AddDataMigrationEnvironment before AddApplicationAsync
await builder.AddApplicationAsync<MyMigratorModule>();
//...
```
See https://github.com/abpframework/abp/pull/13985 for more info.
## Devart.Data.Oracle.EFCore ## Devart.Data.Oracle.EFCore
The `Devart.Data.Oracle.EFCore` package do not yet support EF Core 7.0, If you use `AbpEntityFrameworkCoreOracleDevartModule(Volo.Abp.EntityFrameworkCore.Oracle.Devart)` may not work as expected, We will release new packages as soon as they are updated. The `Devart.Data.Oracle.EFCore` package do not yet support EF Core 7.0, If you use `AbpEntityFrameworkCoreOracleDevartModule(Volo.Abp.EntityFrameworkCore.Oracle.Devart)` may not work as expected, We will release new packages as soon as they are updated.

19
docs/en/Migration-Guides/Abp-7_1.md

@ -0,0 +1,19 @@
# ABP Version 7.1 Migration Guide
This document is a guide for upgrading ABP v7.0 solutions to ABP v7.1. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application.
## Navigation Menu - `CustomData` type changed to `Dictionary<string, object>`
`ApplicationMenu` and `ApplicationMenuItem` classes' `CustomData` property type has been changed to `Dictionary<string, object>`. So, if you use the optional `CustomData` property of these classes, change it accordingly. See [#15608](https://github.com/abpframework/abp/pull/15608) for more information.
*Old usage:*
```csharp
var menu = new ApplicationMenu("Home", L["Home"], "/", customData: new MyCustomData());
```
*New usage:*
```csharp
var menu = new ApplicationMenu("Home", L["Home"], "/").WithCustomData("CustomDataKey", new MyCustomData());
```

1
docs/en/Migration-Guides/Index.md

@ -2,6 +2,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). 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).
- [7.0 to 7.1](Abp-7_1.md)
- [6.0 to 7.0](Abp-7_0.md) - [6.0 to 7.0](Abp-7_0.md)
- [5.3 to 6.0](Abp-6_0.md) - [5.3 to 6.0](Abp-6_0.md)
- [5.2 to 5.3](Abp-5_3.md) - [5.2 to 5.3](Abp-5_3.md)

6
docs/en/Modules/Cms-Kit/Blogging.md

@ -2,6 +2,12 @@
The blogging feature provides the necessary UI to manage and render blogs and blog posts. The blogging feature provides the necessary UI to manage and render blogs and blog posts.
## Enabling the Blogging Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## User Interface ## User Interface
### Menu Items ### Menu Items

8
docs/en/Modules/Cms-Kit/Comments.md

@ -2,6 +2,12 @@
CMS kit provides a **comment** system to add the comment feature to any kind of resource, like blog posts, products, etc. CMS kit provides a **comment** system to add the comment feature to any kind of resource, like blog posts, products, etc.
## Enabling the Comment Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## Options ## Options
The comment system provides a mechanism to group comment definitions by entity types. For example, if you want to use the comment system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product`, and add comments under these entity types. The comment system provides a mechanism to group comment definitions by entity types. For example, if you want to use the comment system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product`, and add comments under these entity types.
@ -16,7 +22,7 @@ Configure<CmsKitCommentOptions>(options =>
}); });
``` ```
> If you're using the blog feature, the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above. > If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
`CmsKitCommentOptions` properties: `CmsKitCommentOptions` properties:

6
docs/en/Modules/Cms-Kit/Global-Resources.md

@ -2,6 +2,12 @@
CMS Kit Global Resources system allows to add global styles and scripts dynamically. CMS Kit Global Resources system allows to add global styles and scripts dynamically.
## Enabling the Global Resources Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## The User Interface ## The User Interface
### Menu items ### Menu items

7
docs/en/Modules/Cms-Kit/Index.md

@ -16,11 +16,12 @@ The following features are currently available:
* Provides a [**global resources**](Global-Resources.md) system to add global styles and scripts dynamically. * Provides a [**global resources**](Global-Resources.md) system to add global styles and scripts dynamically.
* Provides a [**Dynamic Widget**](Dynamic-Widget.md) system to create dynamic widgets for page and blog posts. * Provides a [**Dynamic Widget**](Dynamic-Widget.md) system to create dynamic widgets for page and blog posts.
Click to a feature to understand and learn how to use it. > You can click on the any feature links above to understand and learn how to use it.
All features are individually usable. If you disable a feature, it completely disappears from your application, even from the database tables, by the help of the [Global Features](../../Global-Features.md) system. All features are individually usable. If you disable a feature, it completely disappears from your application, even from the database tables, with the help of the [Global Features](../../Global-Features.md) system.
## Pre Requirements ## Pre Requirements
- This module depends on [BlobStoring](../../Blob-Storing.md) module for keeping media content. - This module depends on [BlobStoring](../../Blob-Storing.md) module for keeping media content.
> Make sure `BlobStoring` module is installed and at least one provider is configured properly. For more information, check the [documentation](../../Blob-Storing.md). > Make sure `BlobStoring` module is installed and at least one provider is configured properly. For more information, check the [documentation](../../Blob-Storing.md).
@ -62,7 +63,7 @@ GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit =>
This module follows the [module development best practices guide](https://docs.abp.io/en/abp/latest/Best-Practices/Index) and consists of several NuGet and NPM packages. See the guide if you want to understand the packages and relations between them. This module follows the [module development best practices guide](https://docs.abp.io/en/abp/latest/Best-Practices/Index) and consists of several NuGet and NPM packages. See the guide if you want to understand the packages and relations between them.
CMS kit packages are designed for various usage scenarios. If you check the [CMS kit packages](https://www.nuget.org/packages?q=Volo.CmsKit), you will see that some packages have `Admin` and `Public` suffixes. The reason is that the module has two application layers, considering they might be used in different type of applications. These application layers uses a single domain layer. CMS kit packages are designed for various usage scenarios. If you check the [CMS kit packages](https://www.nuget.org/packages?q=Volo.CmsKit), you will see that some packages have `Admin` and `Public` suffixes. The reason is that the module has two application layers, considering they might be used in different type of applications. These application layers uses a single domain layer:
- `Volo.CmsKit.Admin.*` packages contain the functionalities required by admin (back office) applications. - `Volo.CmsKit.Admin.*` packages contain the functionalities required by admin (back office) applications.
- `Volo.CmsKit.Public.*` packages contain the functionalities used in public websites where users read blog posts or leave comments. - `Volo.CmsKit.Public.*` packages contain the functionalities used in public websites where users read blog posts or leave comments.

18
docs/en/Modules/Cms-Kit/Menus.md

@ -1,7 +1,13 @@
# CMS Kit: Pages # CMS Kit: Menus
CMS Kit Menu system allows to manage public menus dynamically. CMS Kit Menu system allows to manage public menus dynamically.
## Enabling the Menu Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## The User Interface ## The User Interface
### Menu items ### Menu items
@ -20,22 +26,18 @@ Menus page is used to manage dynamic public menus in the system.
![cms-kit-menus-page](../../images/cmskit-module-menus-page.png) ![cms-kit-menus-page](../../images/cmskit-module-menus-page.png)
The created menu items will be visible on the public-web side, as shown below:
Created menus will be visible on public site.
![cms-kit-public-menus](../../images//cmskit-module-menus-public.png) ![cms-kit-public-menus](../../images//cmskit-module-menus-public.png)
# Internals ## Internals
## Domain Layer ### Domain Layer
#### Aggregates #### Aggregates
This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide.
##### Menus
- `MenuItem` (aggregate root): A Menu Item presents a single node at menu tree. - `MenuItem` (aggregate root): A Menu Item presents a single node at menu tree.
#### Repositories #### Repositories

8
docs/en/Modules/Cms-Kit/Pages.md

@ -2,6 +2,12 @@
CMS Kit Page system allows you to create dynamic pages by specifying URLs, which is the fundamental feature of a CMS. CMS Kit Page system allows you to create dynamic pages by specifying URLs, which is the fundamental feature of a CMS.
## Enabling the Pages Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## The User Interface ## The User Interface
### Menu items ### Menu items
@ -24,5 +30,5 @@ You can create or edit an existing page on this page.
![pages-edit](../../images/cmskit-module-pages-edit.png) ![pages-edit](../../images/cmskit-module-pages-edit.png)
When you create a page, you can access the created page via `/pages/{slug}` URL. When you create a page, you can access the created page via `/{slug}` URL.

8
docs/en/Modules/Cms-Kit/Ratings.md

@ -4,6 +4,12 @@ CMS kit provides a **rating** system to to add ratings feature to any kind of re
![ratings](../../images/cmskit-module-ratings.png) ![ratings](../../images/cmskit-module-ratings.png)
## Enabling the Rating Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## Options ## Options
The rating system provides a mechanism to group ratings by entity types. For example, if you want to use the rating system for products, you need to define an entity type named `Product` and then add ratings under the defined entity type. The rating system provides a mechanism to group ratings by entity types. For example, if you want to use the rating system for products, you need to define an entity type named `Product` and then add ratings under the defined entity type.
@ -17,7 +23,7 @@ Configure<CmsKitRatingOptions>(options =>
}); });
``` ```
> If you're using the blog feature, the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above. > If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
`CmsKitRatingOptions` properties: `CmsKitRatingOptions` properties:

8
docs/en/Modules/Cms-Kit/Reactions.md

@ -8,6 +8,12 @@ Reaction component allows users to react to your content via pre-defined icons/e
You can also customize the reaction icons shown in the reaction component. You can also customize the reaction icons shown in the reaction component.
## Enabling the Reaction Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## Options ## Options
Reaction system provides a mechanism to group reactions by entity types. For example, if you want to use the reaction system for products, you need to define an entity type named `Product`, and then add reactions under the defined entity type. Reaction system provides a mechanism to group reactions by entity types. For example, if you want to use the reaction system for products, you need to define an entity type named `Product`, and then add reactions under the defined entity type.
@ -32,7 +38,7 @@ Configure<CmsKitReactionOptions>(options =>
}); });
``` ```
> If you're using the comment or blog features, the ABP framework defines predefined reactions for these features automatically. > If you're using the [Comment](Comments.md) or [Blogging](Blogging.md) features, the ABP framework defines predefined reactions for these features automatically.
`CmsKitReactionOptions` properties: `CmsKitReactionOptions` properties:

8
docs/en/Modules/Cms-Kit/Tags.md

@ -2,6 +2,12 @@
CMS kit provides a **tag** system to tag any kind of resources, like a blog post. CMS kit provides a **tag** system to tag any kind of resources, like a blog post.
## Enabling the Tag Management Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## Options ## Options
The tag system provides a mechanism to group tags by entity types. For example, if you want to use the tag system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product` and add tags under these entity types. The tag system provides a mechanism to group tags by entity types. For example, if you want to use the tag system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product` and add tags under these entity types.
@ -17,7 +23,7 @@ Configure<CmsKitTagOptions>(options =>
}); });
``` ```
> If you're using the blog feature, the ABP framework defines an entity type for the blog feature automatically. > If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically.
`CmsKitTagOptions` properties: `CmsKitTagOptions` properties:

575
docs/en/Modules/Database-Tables.md

@ -0,0 +1,575 @@
# Database Tables
This documentation describes all database tables and their purposes. You can read this documentation to get general knowledge of the database tables that come from each module.
## [Audit Logging Module](Audit-Logging.md)
### AbpAuditLogs
This table stores information about the audit logs in the application. Each record represents an audit log and tracks the actions performed in the application.
### AbpAuditLogActions
This table stores information about the actions performed in the application, which are logged for auditing purposes.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpAuditLogs](#abpauditlogs) | Id | Links each action to a specific audit log. |
### AbpEntityChanges
This table stores information about entity changes in the application, which are logged for auditing purposes.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpAuditLogs](#abpauditlogs) | Id | Links each entity change to a specific audit log. |
### AbpEntityPropertyChanges
This table stores information about property changes to entities in the application, which are logged for auditing purposes.
## Uses
| Table | Column | Description |
| --- | --- | --- |
| [AbpEntityChanges](#abpentitychanges) | Id | Links each property change to a specific entity change. |
## [Background Jobs Module](Background-Jobs.md)
### AbpBackgroundJobs
This table stores information about the background jobs in the application and facilitates their efficient management and tracking. Each entry in the table contains details of a background job, including the job name, arguments, try count, next try time, last try time, abandoned status, and priority.
## [Tenant Management Module](Tenant-Management.md)
### AbpTenants
This table stores information about the tenants. Each record represents a tenant and contains information about the tenant, such as name and other details.
### AbpTenantConnectionStrings
This table stores information about the tenant database connection strings. When you define a connection string for a tenant, a new record will be added to this table. You can query this database to get connection strings by tenants.
## Uses
| Table | Column | Description |
| --- | --- | --- |
| [AbpTenants](#abptenants) | Id | The `Id` column in the `AbpTenants` table is used to associate the tenant connection string with the corresponding tenant. |
## Blogging Module
### BlgUsers
This table stores information about the blog users. When a new identity user is created, a new record will be added to this table.
### BlgBlogs
This table serves to store blog information and semantically separates the posts of each blog.
### BlgPosts
This table stores information about the blog posts. You can query this table to get blog posts by blogs.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [BlgBlogs](#blgblogs) | Id | To associate the blog post with the corresponding blog. |
### BlgComments
This table stores information about the comments made on blog posts. You can query this table to get comments by posts.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [BlgPosts](#blgposts) | Id | Links the comment to the corresponding blog post. |
| [BlgComments](#blgcomments) | Id | Links the comment to the parent comment. |
### BlgTags
This table stores information about the tags. When a new tag is used, a new record will be added to this table. You can query this table to get tags by blogs.
### BlgPostTags
This table is used to associate tags with blog posts in order to categorize and organize the content. You can query this table to get post tags by posts.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [BlgTags](#blgtags) | Id | Links the post tag to the corresponding tag. |
| [BlgPosts](#blgposts) | Id | Links the post tag to the corresponding blog post. |
## [CMS Kit Module](Cms-Kit/Index.md)
### CmsUsers
This table stores information about the CMS Kit module users. When a new identity user is created, a new record will be added to this table.
### CmsBlogs
This table serves to store blog information and semantically separates the posts of each blog.
### CmsBlogPosts
This table stores information about the blog posts. You can query this table to get blog posts by blogs.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [CmsUsers](#cmsusers) | Id | Links the blog post to the corresponding author. |
### CmsBlogFeatures
This table stores information about the blog features. You can query this table to get blog features by blogs.
### CmsComments
This table is utilized by the [CMS Kit Comment system](Cms-Kit/Comments.md) to store comments made on the blog posts. You can query this table to get comments by posts.
### CmsTags
This table stores information about the tags. When a new tag is used, a new record will be added to this table. You can query this table to get tags by blogs.
### CmsEntityTags
This table is utilized by the [Tag Management system](Cms-Kit/Tags.md) to store tags and their relationship with various entities, thus enabling efficient categorization and organization of content. You can query this table to get entity tags by entities.
### CmsGlobalResources
This table is a database table for the [CMS Kit Global Resources system](Cms-Kit/Global-Resources.md), allowing dynamic addition of global styles and scripts.
### CmsMediaDescriptors
This table is utilized by the CMS kit module to manage media files by using the [BlobStoring](../Blob-Storing.md) module.
### CmsMenuItems
This table is used by the [CMS Kit Menu system](Cms-Kit/Menus.md) to manage and store information about dynamic public menus, including details such as menu item display names, URLs, and hierarchical relationships.
### CmsPages
This table is utilized by the [CMS Kit Page system](Cms-Kit/Pages.md) to store dynamic pages within the application, including information such as page URLs, titles, and content.
### CmsRatings
This table is utilized by the [CMS Kit Rating system](Cms-Kit/Ratings.md) to store ratings made on blog posts. You can query this table to get ratings by posts.
### CmsUserReactions
This table is utilized by the [CMS Kit Reaction system](Cms-Kit/Reactions.md) to store reactions made on blog posts. You can query this table to get reactions by posts.
## [Docs Module](Docs.md)
### DocsProjects
This table stores project information to categorize documents according to different projects.
### DocsDocuments
This table retrieves the document if it's not found in the cache. The documentation is being updated when the content is retrieved from the database.
### DocsDocumentContributors
This table stores information about the contributors of the documents. You can query this table to get document contributors by documents.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [DocsDocuments](#docsdocuments) | Id | Links the document contributor to the corresponding document. |
## [Feature Management Module](Feature-Management.md)
### AbpFeatureGroups
This table stores information about the feature groups in the application. For example, you can group all the features in the [`AbpFeatures`](#abpfeatures) table related to the `Identity` module under the `Identity` group.
### AbpFeatures
This table stores information about the features in the application. You can use the `Name` column to link each feature with its corresponding feature value in the [`AbpFeatureValues`](#abpfeaturevalues) table, so that you can easily manage and organize the features.
### AbpFeatureValues
This table stores the values of the features for different providers. You can use the `Name` column to link each feature value with its corresponding feature in the [`AbpFeatures`](#abpfeatures) table, so that you can easily manage and organize the features.
## [Identity Module](Identity.md)
### AbpUsers
This table stores information about the identity users in the application.
### AbpRoles
This table stores information about the roles in the application. Roles are used to manage and control access to different parts of the application by assigning permissions and claims to roles and then assigning those roles to users. This table is important for managing and organizing the roles in the application, and for defining the access rights of the users.
### AbpClaimTypes
This table stores information about the claim types used in the application. You can use the `Name`, `Regex` columns to filter the claim types by name, and regex pattern respectively, so that you can easily manage and track the claim types in the application.
### AbpLinkUsers
This table is useful for linking multiple user accounts across different tenants or applications to a single user, allowing them to easily switch between their accounts.
### AbpUserClaims
This table can manage user-based access control by allowing to assign claims to users, which describes the access rights of the individual user.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpUsers](#abpusers) | Id | Links the user claim to the corresponding user. |
### AbpUserLogins
This table can store information about the user's external logins such as login with Facebook, Google, etc. and it can also be used to track the login history of users.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpUsers](#abpusers) | Id | Links the user login to the corresponding user. |
### AbpUserRoles
This table can manage user-based access control by allowing to assign roles to users, which describe the access rights of the individual user.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpUsers](#abpusers) | Id | Links the user role to the corresponding user. |
| [AbpRoles](#abproles) | Id | Links the user role to the corresponding role. |
### AbpUserTokens
This table can store information about user's refresh tokens, access tokens and other tokens used in the application. It can also be used to invalidate or revoke user tokens.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpUsers](#abpusers) | Id | Links the user token to the corresponding user. |
### AbpOrganizationUnits
This table is useful for creating and managing a hierarchical structure of the organization, allowing to group users and assign roles based on the organization structure. You can use the `Code`, `ParentId` columns to filter the organization units by code and parent id respectively, so that you can easily manage and track the organization units in the application.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpOrganizationUnits](#abporganizationunits) | ParentId | Links the organization unit to its parent organization unit. |
### AbpOrganizationUnitRoles
This table is useful for managing role-based access control at the level of organization units, allowing to assign different roles to different parts of the organization structure. You can use the `OrganizationUnitId`, `RoleId` columns to filter the roles by organization unit id and role id respectively, so that you can easily manage and track the roles assigned to organization units in the application.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpOrganizationUnits](#abporganizationunits) | Id | Links the organization unit role to the corresponding organization unit. |
| [AbpRoles](#abproles) | Id | Links the organization unit role to the corresponding role. |
### AbpUserOrganizationUnits
This table stores information about the organization units assigned to the users in the application. This table can manage user-organization unit relationships, and to group users based on the organization structure.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpUsers](#abpusers) | Id | Links the user organization unit to the corresponding user. |
| [AbpOrganizationUnits](#abporganizationunits) | Id | Links the user organization unit to the corresponding organization unit. |
### AbpRoleClaims
This table is useful for managing role-based access control by allowing to assign claims to roles, which describes the access rights of the users that belong to that role.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpRoles](#abproles) | Id | Links the role claim to the corresponding role. |
### AbpSecurityLogs
This table logs important operations and changes related to user accounts, allowing users to save the security logs for future reference.
## [Permission Management](Permission-Management.md)
### AbpPermissionGroups
This table is important for managing and organizing the permissions in the application, by grouping them into logical categories.
### AbpPermissions
This table is important for managing and controlling access to different parts of the application and for defining the granular permissions that make up the larger permissions or roles.
### AbpPermissionGrants
The table stores and manage the permissions in the application and to keep track of permissions that are granted, to whom and when. Columns such as `Name`, `ProviderName`, `ProviderKey`, `TenantId` can be used to filter the granted permissions by name, provider name, provider key, and tenant id respectively, so that you can easily manage and track the granted permissions in the application.
## [Setting Management](Setting-Management.md)
### AbpSettings
This table stores key-value pairs of settings for the application, and it allows dynamic configuration of the application without the need for recompilation.
## [OpenIddict](OpenIddict.md)
### OpenIddictApplications
This table can store information about the OpenID Connect applications, including the client id, client secret, redirect URI, and other relevant information. It can also be used to authenticate and authorize clients using OpenID Connect protocol.
### OpenIddictAuthorizations
This table stores the OpenID Connect authorization data in the application. It can also be used to manage and validate the authorization grants issued to clients and users.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [OpenIddictApplications](#openiddictapplications) | Id | Links the authorization to the corresponding application. |
### OpenIddictTokens
This table can store information about the OpenID Connect tokens, including the token payload, expiration, type, and other relevant information. It can also be used to manage and validate the tokens issued to clients and users, such as access tokens and refresh tokens, and to control access to protected resources.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [OpenIddictApplications](#openiddictapplications) | Id | Links the token to the corresponding application. |
| [OpenIddictAuthorizations](#openiddictauthorizations) | Id | Links the token to the corresponding authorization. |
### OpenIddictScopes
This table can store information about the OpenID Connect scopes, including the name and description of the scope. It can also be used to define the permissions or access rights associated with the scopes, which are then used to control access to protected resources.
## [IdentityServer](IdentityServer.md)
### IdentityServerApiResources
This table can store information about the API resources, including the resource name, display name, description, and other relevant information. It can also be used to define the scopes, claims, and properties associated with the API resources, which are then used to control access to protected resources.
### IdentityServerIdentityResources
This table can store information about the identity resources, including the name, display name, description, and enabled status.
### IdentityServerClients
This table can store information about the clients, including the client id, client name, client URI and other relevant information. It can also be used to define the scopes, claims, and properties associated with the clients, which are then used to control access to protected resources.
### IdentityServerApiScopes
This table can store information about the API scopes, including the scope name, display name, description, and other relevant information. It can also be used to define the claims and properties associated with the API scopes, which are then used to control access to protected resources.
### IdentityServerApiResourceClaims
This table can store information about the claims of an API resource, including the claim type and API resource id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the claim to the corresponding API resource. |
### IdentityServerIdentityResourceClaims
This table can store information about the claims of an identity resource, including the claim type and identity resource id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerIdentityResources](#identityserveridentityresources) | Id | Links the claim to the corresponding identity resource. |
### IdentityServerClientClaims
This table can store information about the claims of a client, including the claim type, claim value and client id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerClients](#identityserverclients) | Id | Links the claim to the corresponding client. |
### IdentityServerApiScopeClaims
This table can store information about the claims of an API scope, including the claim type and API scope id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerApiScopes](#identityserverapiscopes) | Id | Links the claim to the corresponding API scope. |
### IdentityServerApiResourceProperties
This table can store information about properties, including the property key and value, and the associated API resource. These properties can store additional metadata or configuration information related to the API resources.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the property to the corresponding API resource. |
### IdentityServerIdentityResourceProperties
This table can store information about properties, including the property key and value, and the associated identity resource. These properties can store additional metadata or configuration information related to the identity resources.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerIdentityResources](#identityserveridentityresources) | Id | Links the property to the corresponding identity resource. |
### IdentityServerClientProperties
This table can be store information about the properties of a client, including the key, value and client id. These properties can store additional metadata or configuration information related to the clients.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerClients](#identityserverclients) | Id | Links the property to the corresponding client. |
### IdentityServerApiScopeProperties
This table can store information about the properties of an API scope, including the key, value and API scope id. These properties can store additional metadata or configuration information related to the API scopes.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerApiScopes](#identityserverapiscopes) | Id | Links the property to the corresponding API scope. |
### IdentityServerApiResourceScopes
This table can store information about the scopes of an API resource, including the scope name and API resource id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the scope to the corresponding API resource. |
### IdentityServerClientScopes
This table can store information about the scopes of a client, including the scope and client id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerClients](#identityserverclients) | Id | Links the scope to the corresponding client. |
### IdentityServerApiResourceSecrets
This table can store information about the secrets of an API resource, including the secret value, expiration date, and API resource id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the secret to the corresponding API resource. |
### IdentityServerClientSecrets
This table can store information about the secrets of a client, including the secret value, expiration date, and client id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerClients](#identityserverclients) | Id | Links the secret to the corresponding client. |
### IdentityServerClientCorsOrigins
This table can store information about the CORS origins of a client, including the origin and client id. It can also be used to manage and validate the CORS origins of a client.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerClients](#identityserverclients) | Id | Links the CORS origin to the corresponding client. |
### IdentityServerClientGrantTypes
This table can store information about the grant types of a client, including the grant type and client id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerClients](#identityserverclients) | Id | Links the grant type to the corresponding client. |
### IdentityServerClientIdPRestrictions
This table can store information about the identity provider restrictions of a client, including the identity provider and client id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerClients](#identityserverclients) | Id | Links the identity provider restriction to the corresponding client. |
### IdentityServerClientPostLogoutRedirectUris
This table can store information about the post logout redirect URIs of a client, including the post logout redirect URI and client id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerClients](#identityserverclients) | Id | Links the post logout redirect URI to the corresponding client. |
### IdentityServerClientRedirectUris
This table can store information about the redirect URIs of a client, including the redirect URI and client id.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [IdentityServerClients](#identityserverclients) | Id | Links the redirect URI to the corresponding client. |
### IdentityServerDeviceFlowCodes
This table can store information about the device flow codes, including the user code, device code, subject id, client id, creation time, expiration, data and session id.
### IdentityServerPersistedGrants
This table can store information about the persisted grants, including the key, type, subject id, client id, creation time, expiration, and data.
## Others
### AbpBlobContainers
This table is important for providing a better user experience by allowing the application to support multiple containers and providing BLOB-specific features.
### AbpBlobs
This table stores the binary data of BLOBs (binary large objects) in the application. Each BLOB is related to a container in the [AbpBlobContainers](#abpblobcontainers) table, where the container name, tenant id and other properties of the container can be found.
#### Foreign Keys
| Table | Column | Description |
| --- | --- | --- |
| [AbpBlobContainers](#abpblobcontainers) | Id | Links the BLOB to the corresponding container. |
### AbpLocalizationResources
This table stores the localization resources for the application. This table is important for providing a better user experience by allowing the application to support multiple resources and providing localized text and other localization-specific features.
### AbpLocalizationTexts
The table contains the resource name, culture name, and a json encoded value which holds the key-value pair of localization text. It allows for efficient storage and management of localization texts and allows for easy update or addition of new translations for specific resources and cultures.

20
docs/en/MongoDB.md

@ -393,7 +393,7 @@ One advantage of using interface for a MongoDbContext is then it becomes replace
Once you properly define and use an interface for a MongoDbContext , then any other implementation can use the following ways to replace it: Once you properly define and use an interface for a MongoDbContext , then any other implementation can use the following ways to replace it:
**ReplaceDbContextAttribute** #### ReplaceDbContext Attribute
```csharp ```csharp
[ReplaceDbContext(typeof(IBookStoreMongoDbContext))] [ReplaceDbContext(typeof(IBookStoreMongoDbContext))]
@ -403,7 +403,7 @@ public class OtherMongoDbContext : AbpMongoDbContext, IBookStoreMongoDbContext
} }
``` ```
**ReplaceDbContext option** #### ReplaceDbContext Option
```csharp ```csharp
context.Services.AddMongoDbContext<OtherMongoDbContext>(options => context.Services.AddMongoDbContext<OtherMongoDbContext>(options =>
@ -415,6 +415,22 @@ context.Services.AddMongoDbContext<OtherMongoDbContext>(options =>
In this example, `OtherMongoDbContext` implements `IBookStoreMongoDbContext`. This feature allows you to have multiple MongoDbContext (one per module) on development, but single MongoDbContext (implements all interfaces of all MongoDbContexts) on runtime. In this example, `OtherMongoDbContext` implements `IBookStoreMongoDbContext`. This feature allows you to have multiple MongoDbContext (one per module) on development, but single MongoDbContext (implements all interfaces of all MongoDbContexts) on runtime.
#### Replacing with Multi-Tenancy
It is also possible to replace a DbContext based on the [multi-tenancy](Multi-Tenancy.md) side. `ReplaceDbContext` attribute and `ReplaceDbContext` method can get a `MultiTenancySides` option with a default value of `MultiTenancySides.Both`.
**Example:** Replace DbContext only for tenants, using the `ReplaceDbContext` attribute
````csharp
[ReplaceDbContext(typeof(IBookStoreDbContext), MultiTenancySides.Tenant)]
````
**Example:** Replace DbContext only for the host side, using the `ReplaceDbContext` method
````csharp
options.ReplaceDbContext<IBookStoreDbContext>(MultiTenancySides.Host);
````
### Customize Bulk Operations ### Customize Bulk Operations
If you have better logic or using an external library for bulk operations, you can override the logic via implementing `IMongoDbBulkOperationProvider`. If you have better logic or using an external library for bulk operations, you can override the logic via implementing `IMongoDbBulkOperationProvider`.

8
docs/en/Repositories.md

@ -164,6 +164,14 @@ If your entity is a soft-delete entity, you can use the `HardDeleteAsync` method
> See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete. > See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete.
### Delete Direct
`DeleteDirectAsync` method of the repository deletes all entities those fit to the given predicate. It directly deletes entities from database, without fetching them.
Some features (like soft-delete, multi-tenancy and audit logging) won't work, so use this method carefully when you need it. Use the `DeleteAsync` method if you need to these features.
> Currently only [EF Core supports it](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#basic-executedelete-examples), For the ORMs doesn't support direct delete, we will fallback to the existing `DeleteAsync` method.
### Ensure Entities Exists ### Ensure Entities Exists
The `EnsureExistsAsync` extension method accepts entity id or entities query expression to ensure entities exist, otherwise, it will throw `EntityNotFoundException`. The `EnsureExistsAsync` extension method accepts entity id or entities query expression to ensure entities exist, otherwise, it will throw `EntityNotFoundException`.

5
docs/en/Startup-Templates/Application-Single-Layer.md

@ -32,19 +32,18 @@ abp new Acme.BookStore -t app-nolayers
This template provides multiple UI frameworks: This template provides multiple UI frameworks:
* `mvc`: ASP.NET Core MVC UI with Razor Pages (default) * `mvc`: ASP.NET Core MVC UI with Razor Pages (default)
* `blazor`: Blazor UI
* `blazor-server`: Blazor Server UI * `blazor-server`: Blazor Server UI
* `angular`: Angular UI * `angular`: Angular UI
* `none`: Without UI (for HTTP API development) * `none`: Without UI (for HTTP API development)
> This template doesn't have Blazor WebAssembly UI, because it requires 3 projects at least (server-side, UI and shared library between these two projects). We are recommending to use the layered [application startup template](Application.md) for Blazor WebAssembly projects.
Use the `-u` (or `--ui`) option to specify the UI framework while creating the solution: Use the `-u` (or `--ui`) option to specify the UI framework while creating the solution:
```bash ```bash
abp new Acme.BookStore -t app-nolayers -u angular abp new Acme.BookStore -t app-nolayers -u angular
``` ```
This example specifies the UI type (the `-u` option) as `angular`. You can also specify `mvc`, `blazor-server` or `none` for the UI type. This example specifies the UI type (the `-u` option) as `angular`. You can also specify `mvc`, `blazor`, `blazor-server` or `none` for the UI type.
### Specify the Database Provider ### Specify the Database Provider

8
docs/en/Themes/LeptonXLite/Angular.md

@ -14,11 +14,15 @@ To add `LeptonX-lite` into your project,
- Install `@abp/ng.theme.lepton-x` - Install `@abp/ng.theme.lepton-x`
`yarn add @abp/ng.theme.lepton-x@preview` ```bash
yarn add @abp/ng.theme.lepton-x
```
- Install `bootstrap-icons` - Install `bootstrap-icons`
`yarn add bootstrap-icons` ```bash
yarn add bootstrap-icons
```
- Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one in the following link : - Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one in the following link :

116
docs/en/Tutorials/Todo/Single-Layer/Index.md

@ -3,7 +3,7 @@
````json ````json
//[doc-params] //[doc-params]
{ {
"UI": ["MVC", "BlazorServer", "NG"], "UI": ["MVC", "Blazor", "BlazorServer", "NG"],
"DB": ["EF", "Mongo"] "DB": ["EF", "Mongo"]
} }
```` ````
@ -14,6 +14,38 @@ This is a single-part quick-start tutorial to build a simple todo application wi
You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp-SingleLayer). You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp-SingleLayer).
{{if UI=="Blazor"}}
We are currently preparing a video tutorial for Blazor UI. You can watch other tutorials for the three UI types from [here](https://www.youtube.com/playlist?list=PLsNclT2aHJcPqZxk7D4tU8LtTeCFcN_ci).
{{else}}
This documentation has a video tutorial on **YouTube**!! You can watch it here:
{{end}}
{{if UI=="MVC" && DB =="EF"}}
<iframe width="560" height="315" src="https://www.youtube.com/embed/Z6jZSPB19iw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
{{else if UI=="BlazorServer" && DB=="EF"}}
<iframe width="560" height="315" src="https://www.youtube.com/embed/-ynMYXBIg4Q" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
{{else if UI=="NG" && DB=="EF"}}
<iframe width="560" height="315" src="https://www.youtube.com/embed/Pz4YWsU7CUs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
{{else if UI=="MVC" && DB=="Mongo"}}
<iframe width="560" height="315" src="https://www.youtube.com/embed/i9oDVl1J7Dk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
{{else if UI=="BlazorServer" && DB=="Mongo"}}
<iframe width="560" height="315" src="https://www.youtube.com/embed/z7YGDjcsTTs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
{{else if UI=="NG" && DB=="Mongo"}}
<iframe width="560" height="315" src="https://www.youtube.com/embed/LdKlIHi9S8I" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
{{end}}
## Pre-Requirements ## Pre-Requirements
* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 7.0+](https://dotnet.microsoft.com/download/dotnet) development. * An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 7.0+](https://dotnet.microsoft.com/download/dotnet) development.
@ -36,13 +68,23 @@ dotnet tool install -g Volo.Abp.Cli
Then create an empty folder, open a command-line terminal and execute the following command in the terminal: Then create an empty folder, open a command-line terminal and execute the following command in the terminal:
````bash ````bash
abp new TodoApp -t app-nolayers{{if UI=="BlazorServer"}} -u blazor-server{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}} abp new TodoApp -t app-nolayers{{if UI=="BlazorServer"}} -u blazor-server{{else if UI=="Blazor"}} -u blazor{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}}
```` ````
{{if UI=="NG"}} {{if UI=="NG"}}
This will create a new solution, named *TodoApp*, with `angular` and `aspnet-core` folders. Once the solution is ready, open the solution (in the `aspnet-core` folder) with your favorite IDE. This will create a new solution, named *TodoApp*, with `angular` and `aspnet-core` folders. Once the solution is ready, open the solution (in the `aspnet-core` folder) with your favorite IDE.
{{else if UI=="Blazor"}}
This will create a new solution with three projects:
* A `blazor` application that contains the Blazor code, the client-side.
* A `host` application, hosts and serves the `blazor` application.
* A `contracts` project, shared library between these two projects.
Once the solution is ready, open it in your favorite IDE.
{{else}} {{else}}
This will create a new solution with a single project, named *TodoApp*. Once the solution is ready, open it in your favorite IDE. This will create a new solution with a single project, named *TodoApp*. Once the solution is ready, open it in your favorite IDE.
@ -51,7 +93,7 @@ This will create a new solution with a single project, named *TodoApp*. Once the
### Create the Database ### Create the Database
You can run the following command in the root directory of your project (in the same folder of the `.csproj` file) to create the database and seed the initial data: You can run the following command in the {{if UI=="Blazor"}} directory of your `TodoApp.Host` project {{else}}root directory of your project (in the same folder of the `.csproj` file){{end}} to create the database and seed the initial data:
```bash ```bash
dotnet run --migrate-database dotnet run --migrate-database
@ -65,6 +107,14 @@ This command will create the database and seed the initial data for you. Then yo
It is good to run the application before starting the development. Running the application is pretty straight-forward, you can run the application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project: It is good to run the application before starting the development. Running the application is pretty straight-forward, you can run the application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project:
{{else if UI=="Blazor"}}
It is good to run the application before starting the development. Running the application is pretty straight-forward, you just need to run the `TodoApp.Host` application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project.
> **Note:** The `host` application hosts and serves the `blazor` application. Therefore, you should run the `host` application only.
After the application runs, open the application in your default browser:
{{else if UI=="NG"}} {{else if UI=="NG"}}
It is good to run the application before starting the development. The solution has two main applications: It is good to run the application before starting the development. The solution has two main applications:
@ -96,12 +146,12 @@ All right. We can start coding!
## Defining the Entity ## Defining the Entity
This application will have a single [entity](../../../Entities.md) and we can start by creating it. So, create a new `TodoItem` class under the `Entities` folder of the project: This application will have a single [entity](../../../Entities.md) and we can start by creating it. So, create a new `TodoItem` class under the `Entities` folder of {{if UI=="Blazor"}}the `TodoApp.Host` project{{else}}the project{{end}}:
````csharp ````csharp
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
namespace TodoApp.Entities; namespace TodoApp{{if UI=="Blazor"}}.{{end}}Entities;
public class TodoItem : BasicAggregateRoot<Guid> public class TodoItem : BasicAggregateRoot<Guid>
{ {
@ -151,7 +201,7 @@ We've mapped the `TodoItem` entity to the `TodoItems` table in the database. The
The startup solution is configured to use Entity Framework Core [Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. The startup solution is configured to use Entity Framework Core [Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database.
Open a command-line terminal in the root directory of your project and type the following command: Open a command-line terminal in the {{if UI=="Blazor"}} directory of your `TodoApp.Host` project {{else}}root directory of your project (in the same folder of the `.csproj` file){{end}} and type the following command:
````bash ````bash
dotnet ef migrations add Added_TodoItem dotnet ef migrations add Added_TodoItem
@ -202,7 +252,7 @@ Before starting to implement these use cases, first we need to create a DTO clas
### Creating the Data Transfer Object (DTO) ### Creating the Data Transfer Object (DTO)
[Application services](../../../Application-Services.md) typically get and return DTOs ([Data Transfer Objects](../../../Data-Transfer-Objects.md)) instead of entities. So, create a new `TodoItemDto` class under the `Services/Dtos` folder: [Application services](../../../Application-Services.md) typically get and return DTOs ([Data Transfer Objects](../../../Data-Transfer-Objects.md)) instead of entities. So, create a new `TodoItemDto` class under the `Services/Dtos` folder{{if UI=="Blazor"}} of your `TodoApp.Contracts` project{{end}}:
```csharp ```csharp
namespace TodoApp.Services.Dtos; namespace TodoApp.Services.Dtos;
@ -216,18 +266,50 @@ public class TodoItemDto
This is a very simple DTO class that has the same properties as the `TodoItem` entity. Now, we are ready to implement our use-cases. This is a very simple DTO class that has the same properties as the `TodoItem` entity. Now, we are ready to implement our use-cases.
{{if UI=="Blazor"}}
### The Application Service Interface
Create a `ITodoAppService` interface under the `Services` folder of the `TodoApp.Contracts` project, as shown below:
```csharp
using TodoApp.Services.Dtos;
using Volo.Abp.Application.Services;
namespace TodoApp.Services;
public interface ITodoAppService : IApplicationService
{
Task<List<TodoItemDto>> GetListAsync();
Task<TodoItemDto> CreateAsync(string text);
Task DeleteAsync(Guid id);
}
```
{{end}}
### The Application Service Implementation ### The Application Service Implementation
Create a `TodoAppService` class under the `Services` folder of your project, as shown below: Create a `TodoAppService` class under the `Services` folder of {{if UI=="Blazor"}}your `TodoApp.Host` project{{else}}your project{{end}}, as shown below:
```csharp ```csharp
{{if UI=="Blazor"}}
using TodoApp.Services;
using TodoApp.Services.Dtos;
using TodoApp.Entities; using TodoApp.Entities;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
{{else}}
using TodoApp.Entities;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
{{end}}
namespace TodoApp.Services; namespace TodoApp.Services;
public class TodoAppService : ApplicationService public class TodoAppService : ApplicationService{{if UI=="Blazor"}}, ITodoAppService{{end}}
{ {
private readonly IRepository<TodoItem, Guid> _todoItemRepository; private readonly IRepository<TodoItem, Guid> _todoItemRepository;
@ -472,23 +554,29 @@ If you open [Swagger UI](https://swagger.io/tools/swagger-ui/) by entering the `
![todo-api](../todo-api.png) ![todo-api](../todo-api.png)
{{else if UI=="BlazorServer"}} {{else if UI=="Blazor" || UI=="BlazorServer"}}
### Index.razor.cs ### Index.razor.cs
Open the `Index.razor.cs` file in the `Pages` folder and replace the content with the following code block: Open the `Index.razor.cs` file in the `Pages` folder{{if UI=="Blazor"}} in your `Todo.Blazor` project{{end}} and replace the content with the following code block:
```csharp ```csharp
{{if UI=="Blazor"}}
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using TodoApp.Services; using TodoApp.Services;
using TodoApp.Services.Dtos; using TodoApp.Services.Dtos;
{{else}}
using Microsoft.AspNetCore.Components;
using TodoApp.Services;
using TodoApp.Services.Dtos;
{{end}}
namespace TodoApp.Pages; namespace TodoApp.Pages;
public partial class Index public partial class Index
{ {
[Inject] [Inject]
private TodoAppService TodoAppService { get; set; } private {{if UI=="Blazor"}}ITodoAppService{{else}}TodoAppService{{end}} TodoAppService { get; set; }
private List<TodoItemDto> TodoItems { get; set; } = new List<TodoItemDto>(); private List<TodoItemDto> TodoItems { get; set; } = new List<TodoItemDto>();
private string NewTodoText { get; set; } private string NewTodoText { get; set; }
@ -514,7 +602,7 @@ public partial class Index
} }
``` ```
This class uses the `TodoAppService` to get the list of todo items. It manipulates the `TodoItems` list after create and delete operations. This way, we don't need to refresh the whole todo list from the server. This class uses the {{if UI=="Blazor"}}`ITodoAppService`{{else}}`TodoAppService`{{end}} to get the list of todo items. It manipulates the `TodoItems` list after create and delete operations. This way, we don't need to refresh the whole todo list from the server.
### Index.razor ### Index.razor
@ -592,7 +680,7 @@ As the final touch, open the `Index.razor.css` file in the `Pages` folder and re
This is a simple styling for the todo page. We believe that you can do much better :) This is a simple styling for the todo page. We believe that you can do much better :)
Now, you can run the application again to see the result. Now, you can run the {{if UI=="Blazor"}}`TodoApp.Host` project{{else}}application{{end}} again to see the result.
{{else if UI=="NG"}} {{else if UI=="NG"}}

12
docs/en/UI/Angular/HTTP-Requests.md

@ -298,3 +298,15 @@ export function handleHttpErrors(injector: Injector, httpError: HttpErrorRespons
return throwError(httpError) return throwError(httpError)
} }
``` ```
### How to Skip HTTP interceptors and ABP headers
The ABP Framework adds several HTTP headers to the HttpClient, such as the "Auth token" or "tenant Id".
The ABP Server must possess the information but the ABP user may not want to send this informations to an external server.
ExternalHttpClient and IS EXTERNAL REQUEST HttpContext Token were added in V6.0.4.
The ABP Http interceptors check the value of the `IS_EXTERNAL_REQUEST` token. If the token is True then ABP-specific headers won't be added to Http Request.
The `ExternalHttpClient` extends from `HTTPClient` and sets the `IS_EXTERNAL_REQUEST` context token to true.
When you are using `ExternalHttpClient` as HttpClient in your components, it does not add ABP-specific headers.
Note: With `IS_EXTERNAL_REQUEST` or without it, ABP loading service works.

2
docs/en/UI/Angular/Modal.md

@ -57,7 +57,7 @@ You can add the `abp-modal` to your component very quickly. See an example:
@Component(/* component metadata */) @Component(/* component metadata */)
export class SampleComponent { export class SampleComponent {
isModelOpen = false isModalOpen = false
} }
``` ```

2
docs/en/UI/AspNetCore/Navigation-Menu.md

@ -100,7 +100,7 @@ There are more options of a menu item (the constructor of the `ApplicationMenuIt
* `url` (`string`): The URL of the menu item. * `url` (`string`): The URL of the menu item.
* `icon` (`string`): An icon name. Free [Font Awesome](https://fontawesome.com/) icon classes are supported out of the box. Example: `fa fa-book`. You can use any CSS font icon class as long as you include the necessary CSS files to your application. * `icon` (`string`): An icon name. Free [Font Awesome](https://fontawesome.com/) icon classes are supported out of the box. Example: `fa fa-book`. You can use any CSS font icon class as long as you include the necessary CSS files to your application.
* `order` (`int`): The order of the menu item. Default value is `1000`. Items are sorted by the adding order unless you specify an order value. * `order` (`int`): The order of the menu item. Default value is `1000`. Items are sorted by the adding order unless you specify an order value.
* `customData` (`object`): A custom object that you can associate to the menu item and use it while rendering the menu item. * `customData` (`Dictionary<string, object>`): A dictionary that allows storing custom objects that you can associate with the menu item and use it while rendering the menu item.
* `target` (`string`): Target of the menu item. Can be `null` (default), "\_*blank*", "\_*self*", "\_*parent*", "\_*top*" or a frame name for web applications. * `target` (`string`): Target of the menu item. Can be `null` (default), "\_*blank*", "\_*self*", "\_*parent*", "\_*top*" or a frame name for web applications.
* `elementId` (`string`): Can be used to render the element with a specific HTML `id` attribute. * `elementId` (`string`): Can be used to render the element with a specific HTML `id` attribute.
* `cssClass` (`string`): Additional string classes for the menu item. * `cssClass` (`string`): Additional string classes for the menu item.

21
docs/en/UI/Blazor/Basic-Theme.md

@ -85,6 +85,27 @@ You can simply override the styles in the Global Styles file of your application
See the [Customization / Overriding Components](Customization-Overriding-Components.md) to learn how you can replace components, customize and extend the user interface. See the [Customization / Overriding Components](Customization-Overriding-Components.md) to learn how you can replace components, customize and extend the user interface.
### Overriding the Menu Item
Basic theme supports overriding a single menu item with a custom component. You can create a custom component and call `UseComponent` extension method of Basic Theme in the **MenuContributor**.
```csharp
using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Navigation;
//...
context.Menu.Items.Add(
new ApplicationMenuItem("Custom.1", "My Custom Menu", "#")
.UseComponent(typeof(MyMenuItemComponent)));
```
```html
<li class="nav-item">
<a href="#" class="nav-link">
My Custom Menu
</a>
</li>
```
### Copy & Customize ### Copy & Customize
You can run the following [ABP CLI](../../CLI.md) command in **Blazor{{if UI == "Blazor"}}WebAssembly{{else}} Server{{end}}** project directory to copy the source code to your solution: You can run the following [ABP CLI](../../CLI.md) command in **Blazor{{if UI == "Blazor"}}WebAssembly{{else}} Server{{end}}** project directory to copy the source code to your solution:

2
docs/en/UI/Blazor/Overall.md

@ -88,7 +88,7 @@ There are a set of standard libraries that comes pre-installed and supported by
* [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. * [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework.
* [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree. * [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree.
* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. * [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library.
* [Flag Icon](https://github.com/lipis/flag-icon-css) as a library to show flags of countries. * [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries.
These libraries are selected as the base libraries and available to the applications and modules. These libraries are selected as the base libraries and available to the applications and modules.

2
docs/en/UI/Blazor/Theming.md

@ -48,7 +48,7 @@ All the themes must depend on the [Volo.Abp.AspNetCore.Components.Server.Theming
* [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. * [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework.
* [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree. * [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree.
* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. * [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library.
* [Flag Icon](https://github.com/lipis/flag-icon-css) as a library to show flags of countries. * [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries.
These libraries are selected as the base libraries and available to the applications and modules. These libraries are selected as the base libraries and available to the applications and modules.

9
docs/en/docs-nav.json

@ -1397,6 +1397,15 @@
{ {
"text": "Virtual File Explorer", "text": "Virtual File Explorer",
"path": "Modules/Virtual-File-Explorer.md" "path": "Modules/Virtual-File-Explorer.md"
},
{
"text": "Common",
"items":[
{
"text": "Database Tables",
"path": "Modules/Database-Tables.md"
}
]
} }
] ]
}, },

1
docs/zh-Hans/CLI.md

@ -129,6 +129,7 @@ abp new Acme.BookStore
* `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项: * `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项:
* `mvc`: ASP.NET Core MVC. * `mvc`: ASP.NET Core MVC.
* `angular`: Angular. * `angular`: Angular.
* `blazor`: Blazor UI.
* `blazor-server`: Blazor Server. * `blazor-server`: Blazor Server.
* `none`: 不包含UI. * `none`: 不包含UI.
* `--database-provider` 或 `-d`: 或者 `-d`: 指定数据库提供程序.默认是 `ef`.其他选项: * `--database-provider` 或 `-d`: 或者 `-d`: 指定数据库提供程序.默认是 `ef`.其他选项:

10
docs/zh-Hans/Dependency-Injection.md

@ -270,16 +270,16 @@ using (var scope = _serviceProvider.CreateScope())
## 高级特性 ## 高级特性
### IServiceCollection.OnRegistred 事件 ### IServiceCollection.OnRegistered 事件
你可能想在注册到依赖注入的每个服务上执行一个操作, 在你的模块的 `PreConfigureServices` 方法中, 使用 `OnRegistred` 方法注册一个回调(callback) , 如下所示: 你可能想在注册到依赖注入的每个服务上执行一个操作, 在你的模块的 `PreConfigureServices` 方法中, 使用 `OnRegistered` 方法注册一个回调(callback) , 如下所示:
````csharp ````csharp
public class AppModule : AbpModule public class AppModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.OnRegistred(ctx => context.Services.OnRegistered(ctx =>
{ {
var type = ctx.ImplementationType; var type = ctx.ImplementationType;
//... //...
@ -295,7 +295,7 @@ public class AppModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.OnRegistred(ctx => context.Services.OnRegistered(ctx =>
{ {
if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true)) if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true))
{ {
@ -308,7 +308,7 @@ public class AppModule : AbpModule
这个示例判断一个服务类是否具有 `MyLogAttribute` 特性, 如果有的话就添加一个 `MyLogInterceptor` 到拦截器集合中. 这个示例判断一个服务类是否具有 `MyLogAttribute` 特性, 如果有的话就添加一个 `MyLogInterceptor` 到拦截器集合中.
> 注意, 如果服务类公开了多于一个服务或接口, `OnRegistred` 回调(callback)可能被同一服务类多次调用. 因此, 较安全的方法是使用 `Interceptors.TryAdd` 方法而不是 `Interceptors.Add` 方法. 请参阅动态代理(dynamic proxying)/拦截器 [文档](Dynamic-Proxying-Interceptors.md). > 注意, 如果服务类公开了多于一个服务或接口, `OnRegistered` 回调(callback)可能被同一服务类多次调用. 因此, 较安全的方法是使用 `Interceptors.TryAdd` 方法而不是 `Interceptors.Add` 方法. 请参阅动态代理(dynamic proxying)/拦截器 [文档](Dynamic-Proxying-Interceptors.md).
## 第三方提供程序 ## 第三方提供程序

15
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/ApplicationConfigurationChangedService.cs

@ -0,0 +1,15 @@
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.Web.Security;
public delegate void ApplicationConfigurationChangedHandler();
public class ApplicationConfigurationChangedService : IScopedDependency
{
public event ApplicationConfigurationChangedHandler Changed;
public void NotifyChanged()
{
Changed?.Invoke();
}
}

20
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs

@ -1,4 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Volo.Abp.AspNetCore.Components.Web.Security;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies;
using Volo.Abp.AspNetCore.Mvc.Client; using Volo.Abp.AspNetCore.Mvc.Client;
@ -10,23 +12,31 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly;
public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency
{ {
protected AbpApplicationConfigurationClientProxy ApplicationConfigurationClientProxy { get; } protected AbpApplicationConfigurationClientProxy ApplicationConfigurationClientProxy { get; }
protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; } protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; }
protected ApplicationConfigurationCache Cache { get; } protected ApplicationConfigurationCache Cache { get; }
protected ICurrentTenantAccessor CurrentTenantAccessor { get; } protected ICurrentTenantAccessor CurrentTenantAccessor { get; }
protected ApplicationConfigurationChangedService ApplicationConfigurationChangedService { get; }
protected AuthenticationStateProvider AuthenticationStateProvider { get; }
public WebAssemblyCachedApplicationConfigurationClient( public WebAssemblyCachedApplicationConfigurationClient(
AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy, AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy,
ApplicationConfigurationCache cache, ApplicationConfigurationCache cache,
ICurrentTenantAccessor currentTenantAccessor, ICurrentTenantAccessor currentTenantAccessor,
AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy) AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy,
ApplicationConfigurationChangedService applicationConfigurationChangedService,
AuthenticationStateProvider authenticationStateProvider)
{ {
ApplicationConfigurationClientProxy = applicationConfigurationClientProxy; ApplicationConfigurationClientProxy = applicationConfigurationClientProxy;
Cache = cache; Cache = cache;
CurrentTenantAccessor = currentTenantAccessor; CurrentTenantAccessor = currentTenantAccessor;
ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; ApplicationLocalizationClientProxy = applicationLocalizationClientProxy;
ApplicationConfigurationChangedService = applicationConfigurationChangedService;
AuthenticationStateProvider = authenticationStateProvider;
} }
public virtual async Task InitializeAsync() public virtual async Task InitializeAsync()
@ -45,9 +55,11 @@ public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicatio
); );
configurationDto.Localization.Resources = localizationDto.Resources; configurationDto.Localization.Resources = localizationDto.Resources;
Cache.Set(configurationDto); Cache.Set(configurationDto);
ApplicationConfigurationChangedService.NotifyChanged();
CurrentTenantAccessor.Current = new BasicTenantInfo( CurrentTenantAccessor.Current = new BasicTenantInfo(
configurationDto.CurrentTenant.Id, configurationDto.CurrentTenant.Id,
configurationDto.CurrentTenant.Name configurationDto.CurrentTenant.Name

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelper.cs

@ -16,6 +16,8 @@ public class AbpColumnTagHelper : AbpTagHelper<AbpColumnTagHelper, AbpColumnTagH
public ColumnSize SizeXl { get; set; } public ColumnSize SizeXl { get; set; }
public ColumnSize SizeXxl { get; set;}
public ColumnSize Offset { get; set; } public ColumnSize Offset { get; set; }
public ColumnSize OffsetSm { get; set; } public ColumnSize OffsetSm { get; set; }
@ -26,6 +28,8 @@ public class AbpColumnTagHelper : AbpTagHelper<AbpColumnTagHelper, AbpColumnTagH
public ColumnSize OffsetXl { get; set; } public ColumnSize OffsetXl { get; set; }
public ColumnSize OffsetXxl { get; set; }
[HtmlAttributeName("order")] [HtmlAttributeName("order")]
public ColumnOrder ColumnOrder { get; set; } public ColumnOrder ColumnOrder { get; set; }

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelperService.cs

@ -24,6 +24,7 @@ public class AbpColumnTagHelperService : AbpTagHelperService<AbpColumnTagHelper>
ProcessSizeClass(context, output, TagHelper.SizeMd, "-md"); ProcessSizeClass(context, output, TagHelper.SizeMd, "-md");
ProcessSizeClass(context, output, TagHelper.SizeLg, "-lg"); ProcessSizeClass(context, output, TagHelper.SizeLg, "-lg");
ProcessSizeClass(context, output, TagHelper.SizeXl, "-xl"); ProcessSizeClass(context, output, TagHelper.SizeXl, "-xl");
ProcessSizeClass(context, output, TagHelper.SizeXxl, "-xxl");
} }
protected virtual void ProcessOffsetClasses(TagHelperContext context, TagHelperOutput output) protected virtual void ProcessOffsetClasses(TagHelperContext context, TagHelperOutput output)
@ -33,6 +34,7 @@ public class AbpColumnTagHelperService : AbpTagHelperService<AbpColumnTagHelper>
ProcessOffsetClass(context, output, TagHelper.OffsetMd, "-md"); ProcessOffsetClass(context, output, TagHelper.OffsetMd, "-md");
ProcessOffsetClass(context, output, TagHelper.OffsetLg, "-lg"); ProcessOffsetClass(context, output, TagHelper.OffsetLg, "-lg");
ProcessOffsetClass(context, output, TagHelper.OffsetXl, "-xl"); ProcessOffsetClass(context, output, TagHelper.OffsetXl, "-xl");
ProcessOffsetClass(context, output, TagHelper.OffsetXxl, "-xxl");
} }
protected virtual void ProcessSizeClass(TagHelperContext context, TagHelperOutput output, ColumnSize size, string breakpoint) protected virtual void ProcessSizeClass(TagHelperContext context, TagHelperOutput output, ColumnSize size, string breakpoint)

9
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs

@ -7,6 +7,13 @@ public class FlagIconCssStyleContributor : BundleContributor
{ {
public override void ConfigureBundle(BundleConfigurationContext context) public override void ConfigureBundle(BundleConfigurationContext context)
{ {
context.Files.AddIfNotContains("/libs/flag-icon-css/css/flag-icons.min.css"); if (context.FileProvider.GetFileInfo("/libs/flag-icons/css/flag-icons.min.css").Exists)
{
context.Files.AddIfNotContains("/libs/flag-icons/css/flag-icons.min.css");
}
else if (context.FileProvider.GetFileInfo("/libs/flag-icon-css/css/flag-icons.min.css").Exists)
{
context.Files.AddIfNotContains("/libs/flag-icon-css/css/flag-icons.min.css");
}
} }
} }

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs

@ -39,7 +39,7 @@ public class AbpAspNetCoreMvcUiWidgetsModule : AbpModule
{ {
var widgetTypes = new List<Type>(); var widgetTypes = new List<Type>();
services.OnRegistred(context => services.OnRegistered(context =>
{ {
if (WidgetAttribute.IsWidget(context.ImplementationType)) if (WidgetAttribute.IsWidget(context.ImplementationType))
{ {

28
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs

@ -1,7 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Volo.Abp.AspNetCore.Mvc.Authentication; namespace Volo.Abp.AspNetCore.Mvc.Authentication;
@ -9,15 +12,17 @@ public abstract class ChallengeAccountController : AbpController
{ {
protected string[] ChallengeAuthenticationSchemas { get; } protected string[] ChallengeAuthenticationSchemas { get; }
protected string AuthenticationType { get; } protected string AuthenticationType { get; }
protected string[] ForbidSchemes { get; }
protected ChallengeAccountController(string[] challengeAuthenticationSchemas = null) protected ChallengeAccountController(string[] challengeAuthenticationSchemas = null)
{ {
ChallengeAuthenticationSchemas = challengeAuthenticationSchemas ?? new[] { "oidc" }; ChallengeAuthenticationSchemas = challengeAuthenticationSchemas ?? new[] { "oidc" };
AuthenticationType = "Identity.Application"; AuthenticationType = "Identity.Application";
ForbidSchemes = Array.Empty<string>();
} }
[HttpGet] [HttpGet]
public ActionResult Login(string returnUrl = "", string returnUrlHash = "") public virtual ActionResult Login(string returnUrl = "", string returnUrlHash = "")
{ {
if (CurrentUser.IsAuthenticated) if (CurrentUser.IsAuthenticated)
{ {
@ -28,7 +33,7 @@ public abstract class ChallengeAccountController : AbpController
} }
[HttpGet] [HttpGet]
public async Task<ActionResult> Logout(string returnUrl = "", string returnUrlHash = "") public virtual async Task<ActionResult> Logout(string returnUrl = "", string returnUrlHash = "")
{ {
await HttpContext.SignOutAsync(); await HttpContext.SignOutAsync();
@ -41,7 +46,7 @@ public abstract class ChallengeAccountController : AbpController
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> FrontChannelLogout(string sid) public virtual async Task<IActionResult> FrontChannelLogout(string sid)
{ {
if (User.Identity != null && User.Identity.IsAuthenticated) if (User.Identity != null && User.Identity.IsAuthenticated)
{ {
@ -54,4 +59,21 @@ public abstract class ChallengeAccountController : AbpController
return NoContent(); return NoContent();
} }
[HttpGet]
public virtual Task<IActionResult> AccessDenied(string returnUrl = "", string returnUrlHash = "")
{
return Task.FromResult<IActionResult>(Challenge(
new AuthenticationProperties
{
RedirectUri = GetRedirectUrl(returnUrl, returnUrlHash)
},
ForbidSchemes.IsNullOrEmpty()
? new[]
{
HttpContext.RequestServices.GetRequiredService<IOptions<AuthenticationOptions>>().Value.DefaultForbidScheme
}
: ForbidSchemes
));
}
} }

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs

@ -20,6 +20,8 @@ public static class MvcCoreBuilderExtensions
options.JsonSerializerOptions.Converters.Add(new AbpStringToEnumFactory()); options.JsonSerializerOptions.Converters.Add(new AbpStringToEnumFactory());
options.JsonSerializerOptions.Converters.Add(new AbpStringToBooleanConverter()); options.JsonSerializerOptions.Converters.Add(new AbpStringToBooleanConverter());
options.JsonSerializerOptions.Converters.Add(new AbpStringToGuidConverter());
options.JsonSerializerOptions.Converters.Add(new AbpNullableStringToGuidConverter());
options.JsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter()); options.JsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter());
options.JsonSerializerOptions.TypeInfoResolver = new AbpDefaultJsonTypeInfoResolver(serviceProvider options.JsonSerializerOptions.TypeInfoResolver = new AbpDefaultJsonTypeInfoResolver(serviceProvider

2
framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs

@ -91,7 +91,7 @@ public class AbpAspNetCoreSignalRModule : AbpModule
{ {
var hubTypes = new List<Type>(); var hubTypes = new List<Type>();
services.OnRegistred(context => services.OnRegistered(context =>
{ {
if (IsHubClass(context) && !IsDisabledForAutoMap(context)) if (IsHubClass(context) && !IsDisabledForAutoMap(context))
{ {

3
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs

@ -28,9 +28,6 @@ public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency
/*The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a <frame>, <iframe> or <object>. SAMEORIGIN makes it being displayed in a frame on the same origin as the page itself. The spec leaves it up to browser vendors to decide whether this option applies to the top level, the parent, or the whole chain*/ /*The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a <frame>, <iframe> or <object>. SAMEORIGIN makes it being displayed in a frame on the same origin as the page itself. The spec leaves it up to browser vendors to decide whether this option applies to the top level, the parent, or the whole chain*/
AddHeaderIfNotExists(context, "X-Frame-Options", "SAMEORIGIN"); AddHeaderIfNotExists(context, "X-Frame-Options", "SAMEORIGIN");
/*The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types advertised in the Content-Type headers should be followed and not be changed. The header allows you to avoid MIME type sniffing by saying that the MIME types are deliberately configured.*/
AddHeaderIfNotExists(context, "X-Content-Type-Options", "nosniff");
if (Options.Value.UseContentSecurityPolicyHeader) if (Options.Value.UseContentSecurityPolicyHeader)
{ {
AddHeaderIfNotExists(context, "Content-Security-Policy", AddHeaderIfNotExists(context, "Content-Security-Policy",

2
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs

@ -24,7 +24,7 @@ public class AbpAuditingModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.OnRegistred(AuditingInterceptorRegistrar.RegisterIfNeeded); context.Services.OnRegistered(AuditingInterceptorRegistrar.RegisterIfNeeded);
} }
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)

4
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs

@ -22,7 +22,7 @@ public class AbpAuthorizationModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.OnRegistred(AuthorizationInterceptorRegistrar.RegisterIfNeeded); context.Services.OnRegistered(AuthorizationInterceptorRegistrar.RegisterIfNeeded);
AutoAddDefinitionProviders(context.Services); AutoAddDefinitionProviders(context.Services);
} }
@ -64,7 +64,7 @@ public class AbpAuthorizationModule : AbpModule
{ {
var definitionProviders = new List<Type>(); var definitionProviders = new List<Type>();
services.OnRegistred(context => services.OnRegistered(context =>
{ {
if (typeof(IPermissionDefinitionProvider).IsAssignableFrom(context.ImplementationType)) if (typeof(IPermissionDefinitionProvider).IsAssignableFrom(context.ImplementationType))
{ {

2
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs

@ -21,7 +21,7 @@ public class AbpBackgroundJobsAbstractionsModule : AbpModule
{ {
var jobTypes = new List<Type>(); var jobTypes = new List<Type>();
services.OnRegistred(context => services.OnRegistered(context =>
{ {
if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IBackgroundJob<>)) || if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IBackgroundJob<>)) ||
ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IAsyncBackgroundJob<>))) ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IAsyncBackgroundJob<>)))

8
framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj

@ -14,10 +14,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Blazorise" Version="1.1.4.1" /> <PackageReference Include="Blazorise" Version="1.2.0" />
<PackageReference Include="Blazorise.DataGrid" Version="1.1.4.1" /> <PackageReference Include="Blazorise.DataGrid" Version="1.2.0" />
<PackageReference Include="Blazorise.Snackbar" Version="1.1.4.1" /> <PackageReference Include="Blazorise.Snackbar" Version="1.2.0" />
<PackageReference Include="Blazorise.Components" Version="1.1.4.1" /> <PackageReference Include="Blazorise.Components" Version="1.2.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

20
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs

@ -127,6 +127,26 @@ public class AuthService : IAuthService, ITransientDependency
} }
} }
public async Task<bool> CheckMultipleOrganizationsAsync(string username)
{
var url = $"{CliUrls.WwwAbpIo}api/license/check-multiple-organizations?username={username}";
var client = CliHttpClientFactory.CreateClient();
using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, Logger))
{
if (!response.IsSuccessStatusCode)
{
throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'");
}
await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response);
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<bool>(responseContent);
}
}
private async Task LogoutAsync(string accessToken) private async Task LogoutAsync(string accessToken)
{ {
try try

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/IAuthService.cs

@ -9,4 +9,6 @@ public interface IAuthService
Task LoginAsync(string userName, string password, string organizationName = null); Task LoginAsync(string userName, string password, string organizationName = null);
Task LogoutAsync(); Task LogoutAsync();
Task<bool> CheckMultipleOrganizationsAsync(string username);
} }

30
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs

@ -2,13 +2,11 @@
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using System; using System;
using System.Text; using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.Auth; using Volo.Abp.Cli.Auth;
using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.ProjectBuilding; using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.Utils; using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -26,17 +24,13 @@ public class LoginCommand : IConsoleCommand, ITransientDependency
public ICancellationTokenProvider CancellationTokenProvider { get; } public ICancellationTokenProvider CancellationTokenProvider { get; }
public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; }
private readonly CliHttpClientFactory _cliHttpClientFactory;
public LoginCommand(AuthService authService, public LoginCommand(AuthService authService,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
IRemoteServiceExceptionHandler remoteServiceExceptionHandler, IRemoteServiceExceptionHandler remoteServiceExceptionHandler)
CliHttpClientFactory cliHttpClientFactory)
{ {
AuthService = authService; AuthService = authService;
CancellationTokenProvider = cancellationTokenProvider; CancellationTokenProvider = cancellationTokenProvider;
RemoteServiceExceptionHandler = remoteServiceExceptionHandler; RemoteServiceExceptionHandler = remoteServiceExceptionHandler;
_cliHttpClientFactory = cliHttpClientFactory;
Logger = NullLogger<LoginCommand>.Instance; Logger = NullLogger<LoginCommand>.Instance;
} }
@ -111,7 +105,7 @@ public class LoginCommand : IConsoleCommand, ITransientDependency
private async Task<bool> HasMultipleOrganizationAndThisNotSpecified(CommandLineArgs commandLineArgs, string organization) private async Task<bool> HasMultipleOrganizationAndThisNotSpecified(CommandLineArgs commandLineArgs, string organization)
{ {
if (string.IsNullOrWhiteSpace(organization) && if (string.IsNullOrWhiteSpace(organization) &&
await CheckMultipleOrganizationsAsync(commandLineArgs.Target)) await AuthService.CheckMultipleOrganizationsAsync(commandLineArgs.Target))
{ {
Logger.LogError($"You have multiple organizations, please specify your organization with `--organization` parameter."); Logger.LogError($"You have multiple organizations, please specify your organization with `--organization` parameter.");
return true; return true;
@ -168,26 +162,6 @@ public class LoginCommand : IConsoleCommand, ITransientDependency
return false; return false;
} }
private async Task<bool> CheckMultipleOrganizationsAsync(string username)
{
var url = $"{CliUrls.WwwAbpIo}api/license/check-multiple-organizations?username={username}";
var client = _cliHttpClientFactory.CreateClient();
using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, Logger))
{
if (!response.IsSuccessStatusCode)
{
throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'");
}
await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response);
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<bool>(responseContent);
}
}
public string GetUsageInfo() public string GetUsageInfo()
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();

6
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs

@ -25,6 +25,12 @@ public class LoginInfoCommand : IConsoleCommand, ITransientDependency
public async Task ExecuteAsync(CommandLineArgs commandLineArgs) public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
{ {
if (!AuthService.IsLoggedIn())
{
Logger.LogError("You are not logged in.");
return;
}
var loginInfo = await AuthService.GetLoginInfoAsync(); var loginInfo = await AuthService.GetLoginInfoAsync();
if (loginInfo == null) if (loginInfo == null)

6
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs

@ -417,7 +417,8 @@ public abstract class ProjectCreationCommandBase
protected async Task RunBundleForBlazorWasmOrMauiBlazorTemplateAsync(ProjectBuildArgs projectArgs) protected async Task RunBundleForBlazorWasmOrMauiBlazorTemplateAsync(ProjectBuildArgs projectArgs)
{ {
if (AppTemplateBase.IsAppTemplate(projectArgs.TemplateName) && projectArgs.UiFramework is UiFramework.Blazor or UiFramework.MauiBlazor) if ((AppTemplateBase.IsAppTemplate(projectArgs.TemplateName) || AppNoLayersTemplateBase.IsAppNoLayersTemplate(projectArgs.TemplateName))
&& projectArgs.UiFramework is UiFramework.Blazor or UiFramework.MauiBlazor)
{ {
var isWebassembly = projectArgs.UiFramework == UiFramework.Blazor; var isWebassembly = projectArgs.UiFramework == UiFramework.Blazor;
var message = isWebassembly ? "Generating bundles for Blazor Wasm" : "Generating bundles for MAUI Blazor"; var message = isWebassembly ? "Generating bundles for Blazor Wasm" : "Generating bundles for MAUI Blazor";
@ -455,7 +456,8 @@ public abstract class ProjectCreationCommandBase
break; break;
case AppNoLayersTemplate.TemplateName: case AppNoLayersTemplate.TemplateName:
case AppNoLayersProTemplate.TemplateName: case AppNoLayersProTemplate.TemplateName:
efCoreProjectPath = Directory.GetFiles(projectArgs.OutputFolder, "*.csproj", SearchOption.AllDirectories).FirstOrDefault(); efCoreProjectPath = Directory.GetFiles(projectArgs.OutputFolder, "*.Host.csproj", SearchOption.AllDirectories).FirstOrDefault()
?? Directory.GetFiles(projectArgs.OutputFolder, "*.csproj", SearchOption.AllDirectories).FirstOrDefault();
isLayeredTemplate = false; isLayeredTemplate = false;
break; break;
default: default:

94
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SuiteAppSettingsService.cs

@ -0,0 +1,94 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands.Services;
public class SuiteAppSettingsService : ITransientDependency
{
private const int DefaultPort = 3000;
public CmdHelper CmdHelper { get; }
public SuiteAppSettingsService(CmdHelper cmdHelper)
{
CmdHelper = cmdHelper;
}
public async Task<int> GetSuitePortAsync()
{
return await GetSuitePortAsync(GetCurrentSuiteVersion());
}
public async Task<int> GetSuitePortAsync(string version)
{
var filePath = GetFilePathOrNull(version);
if (filePath == null)
{
return DefaultPort;
}
var content = File.ReadAllText(filePath);
var contentAsJson = JObject.Parse(content);
var url = contentAsJson["AbpSuite"]?["ApplicationUrl"]?.ToString();
if (url == null)
{
return DefaultPort;
}
return Convert.ToInt32(url.Split(":").Last());
}
private string GetFilePathOrNull(string version)
{
if (version == null)
{
return null;
}
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
".dotnet",
"tools",
".store",
"volo.abp.suite",
version,
"volo.abp.suite",
version,
"tools",
"net7.0",
"any",
"appsettings.json"
);
if (!File.Exists(path))
{
return null;
}
return path;
}
private string GetCurrentSuiteVersion()
{
var dotnetToolList = CmdHelper.RunCmdAndGetOutput("dotnet tool list -g", out int exitCode);
var suiteLine = dotnetToolList.Split(Environment.NewLine)
.FirstOrDefault(l => l.ToLower().StartsWith("volo.abp.suite "));
if (string.IsNullOrEmpty(suiteLine))
{
return null;
}
return suiteLine.Split(" ", StringSplitOptions.RemoveEmptyEntries)[1];
}
}

65
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs

@ -35,23 +35,26 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
private readonly NuGetService _nuGetService; private readonly NuGetService _nuGetService;
private readonly AuthService _authService; private readonly AuthService _authService;
private readonly CliHttpClientFactory _cliHttpClientFactory; private readonly CliHttpClientFactory _cliHttpClientFactory;
private readonly SuiteAppSettingsService _suiteAppSettingsService;
private const string SuitePackageName = "Volo.Abp.Suite"; private const string SuitePackageName = "Volo.Abp.Suite";
public ILogger<SuiteCommand> Logger { get; set; } public ILogger<SuiteCommand> Logger { get; set; }
private const string AbpSuiteHost = "http://localhost:3000"; private int _abpSuitePort = 3000;
public SuiteCommand( public SuiteCommand(
AbpNuGetIndexUrlService nuGetIndexUrlService, AbpNuGetIndexUrlService nuGetIndexUrlService,
NuGetService nuGetService, NuGetService nuGetService,
ICmdHelper cmdHelper, ICmdHelper cmdHelper,
AuthService authService, AuthService authService,
CliHttpClientFactory cliHttpClientFactory) CliHttpClientFactory cliHttpClientFactory,
SuiteAppSettingsService suiteAppSettingsService)
{ {
CmdHelper = cmdHelper; CmdHelper = cmdHelper;
_nuGetIndexUrlService = nuGetIndexUrlService; _nuGetIndexUrlService = nuGetIndexUrlService;
_nuGetService = nuGetService; _nuGetService = nuGetService;
_authService = authService; _authService = authService;
_cliHttpClientFactory = cliHttpClientFactory; _cliHttpClientFactory = cliHttpClientFactory;
_suiteAppSettingsService = suiteAppSettingsService;
Logger = NullLogger<SuiteCommand>.Instance; Logger = NullLogger<SuiteCommand>.Instance;
} }
@ -72,17 +75,20 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
commandLineArgs.Options.ContainsKey(Options.Preview.Long); commandLineArgs.Options.ContainsKey(Options.Preview.Long);
var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long);
var currentSuiteVersionAsString = GetCurrentSuiteVersion();
switch (operationType) switch (operationType)
{ {
case "": case "":
case null: case null:
await InstallSuiteIfNotInstalledAsync(); await InstallSuiteIfNotInstalledAsync(currentSuiteVersionAsString);
_abpSuitePort = await _suiteAppSettingsService.GetSuitePortAsync(currentSuiteVersionAsString);
RunSuite(); RunSuite();
break; break;
case "generate": case "generate":
await InstallSuiteIfNotInstalledAsync(); await InstallSuiteIfNotInstalledAsync(currentSuiteVersionAsString);
_abpSuitePort = await _suiteAppSettingsService.GetSuitePortAsync(currentSuiteVersionAsString);
var suiteProcess = StartSuite(); var suiteProcess = StartSuite();
System.Threading.Thread.Sleep(500); //wait for initialization of the app System.Threading.Thread.Sleep(500); //wait for initialization of the app
await GenerateCrudPageAsync(commandLineArgs); await GenerateCrudPageAsync(commandLineArgs);
@ -130,7 +136,7 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
} }
var IsSolutionBuiltResponse = await client.GetAsync( var IsSolutionBuiltResponse = await client.GetAsync(
$"{AbpSuiteHost}/api/abpSuite/solutions/{solutionId.ToString()}/is-built" $"http://localhost:{_abpSuitePort}/api/abpSuite/solutions/{solutionId.ToString()}/is-built"
); );
var IsSolutionBuilt = Convert.ToBoolean(await IsSolutionBuiltResponse.Content.ReadAsStringAsync()); var IsSolutionBuilt = Convert.ToBoolean(await IsSolutionBuiltResponse.Content.ReadAsStringAsync());
@ -148,7 +154,7 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
); );
var responseMessage = await client.PostAsync( var responseMessage = await client.PostAsync(
$"{AbpSuiteHost}/api/abpSuite/crudPageGenerator/{solutionId.ToString()}/save-and-generate-entity", $"http://localhost:{_abpSuitePort}/api/abpSuite/crudPageGenerator/{solutionId.ToString()}/save-and-generate-entity",
entityContent entityContent
); );
@ -178,7 +184,7 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
} }
var responseMessage = await client.GetHttpResponseMessageWithRetryAsync( var responseMessage = await client.GetHttpResponseMessageWithRetryAsync(
"http://localhost:3000/api/abpSuite/solutions", $"http://localhost:{_abpSuitePort}/api/abpSuite/solutions",
_cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)), _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)),
Logger, Logger,
timeIntervals.ToArray()); timeIntervals.ToArray());
@ -216,7 +222,7 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
); );
var responseMessage = await client.PostAsync( var responseMessage = await client.PostAsync(
"http://localhost:3000/api/abpSuite/addSolution", $"http://localhost:{_abpSuitePort}/api/abpSuite/addSolution",
entityContent, entityContent,
_cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)) _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10))
); );
@ -234,11 +240,9 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
} }
} }
private async Task InstallSuiteIfNotInstalledAsync() private async Task InstallSuiteIfNotInstalledAsync(string currentSuiteVersion)
{ {
var currentSuiteVersionAsString = GetCurrentSuiteVersion(); if (string.IsNullOrEmpty(currentSuiteVersion))
if (string.IsNullOrEmpty(currentSuiteVersionAsString))
{ {
await InstallSuiteAsync(); await InstallSuiteAsync();
} }
@ -430,6 +434,19 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
Logger.LogWarning("Couldn't check ABP Suite installed status: " + ex.Message); Logger.LogWarning("Couldn't check ABP Suite installed status: " + ex.Message);
} }
if (IsSuiteAlreadyRunning())
{
Logger.LogInformation("Opening suite...");
CmdHelper.Open($"http://localhost:{_abpSuitePort}");
return;
}
if (IsPortAlreadyInUse())
{
Logger.LogError($"Port \"{_abpSuitePort}\" is already in use.");
return;
}
CmdHelper.RunCmd("abp-suite"); CmdHelper.RunCmd("abp-suite");
} }
@ -453,23 +470,32 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
return null; return null;
} }
if (IsPortAlreadyInUse())
{
Logger.LogError($"Port \"{_abpSuitePort}\" is already in use.");
return null;
}
return CmdHelper.RunCmdAndGetProcess("abp-suite --no-browser"); return CmdHelper.RunCmdAndGetProcess("abp-suite --no-browser");
} }
private bool IsSuiteAlreadyRunning() private bool IsSuiteAlreadyRunning()
{
return GetProcessesRelatedWithSuite().Any();
}
private bool IsPortAlreadyInUse()
{ {
var ipGP = IPGlobalProperties.GetIPGlobalProperties(); var ipGP = IPGlobalProperties.GetIPGlobalProperties();
var endpoints = ipGP.GetActiveTcpListeners(); var endpoints = ipGP.GetActiveTcpListeners();
return endpoints.Any(e => e.Port == 3000); return endpoints.Any(e => e.Port == _abpSuitePort);
} }
private void KillSuite() private void KillSuite()
{ {
try try
{ {
var suiteProcesses = (from p in Process.GetProcesses() var suiteProcesses = GetProcessesRelatedWithSuite();
where p.ProcessName.ToLower().Contains("abp-suite")
select p);
foreach (var suiteProcess in suiteProcesses) foreach (var suiteProcess in suiteProcesses)
{ {
@ -483,6 +509,13 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
} }
} }
private IEnumerable<Process> GetProcessesRelatedWithSuite()
{
return (from p in Process.GetProcesses()
where p.ProcessName.ToLower().Contains("abp-suite")
select p);
}
public string GetUsageInfo() public string GetUsageInfo()
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();

13
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/AppNoLayersMigrateDatabaseChangeStep.cs

@ -0,0 +1,13 @@
using System;
using System.Linq;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
public class AppNoLayersMigrateDatabaseChangeStep : ProjectBuildPipelineStep
{
public override void Execute(ProjectBuildContext context)
{
var file = context.Files.FirstOrDefault(file => file.Name.EndsWith("/migrate-database.ps1"));
file?.SetContent(file.Content.Replace("MyCompanyName.MyProjectName", "MyCompanyName.MyProjectName.Host"));
}
}

57
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/AppNoLayersMoveProjectsStep.cs

@ -0,0 +1,57 @@
using System;
using System.Linq;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
public class AppNoLayersMoveProjectsStep : ProjectBuildPipelineStep
{
public override void Execute(ProjectBuildContext context)
{
MoveFiles(context, "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client",
"/aspnet-core/MyCompanyName.MyProjectName.Blazor");
MoveFiles(context, "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server",
"/aspnet-core/MyCompanyName.MyProjectName.Host");
MoveFiles(context, "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Shared",
"/aspnet-core/MyCompanyName.MyProjectName.Contracts");
ModifySolutionFile(context, "MyCompanyName.MyProjectName.Blazor.WebAssembly\\Client\\MyCompanyName.MyProjectName.Blazor.csproj",
"MyCompanyName.MyProjectName.Blazor\\MyCompanyName.MyProjectName.Blazor.csproj");
ModifySolutionFile(context, "MyCompanyName.MyProjectName.Blazor.WebAssembly\\Server\\MyCompanyName.MyProjectName.Host.csproj",
"MyCompanyName.MyProjectName.Host\\MyCompanyName.MyProjectName.Host.csproj");
ModifySolutionFile(context, "MyCompanyName.MyProjectName.Blazor.WebAssembly\\Server.Mongo\\MyCompanyName.MyProjectName.Host.csproj",
"MyCompanyName.MyProjectName.Host\\MyCompanyName.MyProjectName.Host.csproj");
ModifySolutionFile(context, "MyCompanyName.MyProjectName.Blazor.WebAssembly\\Shared\\MyCompanyName.MyProjectName.Contracts.csproj",
"MyCompanyName.MyProjectName.Contracts\\MyCompanyName.MyProjectName.Contracts.csproj");
ModifyProjectFiles(context, "..\\Shared\\MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared.csproj",
"..\\MyCompanyName.MyProjectName.Contracts\\MyCompanyName.MyProjectName.Contracts.csproj");
ModifyProjectFiles(context, "..\\Client\\MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj",
"..\\MyCompanyName.MyProjectName.Blazor\\MyCompanyName.MyProjectName.Blazor.csproj");
}
public void MoveFiles(ProjectBuildContext context, string projectFolder, string newProjectFolder)
{
var fileEntries = context.Files.Where(file => file.Name.StartsWith(projectFolder));
foreach (var fileEntry in fileEntries)
{
fileEntry.SetName(fileEntry.Name.ReplaceFirst(projectFolder, newProjectFolder));
}
}
public void ModifySolutionFile(ProjectBuildContext context, string pathInSlnFile, string newPathInSlnFile)
{
var slnFile = context.Files.First(file => file.Name.EndsWith(".sln"));
slnFile.SetContent(slnFile.Content.Replace($"\"{pathInSlnFile}\"", $"\"{newPathInSlnFile}\""));
}
public void ModifyProjectFiles(ProjectBuildContext context, string oldContent, string newContent)
{
var projectFiles = context.Files.Where(file => file.Name.EndsWith(".csproj"));
foreach (var projectFile in projectFiles)
{
projectFile.SetContent(projectFile.Content.Replace(oldContent, newContent));
}
}
}

50
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs

@ -73,6 +73,31 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
$"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Components", $"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Components",
"Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic" "Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic"
); );
ReplacePackageReferenceWithProjectReference(
context,
"/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}",
@"..\..\..\..\..\modules\basic-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj"
);
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling",
$"{defaultThemeName}ThemeBundles.Styles.Global",
"BasicThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic",
$"AbpAspNetCoreMvcUi{defaultThemeName}ThemeModule",
"AbpAspNetCoreMvcUiBasicThemeModule"
);
#endregion #endregion
@ -174,6 +199,31 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
"Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme", "Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme",
"Volo.Abp.AspNetCore.Components.WebAssembly.LeptonTheme" "Volo.Abp.AspNetCore.Components.WebAssembly.LeptonTheme"
); );
ReplacePackageReferenceWithProjectReference(
context,
"/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX",
@"..\..\..\..\..\lepton-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton\Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.csproj"
);
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.Bundling",
"LeptonXThemeBundles.Styles.Global",
"LeptonThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton",
"AbpAspNetCoreMvcUiLeptonXThemeModule",
"AbpAspNetCoreMvcUiLeptonThemeModule"
);
#endregion #endregion

45
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppNoLayersTemplateBase.cs

@ -29,15 +29,19 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc.Mongo")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc.Mongo"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host.Mongo")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host.Mongo"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server.Mongo")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server.Mongo"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo", projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo"));
break; break;
case DatabaseProvider.MongoDb: case DatabaseProvider.MongoDb:
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Server", projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server"));
steps.Add(new MoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo", "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Mvc.Mongo", "MyCompanyName.MyProjectName.Mvc")); steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Mvc.Mongo", "MyCompanyName.MyProjectName.Mvc"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Host.Mongo", "MyCompanyName.MyProjectName.Host")); steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Host.Mongo", "MyCompanyName.MyProjectName.Host"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.Server.Mongo", "MyCompanyName.MyProjectName.Blazor.Server")); steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.Server.Mongo", "MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo", "MyCompanyName.MyProjectName.Blazor.WebAssembly.Server"));
break; break;
} }
@ -49,6 +53,7 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Host", "MyCompanyName.MyProjectName")); steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Host", "MyCompanyName.MyProjectName"));
RemoveBlazorWasmProjects(steps);
break; break;
case UiFramework.None: case UiFramework.None:
@ -56,13 +61,33 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Host", "MyCompanyName.MyProjectName")); steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Host", "MyCompanyName.MyProjectName"));
RemoveBlazorWasmProjects(steps);
break; break;
case UiFramework.Blazor:
steps.Add(new RemoveFolderStep("/angular"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Server",
"MyCompanyName.MyProjectName.Host"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Client",
"MyCompanyName.MyProjectName.Blazor"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared",
"MyCompanyName.MyProjectName.Contracts"));
steps.Add(new AppNoLayersMoveProjectsStep());
steps.Add(new AppNoLayersMigrateDatabaseChangeStep());
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly"));
break;
case UiFramework.BlazorServer: case UiFramework.BlazorServer:
steps.Add(new RemoveFolderStep("/angular")); steps.Add(new RemoveFolderStep("/angular"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.Server", "MyCompanyName.MyProjectName")); steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.Server", "MyCompanyName.MyProjectName"));
RemoveBlazorWasmProjects(steps);
break; break;
case UiFramework.NotSpecified: case UiFramework.NotSpecified:
@ -71,14 +96,15 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Mvc", "MyCompanyName.MyProjectName")); steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Mvc", "MyCompanyName.MyProjectName"));
RemoveBlazorWasmProjects(steps);
break; break;
case UiFramework.Blazor: default:
throw new AbpException("app-nolayers doesn't support blazor wasm."); throw new AbpException("Unkown UI framework: " + context.BuildArgs.UiFramework);
break;
} }
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName/Migrations")); steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName/Migrations"));
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations"));
RandomizeSslPorts(context, steps); RandomizeSslPorts(context, steps);
RandomizeStringEncryption(context, steps); RandomizeStringEncryption(context, steps);
UpdateNuGetConfig(context, steps); UpdateNuGetConfig(context, steps);
@ -94,6 +120,17 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
return steps; return steps;
} }
private static void RemoveBlazorWasmProjects(List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Server",
projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Client",
projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared",
projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Shared"));
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly"));
}
protected void ConfigureDockerFiles(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps) protected void ConfigureDockerFiles(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{ {
switch (context.BuildArgs.UiFramework) switch (context.BuildArgs.UiFramework)

17
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs

@ -216,7 +216,7 @@ public abstract class AppTemplateBase : TemplateInfo
RemoveThemeLogoFolders(context, steps); RemoveThemeLogoFolders(context, steps);
if (IsDefaultThemeForTemplate(context.BuildArgs.Theme.Value)) if (IsDefaultThemeForTemplate(context.BuildArgs))
{ {
return; return;
} }
@ -238,15 +238,17 @@ public abstract class AppTemplateBase : TemplateInfo
} }
} }
private static bool IsDefaultThemeForTemplate(Theme theme) private static bool IsDefaultThemeForTemplate(ProjectBuildArgs args)
{ {
var defaultThemesForTemplates = new[] var templateThemes = new Dictionary<string, Theme>
{ {
AppTemplate.DefaultTheme, AppProTemplate.DefaultTheme, { AppTemplate.TemplateName, AppTemplate.DefaultTheme },
AppNoLayersTemplate.DefaultTheme, AppNoLayersProTemplate.DefaultTheme { AppProTemplate.TemplateName, AppProTemplate.DefaultTheme },
{ AppNoLayersTemplate.TemplateName, AppNoLayersTemplate.DefaultTheme },
{ AppNoLayersProTemplate.TemplateName, AppNoLayersProTemplate.DefaultTheme }
}; };
return defaultThemesForTemplates.Any(defaultTheme => defaultTheme == theme); return templateThemes.TryGetValue(args.TemplateName!, out var templateTheme) && templateTheme == args.Theme;
} }
private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List<ProjectBuildPipelineStep> steps, bool isProTemplate, UiFramework uiFramework) private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List<ProjectBuildPipelineStep> steps, bool isProTemplate, UiFramework uiFramework)
@ -584,7 +586,8 @@ public abstract class AppTemplateBase : TemplateInfo
"https://localhost:44306", "https://localhost:44306",
"https://localhost:44307", "https://localhost:44307",
"https://localhost:44308", "https://localhost:44308",
"https://localhost:44309" "https://localhost:44309",
"https://localhost:44310"
} }
) )
); );

12
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularSourceCodeAdder.cs

@ -267,11 +267,19 @@ public class AngularSourceCodeAdder : ITransientDependency
var fileContent = File.ReadAllText(filePath); var fileContent = File.ReadAllText(filePath);
fileContent = "import { "+moduleName.Split(".").Last()+"Module } from '@"+moduleName.Split(".").Last().ToKebabCase()+"/config';" + Environment.NewLine + fileContent; var moduleNameAsConfigPath = moduleName.ToKebabCase();
if (moduleName.Contains("."))
{
var moduleNameSplited = moduleName.Split(".");
moduleNameAsConfigPath = moduleNameSplited.Take(moduleNameSplited.Length-1).JoinAsString(".").ToKebabCase() + "/" + moduleNameSplited.Last().ToKebabCase();
}
fileContent = "import { "+moduleName.Split(".").Last()+"ConfigModule } from '@"+moduleNameAsConfigPath+"/config';" + Environment.NewLine + fileContent;
fileContent = Regex.Replace(fileContent, "imports\\s*:\\s*\\[", fileContent = Regex.Replace(fileContent, "imports\\s*:\\s*\\[",
"imports: ["+ Environment.NewLine + "imports: ["+ Environment.NewLine +
" " + moduleName.Split(".").Last() + "Module.forRoot(),"); " " + moduleName.Split(".").Last() + "ConfigModule.forRoot(),");
File.WriteAllText(filePath, fileContent); File.WriteAllText(filePath, fileContent);
} }

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs

@ -125,7 +125,6 @@ public class ProjectNpmPackageAdder : ITransientDependency
Logger.LogInformation($"Installing '{npmPackage.Name}' package to the project '{packageJsonFilePath}'..."); Logger.LogInformation($"Installing '{npmPackage.Name}' package to the project '{packageJsonFilePath}'...");
if (version == null) if (version == null)
{ {
version = DetectAbpVersionOrNull(Path.Combine(directory, "package.json")); version = DetectAbpVersionOrNull(Path.Combine(directory, "package.json"));
@ -171,7 +170,7 @@ public class ProjectNpmPackageAdder : ITransientDependency
foreach (var package in packages) foreach (var package in packages)
{ {
if (package.Name.StartsWith("@abp/") || package.Name.StartsWith("@volo/")) if ((package.Name.StartsWith("@abp/") || package.Name.StartsWith("@volo/")) && !package.Name.Contains("leptonx"))
{ {
return package.Value.ToString(); return package.Value.ToString();
} }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs

@ -174,7 +174,7 @@ public class CmdHelper : ICmdHelper, ITransientDependency
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{ {
return delaySeconds == null ? "-c \"" + command + "\"" : "-c \"" + $"sleep {delaySeconds}s > /dev/null && " + command + "\""; return delaySeconds == null ? "-c \"" + command + "\"" : "-c \"" + $"sleep {delaySeconds} > /dev/null && " + command + "\"";
} }
//Windows default. //Windows default.

4
framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionRegistrationActionExtensions.cs

@ -5,9 +5,9 @@ namespace Microsoft.Extensions.DependencyInjection;
public static class ServiceCollectionRegistrationActionExtensions public static class ServiceCollectionRegistrationActionExtensions
{ {
// OnRegistred // OnRegistered
public static void OnRegistred(this IServiceCollection services, Action<IOnServiceRegistredContext> registrationAction) public static void OnRegistered(this IServiceCollection services, Action<IOnServiceRegistredContext> registrationAction)
{ {
GetOrCreateRegistrationActionList(services).Add(registrationAction); GetOrCreateRegistrationActionList(services).Add(registrationAction);
} }

2
framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs

@ -42,7 +42,7 @@ public class AbpDataModule : AbpModule
{ {
var contributors = new List<Type>(); var contributors = new List<Type>();
services.OnRegistred(context => services.OnRegistered(context =>
{ {
if (typeof(IDataSeedContributor).IsAssignableFrom(context.ImplementationType)) if (typeof(IDataSeedContributor).IsAssignableFrom(context.ImplementationType))
{ {

17
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.DependencyInjection; namespace Volo.Abp.DependencyInjection;
@ -15,7 +16,7 @@ public abstract class AbpCommonDbContextRegistrationOptions : IAbpCommonDbContex
public IServiceCollection Services { get; } public IServiceCollection Services { get; }
public Dictionary<Type, Type> ReplacedDbContextTypes { get; } public Dictionary<MultiTenantDbContextType, Type> ReplacedDbContextTypes { get; }
public Type DefaultRepositoryDbContextType { get; protected set; } public Type DefaultRepositoryDbContextType { get; protected set; }
@ -39,28 +40,28 @@ public abstract class AbpCommonDbContextRegistrationOptions : IAbpCommonDbContex
Services = services; Services = services;
DefaultRepositoryDbContextType = originalDbContextType; DefaultRepositoryDbContextType = originalDbContextType;
CustomRepositories = new Dictionary<Type, Type>(); CustomRepositories = new Dictionary<Type, Type>();
ReplacedDbContextTypes = new Dictionary<Type, Type>(); ReplacedDbContextTypes = new Dictionary<MultiTenantDbContextType, Type>();
SpecifiedDefaultRepositories = new List<Type>(); SpecifiedDefaultRepositories = new List<Type>();
} }
public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext<TOtherDbContext>() public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext<TOtherDbContext>(MultiTenancySides multiTenancySides = MultiTenancySides.Both)
{ {
return ReplaceDbContext(typeof(TOtherDbContext)); return ReplaceDbContext(typeof(TOtherDbContext), multiTenancySides: multiTenancySides);
} }
public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext<TOtherDbContext, TTargetDbContext>() public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext<TOtherDbContext, TTargetDbContext>(MultiTenancySides multiTenancySides = MultiTenancySides.Both)
{ {
return ReplaceDbContext(typeof(TOtherDbContext), typeof(TTargetDbContext)); return ReplaceDbContext(typeof(TOtherDbContext), typeof(TTargetDbContext), multiTenancySides);
} }
public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType, Type targetDbContextType = null) public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType, Type targetDbContextType = null, MultiTenancySides multiTenancySides = MultiTenancySides.Both)
{ {
if (!otherDbContextType.IsAssignableFrom(OriginalDbContextType)) if (!otherDbContextType.IsAssignableFrom(OriginalDbContextType))
{ {
throw new AbpException($"{OriginalDbContextType.AssemblyQualifiedName} should inherit/implement {otherDbContextType.AssemblyQualifiedName}!"); throw new AbpException($"{OriginalDbContextType.AssemblyQualifiedName} should inherit/implement {otherDbContextType.AssemblyQualifiedName}!");
} }
ReplacedDbContextTypes[otherDbContextType] = targetDbContextType; ReplacedDbContextTypes[new MultiTenantDbContextType(otherDbContextType, multiTenancySides)] = targetDbContextType;
return this; return this;
} }

10
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs

@ -1,6 +1,7 @@
using System; using System;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.DependencyInjection; namespace Volo.Abp.DependencyInjection;
@ -72,19 +73,22 @@ public interface IAbpCommonDbContextRegistrationOptionsBuilder
/// Replaces given DbContext type with this DbContext type. /// Replaces given DbContext type with this DbContext type.
/// </summary> /// </summary>
/// <typeparam name="TOtherDbContext">The DbContext type to be replaced</typeparam> /// <typeparam name="TOtherDbContext">The DbContext type to be replaced</typeparam>
IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext<TOtherDbContext>(); /// <param name="multiTenancySides">MultiTenancy side</param>
IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext<TOtherDbContext>(MultiTenancySides multiTenancySides = MultiTenancySides.Both);
/// <summary> /// <summary>
/// Replaces given DbContext type with the target DbContext type. /// Replaces given DbContext type with the target DbContext type.
/// </summary> /// </summary>
/// <typeparam name="TOtherDbContext">The DbContext type to be replaced</typeparam> /// <typeparam name="TOtherDbContext">The DbContext type to be replaced</typeparam>
/// <typeparam name="TTargetDbContext">The target DbContext type</typeparam> /// <typeparam name="TTargetDbContext">The target DbContext type</typeparam>
IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext<TOtherDbContext, TTargetDbContext>(); /// <param name="multiTenancySides">MultiTenancy side</param>
IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext<TOtherDbContext, TTargetDbContext>(MultiTenancySides multiTenancySides = MultiTenancySides.Both);
/// <summary> /// <summary>
/// Replaces given DbContext type with the given or this DbContext type. /// Replaces given DbContext type with the given or this DbContext type.
/// </summary> /// </summary>
/// <param name="otherDbContextType">The DbContext type to be replaced</param> /// <param name="otherDbContextType">The DbContext type to be replaced</param>
/// <param name="targetDbContextType">The target DbContext type (optional, used this DbContext type if not provided)</param> /// <param name="targetDbContextType">The target DbContext type (optional, used this DbContext type if not provided)</param>
IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType, Type targetDbContextType = null); /// <param name="multiTenancySides">MultiTenancy side</param>
IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType, Type targetDbContextType = null, MultiTenancySides multiTenancySides = MultiTenancySides.Both);
} }

34
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/MultiTenantDbContextType.cs

@ -0,0 +1,34 @@
using System;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.DependencyInjection;
public class MultiTenantDbContextType
{
public Type Type { get; }
public MultiTenancySides MultiTenancySide { get; }
public MultiTenantDbContextType(Type type, MultiTenancySides multiTenancySide = MultiTenancySides.Both)
{
Type = type;
MultiTenancySide = multiTenancySide;
}
public override bool Equals(object obj)
{
var other = obj as MultiTenantDbContextType;
if (other == null)
{
return false;
}
return other.Type == Type && other.MultiTenancySide == MultiTenancySide;
}
public override int GetHashCode()
{
return Type.GetHashCode() ^ MultiTenancySide.GetHashCode();
}
}

11
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/ReplaceDbContextAttribute.cs

@ -1,14 +1,21 @@
using System; using System;
using System.Linq;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.DependencyInjection; namespace Volo.Abp.DependencyInjection;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ReplaceDbContextAttribute : Attribute public class ReplaceDbContextAttribute : Attribute
{ {
public Type[] ReplacedDbContextTypes { get; } public MultiTenantDbContextType[] ReplacedDbContextTypes { get; }
public ReplaceDbContextAttribute(params Type[] replacedDbContextTypes) public ReplaceDbContextAttribute(params Type[] replacedDbContextTypes)
{ {
ReplacedDbContextTypes = replacedDbContextTypes; ReplacedDbContextTypes = replacedDbContextTypes.Select(type => new MultiTenantDbContextType(type)).ToArray();
}
public ReplaceDbContextAttribute(Type replacedDbContextType, MultiTenancySides side)
{
ReplacedDbContextTypes = new[] {new MultiTenantDbContextType(replacedDbContextType, side)};
} }
} }

7
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using JetBrains.Annotations; using JetBrains.Annotations;
using Volo.Abp.Domain.Values;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
@ -122,6 +123,12 @@ public static class EntityHelper
return typeof(IEntity).IsAssignableFrom(type); return typeof(IEntity).IsAssignableFrom(type);
} }
public static bool IsValueObject([NotNull] Type type)
{
Check.NotNull(type, nameof(type));
return typeof(ValueObject).IsAssignableFrom(type);
}
public static void CheckEntity([NotNull] Type type) public static void CheckEntity([NotNull] Type type)
{ {
Check.NotNull(type, nameof(type)); Check.NotNull(type, nameof(type));

23
framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs

@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection; using Volo.Abp.EntityFrameworkCore.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.DependencyInjection;
@ -19,12 +20,12 @@ public static class AbpEfCoreServiceCollectionExtensions
var options = new AbpDbContextRegistrationOptions(typeof(TDbContext), services); var options = new AbpDbContextRegistrationOptions(typeof(TDbContext), services);
var replacedDbContextTypes = typeof(TDbContext).GetCustomAttributes<ReplaceDbContextAttribute>(true) var replacedMultiTenantDbContextTypes = typeof(TDbContext).GetCustomAttributes<ReplaceDbContextAttribute>(true)
.SelectMany(x => x.ReplacedDbContextTypes).ToList(); .SelectMany(x => x.ReplacedDbContextTypes).ToList();
foreach (var dbContextType in replacedDbContextTypes) foreach (var dbContextType in replacedMultiTenantDbContextTypes)
{ {
options.ReplaceDbContext(dbContextType); options.ReplaceDbContext(dbContextType.Type, multiTenancySides: dbContextType.MultiTenancySide);
} }
optionsBuilder?.Invoke(options); optionsBuilder?.Invoke(options);
@ -33,19 +34,19 @@ public static class AbpEfCoreServiceCollectionExtensions
foreach (var entry in options.ReplacedDbContextTypes) foreach (var entry in options.ReplacedDbContextTypes)
{ {
var originalDbContextType = entry.Key; var originalDbContextType = entry.Key.Type;
var targetDbContextType = entry.Value ?? typeof(TDbContext); var targetDbContextType = entry.Value ?? typeof(TDbContext);
services.Replace( services.Replace(ServiceDescriptor.Transient(originalDbContextType, sp =>
ServiceDescriptor.Transient( {
originalDbContextType, var dbContextType = sp.GetRequiredService<IEfCoreDbContextTypeProvider>().GetDbContextType(originalDbContextType);
sp => sp.GetRequiredService(targetDbContextType) return sp.GetRequiredService(dbContextType);
) }));
);
services.Configure<AbpDbContextOptions>(opts => services.Configure<AbpDbContextOptions>(opts =>
{ {
opts.DbContextReplacements[originalDbContextType] = targetDbContextType; var multiTenantDbContextType = new MultiTenantDbContextType(originalDbContextType, entry.Key.MultiTenancySide);
opts.DbContextReplacements[multiTenantDbContextType] = targetDbContextType;
}); });
} }

17
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs

@ -1,7 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore.DependencyInjection; using Volo.Abp.EntityFrameworkCore.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.EntityFrameworkCore; namespace Volo.Abp.EntityFrameworkCore;
@ -15,14 +18,14 @@ public class AbpDbContextOptions
internal Dictionary<Type, object> ConfigureActions { get; } internal Dictionary<Type, object> ConfigureActions { get; }
internal Dictionary<Type, Type> DbContextReplacements { get; } internal Dictionary<MultiTenantDbContextType, Type> DbContextReplacements { get; }
public AbpDbContextOptions() public AbpDbContextOptions()
{ {
DefaultPreConfigureActions = new List<Action<AbpDbContextConfigurationContext>>(); DefaultPreConfigureActions = new List<Action<AbpDbContextConfigurationContext>>();
PreConfigureActions = new Dictionary<Type, List<object>>(); PreConfigureActions = new Dictionary<Type, List<object>>();
ConfigureActions = new Dictionary<Type, object>(); ConfigureActions = new Dictionary<Type, object>();
DbContextReplacements = new Dictionary<Type, Type>(); DbContextReplacements = new Dictionary<MultiTenantDbContextType, Type>();
} }
public void PreConfigure([NotNull] Action<AbpDbContextConfigurationContext> action) public void PreConfigure([NotNull] Action<AbpDbContextConfigurationContext> action)
@ -76,22 +79,22 @@ public class AbpDbContextOptions
return ConfigureActions.ContainsKey(dbContextType); return ConfigureActions.ContainsKey(dbContextType);
} }
internal Type GetReplacedTypeOrSelf(Type dbContextType) internal Type GetReplacedTypeOrSelf(Type dbContextType, MultiTenancySides multiTenancySides = MultiTenancySides.Both)
{ {
var replacementType = dbContextType; var replacementType = dbContextType;
while (true) while (true)
{ {
if (DbContextReplacements.TryGetValue(replacementType, out var foundType)) var foundType = DbContextReplacements.LastOrDefault(x => x.Key.Type == replacementType && x.Key.MultiTenancySide.HasFlag(multiTenancySides));
if (!foundType.Equals(default(KeyValuePair<MultiTenantDbContextType, Type>)))
{ {
if (foundType == dbContextType) if (foundType.Value == dbContextType)
{ {
throw new AbpException( throw new AbpException(
"Circular DbContext replacement found for " + "Circular DbContext replacement found for " +
dbContextType.AssemblyQualifiedName dbContextType.AssemblyQualifiedName
); );
} }
replacementType = foundType.Value;
replacementType = foundType;
} }
else else
{ {

23
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreDbContextTypeProvider.cs

@ -0,0 +1,23 @@
using System;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.EntityFrameworkCore;
public class EfCoreDbContextTypeProvider : IEfCoreDbContextTypeProvider, ITransientDependency
{
private readonly AbpDbContextOptions _options;
private readonly ICurrentTenant _currentTenant;
public EfCoreDbContextTypeProvider(IOptions<AbpDbContextOptions> options, ICurrentTenant currentTenant)
{
_currentTenant = currentTenant;
_options = options.Value;
}
public virtual Type GetDbContextType(Type dbContextType)
{
return _options.GetReplacedTypeOrSelf(dbContextType, _currentTenant.GetMultiTenancySide());
}
}

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

Loading…
Cancel
Save