Browse Source

Merge branch 'dev' into bundle-command

pull/19842/head
maliming 2 years ago
parent
commit
f7223ef6c3
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 20
      .github/workflows/auto-pr.yml
  2. 14
      Directory.Packages.props
  3. 232
      LICENSE.md
  4. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ar.json
  5. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/cs.json
  6. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/de.json
  7. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  8. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/es.json
  9. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json
  10. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json
  11. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json
  12. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hr.json
  13. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json
  14. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/is.json
  15. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json
  16. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/nl.json
  17. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pl-PL.json
  18. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pt-BR.json
  19. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ro-RO.json
  20. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sk.json
  21. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json
  22. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
  23. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/vi.json
  24. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json
  25. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json
  26. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/ar.json
  27. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  28. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json
  29. 37
      docs/en/API/API-Versioning.md
  30. 11
      docs/en/API/Application-Configuration.md
  31. 269
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/POST.md
  32. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/abp-dotnet-conf-2024.png
  33. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/audit-logging-module-general-settings.png
  34. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/audit-logging-module-global-settings.png
  35. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/block-ui-service-blazor.gif
  36. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/concurrent-login-settings.png
  37. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/cover-image.png
  38. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/devdays-europe.jpg
  39. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/devnot-dotnet-conference-alper-ebicoglu.png
  40. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/devnot-dotnet-conference-enis-necipoglu.png
  41. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/devops-pro-europe.jpg
  42. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/manage-user-sessions-1.png
  43. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/manage-user-sessions-2.png
  44. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-dateonly-timeonly-properties.png
  45. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-dateonly-timeonly-ui.png
  46. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-file-property-create.png
  47. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-file-property.png
  48. BIN
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-file-upload-in-action.gif
  49. 3
      docs/en/Community-Articles/2024-05-07-Azure-Container-Apps-Deployment-with-ABP/POST.md
  50. BIN
      docs/en/Community-Articles/2024-05-09-what-is-object-to-object-mapping/images/automapper.png
  51. BIN
      docs/en/Community-Articles/2024-05-09-what-is-object-to-object-mapping/images/swagger1.png
  52. BIN
      docs/en/Community-Articles/2024-05-09-what-is-object-to-object-mapping/images/swagger2.png
  53. 134
      docs/en/Community-Articles/2024-05-09-what-is-object-to-object-mapping/post.md
  54. 8
      docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md
  55. 228
      docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/POST.md
  56. BIN
      docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks-helm.png
  57. BIN
      docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks.png
  58. BIN
      docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/build-docker-image.png
  59. BIN
      docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/configure-cors.png
  60. BIN
      docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/create-azure-kubernetes-service.png
  61. BIN
      docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/deploy-helm-chart.png
  62. BIN
      docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/docker-hub-repository.png
  63. BIN
      docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/helm-chart.png
  64. 4
      docs/en/Getting-Started-React-Native.md
  65. 61
      docs/en/Migration-Guides/Abp-8_2.md
  66. 1
      docs/en/Migration-Guides/Index.md
  67. 112
      docs/en/Multi-Tenancy.md
  68. 13
      docs/en/Themes/LeptonXLite/Angular.md
  69. 19
      docs/en/UI/Angular/Account-Module.md
  70. 2
      docs/en/UI/Angular/Basic-Theme.md
  71. 117
      docs/en/UI/Angular/Component-Replacement.md
  72. 37
      docs/en/UI/Angular/Confirmation-Service.md
  73. 8
      docs/en/UI/Angular/Feature-Libraries.md
  74. 52
      docs/en/UI/Angular/Form-Validation.md
  75. 22
      docs/en/UI/Angular/HTTP-Error-Handling.md
  76. 183
      docs/en/UI/Angular/Localization.md
  77. 9
      docs/en/UI/Angular/OAuth-Module.md
  78. 59
      docs/en/UI/Angular/Title-Strategy-Service.md
  79. 4
      docs/en/docs-nav.json
  80. 37
      docs/zh-Hans/API/API-Versioning.md
  81. 27
      docs/zh-Hans/UI/Angular/Localization.md
  82. 26
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs
  83. 57
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/CachedBundleDynamicFileProvider.cs
  84. 22
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/InMemoryFileInfoCacheItem.cs
  85. 6
      framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningExtensions.cs
  86. 1
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
  87. 7
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditActionFilter.cs
  88. 7
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs
  89. 12
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs
  90. 14
      framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor
  91. 2
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor
  92. 1
      framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
  93. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs
  94. 11
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  95. 21
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  96. 6
      framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DynamicFileProvider.cs
  97. 8
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  98. 8
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  99. 19
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs
  100. 101
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs

20
.github/workflows/auto-pr.yml

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

14
Directory.Packages.props

@ -11,15 +11,16 @@
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="Autofac.Extras.DynamicProxy" Version="7.1.0" />
<PackageVersion Include="AutoMapper" Version="13.0.1" />
<PackageVersion Include="Asp.Versioning.Mvc" Version="7.1.0" />
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageVersion Include="AWSSDK.S3" Version="3.7.300.2" />
<PackageVersion Include="AWSSDK.SecurityToken" Version="3.7.300.2" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.17.0" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.19.1" />
<PackageVersion Include="Blazorise" Version="1.5.0" />
<PackageVersion Include="Blazorise.Components" Version="1.5.0" />
<PackageVersion Include="Blazorise.DataGrid" Version="1.5.0" />
<PackageVersion Include="Blazorise.Snackbar" Version="1.5.0" />
<PackageVersion Include="Blazorise" Version="1.5.2" />
<PackageVersion Include="Blazorise.Components" Version="1.5.2" />
<PackageVersion Include="Blazorise.DataGrid" Version="1.5.2" />
<PackageVersion Include="Blazorise.Snackbar" Version="1.5.2" />
<PackageVersion Include="Castle.Core" Version="5.1.1" />
<PackageVersion Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
<PackageVersion Include="CommonMark.NET" Version="0.15.1" />
@ -60,7 +61,6 @@
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="8.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.4" />
@ -88,7 +88,7 @@
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Composite" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.4" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />

232
LICENSE.md

@ -1,165 +1,89 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public.
The license is supplemented by the additional permissions listed below.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
**0. Additional Definitions.**
As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License.
"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided by the Library but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version".
The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
**1. Exception to Section 3 of the GNU GPL.**
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
**2. Conveying Modified Versions.**
If you modify a copy of the Library and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
**a)** under this License, provided that you make a good-faith effort to ensure that, in the event an Application does not supply the
function or data, the facility still operates and performs whatever part of its purpose remains meaningful, or
**b)** under the GNU GPL, with none of the additional permissions of this License is applicable to that copy.
**3. Object Code Incorporating Material from Library Header Files.**
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such an object code under terms of your choice, provided that if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
**a)** Give prominent notice with each copy of the object code that the Library is used in it, and the Library and its use are covered by this License.
**b)** Accompany the object code with a copy of the GNU GPL and this license document.
**4. Combined Works.**
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
**a)** Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
**b)** Accompany the Combined Work with a copy of the GNU GPL and this license document.
**c)** For a Combined Work that displays copyright notices during execution, including the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
**d)** Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this License and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
**e)** Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying the Corresponding Source.)
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
You may place library facilities that are work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License and convey such a combined library under the terms of your choice if you do both of the following:
**a)** Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
**b)** Give prominent notice with the combined library that part of it is a work based on the Library and explains where to find the accompanying uncombined form of the same work.
**6. Revised Versions of the GNU Lesser General Public License.**
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received, it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ar.json

@ -816,6 +816,7 @@
"ErrorExceptionMessage": "حدث خطأ أثناء معالجة طلبك",
"WatchTakeCodeGeneration": "شاهد فيديو \"استكشاف إمكانات إنشاء الأكواد البرمجية: ABP Suite\"!",
"ExtendNow": "تمديد / تجديد",
"RegisterDemo": "يسجل"
"RegisterDemo": "يسجل",
"AbpStudio_ComingSoon": "إذا كنت ترغب في اختبار ABP Studio قبل أي شخص آخر، يمكنك أن تصبح أحد مختبري النسخة التجريبية"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/cs.json

@ -431,6 +431,7 @@
"ErrorExceptionMessage": "Při zpracování vašeho požadavku nastala chyba",
"WatchTakeCodeGeneration": "Podívejte se na video „Prozkoumejte potenciál generování kódu: ABP Suite“!",
"ExtendNow": "Prodloužit / Obnovit",
"RegisterDemo": "Registrovat"
"RegisterDemo": "Registrovat",
"AbpStudio_ComingSoon": "Pokud chcete ABP Studio otestovat dříve než kdokoli jiný, staňte se BETA testerem"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/de.json

@ -411,6 +411,7 @@
"SendUsEmail": "Senden Sie uns eine E-Mail",
"ErrorExceptionMessage": "Während Ihrer Anfrage ist ein Fehler aufgetreten",
"WatchTakeCodeGeneration": "Sehen Sie sich das Video „Entdecken Sie das Potenzial der Codegenerierung: ABP Suite“ an!",
"ExtendNow": "Verlängern / Erneuern"
"ExtendNow": "Verlängern / Erneuern",
"AbpStudio_ComingSoon": "Wenn Sie ABP Studio vor allen anderen testen möchten, werden Sie BETA-Tester"
}
}

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

@ -916,7 +916,7 @@
"AbpStudio_Description": "ABP Studio is still under development. You can fill out the form below to be one of the first users.",
"AbpStudio_Description1": "ABP Studio is a <span class=\"text-highlight-white\">cross-platform desktop application</span> for ABP developers.",
"AbpStudio_Description2": "It is <span class=\"text-highlight-white\">well integrated</span> to the ABP Framework and aims to provide a comfortable development environment for you by <span class=\"text-highlight-white\">automating things, providing insights</span> about your solution, making <span class=\"text-highlight-white\">develop, run</span> and <span class=\"text-highlight-white\">deploy</span> your solutions much easier.",
"AbpStudio_ComingSoon": "Coming Soon <span class=\"opacity-50 ms-0 ms-md-3 d-block d-md-inline-block \">Planned beta release date: Q4 of 2023.</span>",
"AbpStudio_ComingSoon": "If you want to test ABP Studio before anyone else, become a BETA tester",
"AbpStudio_PlannedPreviewDate": "Planned preview release date: Q4 of 2023.",
"BetaRequest": "Beta Request",
"CreateNewSolutions": "Create New Solutions",

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/es.json

@ -411,6 +411,7 @@
"SendUsEmail": "Envíanos un correo electrónico",
"ErrorExceptionMessage": "Se produjo un error al procesar su solicitud.",
"WatchTakeCodeGeneration": "¡Mire el vídeo \"Explore el potencial de la generación de código: ABP Suite\"!",
"ExtendNow": "Ampliar / Renovar"
"ExtendNow": "Ampliar / Renovar",
"AbpStudio_ComingSoon": "Si quieres probar ABP Studio antes que nadie, conviértete en tester BETA"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json

@ -852,6 +852,7 @@
"ErrorExceptionMessage": "Virhe pyynnön käsittelyn yhteydessä",
"WatchTakeCodeGeneration": "Katso \"Tutki koodin luomisen potentiaalia: ABP Suite\" -video!",
"ExtendNow": "Laajenna / Uusi",
"RegisterDemo": "Rekisteröidy"
"RegisterDemo": "Rekisteröidy",
"AbpStudio_ComingSoon": "Jos haluat testata ABP Studiota ennen muita, ryhdy BETA-testaajaksi"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json

@ -431,6 +431,7 @@
"ErrorExceptionMessage": "Une erreur s'est produite lors du traitement de votre demande",
"WatchTakeCodeGeneration": "Regardez la vidéo « Explorez le potentiel de la génération de code : ABP Suite » !",
"ExtendNow": "Prolonger / Renouveler",
"RegisterDemo": "Registre"
"RegisterDemo": "Registre",
"AbpStudio_ComingSoon": "Si vous souhaitez tester ABP Studio avant tout le monde, devenez BETA testeur"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json

@ -430,6 +430,7 @@
"ErrorExceptionMessage": "आपका अनुरोध संसाधित करते समय एक त्रुटि पाई गई",
"WatchTakeCodeGeneration": "\"कोड जनरेशन की क्षमता का अन्वेषण करें: एबीपी सुइट\" वीडियो देखें!",
"ExtendNow": "विस्तार/नवीनीकरण",
"RegisterDemo": "पंजीकरण करवाना"
"RegisterDemo": "पंजीकरण करवाना",
"AbpStudio_ComingSoon": "यदि आप किसी और से पहले एबीपी स्टूडियो का परीक्षण करना चाहते हैं, तो बीटा परीक्षक बनें"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hr.json

@ -915,7 +915,6 @@
"AbpStudio_Description": "ABP Studio je još uvijek u razvoju. Možete ispuniti obrazac ispod i biti jedan od prvih korisnika.",
"AbpStudio_Description1": "ABP Studio je <span class=\"text-highlight-white\">stolna aplikacija za više platformi</span> za ABP programere.",
"AbpStudio_Description2": "<span class=\"text-highlight-white\">Dobro je integriran</span> u ABP Framework i ima za cilj pružiti vam ugodno razvojno okruženje <span class=\"text-highlight-white\">automatiziranjem stvari, pružanjem uvida</span> u vaše rješenje, čineći <span class=\"text-highlight-white\">razvoj, pokretanje</span> i <span class=\"text-highlight-white\">implementaciju</span> vaših rješenja puno lakšim.",
"AbpStudio_ComingSoon": "Uskoro. <span class=\"opacity-50 ms-0 ms-md-3 d-block d-md-inline-block \">Planirani datum izdavanja beta verzije: 4. tromjesečje 2023.</span>",
"AbpStudio_PlannedPreviewDate": "Planirani datum izdavanja pregleda: 4. kvartal 2023.",
"BetaRequest": "Zahtjev za beta verziju",
"CreateNewSolutions": "Stvorite nova rješenja",
@ -1107,6 +1106,7 @@
"SendUsEmail": "Pošaljite nam e-mail",
"ErrorExceptionMessage": "Pojavila se greška prilikom obrade Vašeg zahtjeva",
"WatchTakeCodeGeneration": "Pogledajte video \"Istražite potencijal generiranja koda: ABP Suite\"!",
"ExtendNow": "Produži / obnovi"
"ExtendNow": "Produži / obnovi",
"AbpStudio_ComingSoon": "Ako želite testirati ABP Studio prije svih ostalih, postanite BETA tester"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json

@ -777,6 +777,7 @@
"SendUsEmail": "Küldjön nekünk e-mailt",
"ErrorExceptionMessage": "Hiba történt a kérése feldolgozása során",
"WatchTakeCodeGeneration": "Nézze meg a \"Fedezze fel a kódgenerálás lehetőségét: ABP Suite\" videót!",
"ExtendNow": "Bővítés / Megújítás"
"ExtendNow": "Bővítés / Megújítás",
"AbpStudio_ComingSoon": "Ha bárki más előtt szeretné tesztelni az ABP Studio-t, legyen BÉTA tesztelő"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/is.json

@ -408,6 +408,7 @@
"SendUsEmail": "Sendu okkur tölvupóst",
"ErrorExceptionMessage": "Villa kom upp við úrvinnslu beiðni þinnar",
"WatchTakeCodeGeneration": "Horfðu á \"Kannaðu möguleika kóðaframleiðslu: ABP Suite\" myndbandið!",
"ExtendNow": "Framlengja / endurnýja"
"ExtendNow": "Framlengja / endurnýja",
"AbpStudio_ComingSoon": "Ef þú vilt prófa ABP Studio á undan öllum öðrum skaltu gerast BETA prófari"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json

@ -430,6 +430,7 @@
"ErrorExceptionMessage": "Si è verificato un errore durante l'elaborazione della richiesta",
"WatchTakeCodeGeneration": "Guarda il video \"Esplora il potenziale della generazione di codice: ABP Suite\"!",
"ExtendNow": "Estendi/Rinnova",
"RegisterDemo": "Registrati"
"RegisterDemo": "Registrati",
"AbpStudio_ComingSoon": "Se vuoi provare ABP Studio prima di chiunque altro, diventa un BETA tester"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/nl.json

@ -408,6 +408,7 @@
"SendUsEmail": "Stuur ons een e-mail",
"ErrorExceptionMessage": "Er is een fout opgetreden bij het verwerken van uw verzoek",
"WatchTakeCodeGeneration": "Bekijk de video 'Ontdek het potentieel van codegeneratie: ABP Suite'!",
"ExtendNow": "Verlengen / Verlengen"
"ExtendNow": "Verlengen / Verlengen",
"AbpStudio_ComingSoon": "Als u ABP Studio als eerste wilt testen, wordt dan BETA-tester"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pl-PL.json

@ -409,6 +409,7 @@
"ErrorExceptionMessage": "Podczas przetwarzania żądania wystąpił błąd",
"WatchTakeCodeGeneration": "Obejrzyj film „Odkryj potencjał generowania kodu: pakiet ABP”!",
"ExtendNow": "Przedłuż/Odnów",
"RegisterDemo": "Rejestr"
"RegisterDemo": "Rejestr",
"AbpStudio_ComingSoon": "Jeśli chcesz przetestować ABP Studio zanim ktokolwiek inny, zostań testerem BETA"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pt-BR.json

@ -431,6 +431,7 @@
"SendUsEmail": "Envie-nos um e-mail",
"ErrorExceptionMessage": "Um erro ocorreu durante o processamento do seu pedido",
"WatchTakeCodeGeneration": "Assista ao vídeo \"Explore o potencial de geração de código: ABP Suite\"!",
"ExtendNow": "Estender/Renovar"
"ExtendNow": "Estender/Renovar",
"AbpStudio_ComingSoon": "Se você quiser testar o ABP Studio antes de todo mundo, torne-se um testador BETA"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ro-RO.json

@ -408,6 +408,7 @@
"SendUsEmail": "Trimite-ne e-mail",
"ErrorExceptionMessage": "A apărut o eroare în timpul procesării cererii dumneavoastră",
"WatchTakeCodeGeneration": "Urmărește videoclipul „Explorați potențialul generării codului: ABP Suite”!",
"ExtendNow": "Extinde/Reînnoiește"
"ExtendNow": "Extinde/Reînnoiește",
"AbpStudio_ComingSoon": "Dacă doriți să testați ABP Studio înaintea oricui, deveniți un tester BETA"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sk.json

@ -427,6 +427,7 @@
"SendUsEmail": "Pošlite nám e-mail",
"ErrorExceptionMessage": "Nastala chyba pri spracovaní vašej žiadosti",
"WatchTakeCodeGeneration": "Pozrite si video „Preskúmajte potenciál generovania kódu: ABP Suite“!",
"ExtendNow": "Predĺžiť / obnoviť"
"ExtendNow": "Predĺžiť / obnoviť",
"AbpStudio_ComingSoon": "Ak chcete otestovať ABP Studio skôr ako ktokoľvek iný, staňte sa BETA testerom"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json

@ -409,6 +409,7 @@
"ErrorExceptionMessage": "Pri obdelavi vaše zahteve je prišlo do napake",
"WatchTakeCodeGeneration": "Oglejte si video »Raziščite potencial generiranja kode: zbirka ABP«!",
"ExtendNow": "Podaljšaj / obnovi",
"RegisterDemo": "Registrirajte se"
"RegisterDemo": "Registrirajte se",
"AbpStudio_ComingSoon": "Če želite preizkusiti ABP Studio pred vsemi drugimi, postanite BETA tester"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json

@ -1058,7 +1058,6 @@
"AbpStudio_Description": "ABP Studio hala geliştirme aşamasında. İlk kullanıcılardan biri olmak için aşağıdaki formu doldurabilirsiniz.",
"AbpStudio_Description1": "ABP Studio, ABP geliştiricileri için <span class=\"text-highlight-white\">çok platformlu masaüstü uygulamasıdır</span>.",
"AbpStudio_Description2": "ABP Studio, ABP Framework'e <span class=\"text-highlight-white\">iyi entegre edilmiştir</span> ve sizin için işleri <span class=\"text-highlight-white\">otomatikleştirerek, projeniz hakkında bilgi sağlayarak</span>, projelerinizi <span class=\"text-highlight-white\">geliştirmeyi, çalıştırmayı</span> ve <span class=\"text-highlight-white\">dağıtımını</span> çok daha kolay hale getirmeyi amaçlamaktadır.",
"AbpStudio_ComingSoon": "Yakında <span class=\"opacity-50 ms-0 ms-md-3 d-block d-md-inline-block \">Planlanan beta sürüm tarihi: 2023'ün 4. çeyreği.</span>",
"AbpStudio_PlannedPreviewDate": "Planlanan beta sürüm tarihi: 2023'ün 4. çeyreği.",
"BetaRequest": "Beta Talebi",
"CreateNewSolutions": "Yeni Projeler Oluştur",
@ -1140,6 +1139,7 @@
"RemoveBasket": "Sepetten kaldır",
"TrainingPack": "Eğitim paketi",
"TrainingPackDiscount": "Eğitim paketi indirimi",
"LinkExpiredMessage": "Ödeme bağlantısının süresi doldu! Bağlantıyı güncellemek için <a href='mailto:sales@volosoft.com'>sales@volosoft.com</a> adresinden bizimle iletişime geçin veya iletişim sayfasına gitmek için <a href='https://commercial.abp.io/contact'>buraya</a> tıklayın."
"LinkExpiredMessage": "Ödeme bağlantısının süresi doldu! Bağlantıyı güncellemek için <a href='mailto:sales@volosoft.com'>sales@volosoft.com</a> adresinden bizimle iletişime geçin veya iletişim sayfasına gitmek için <a href='https://commercial.abp.io/contact'>buraya</a> tıklayın.",
"AbpStudio_ComingSoon": "ABP Studio'yu herkesten önce test etmek istiyorsanız BETA test kullanıcısı olun"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/vi.json

@ -408,6 +408,7 @@
"SendUsEmail": "Gửi email cho chúng tôi",
"ErrorExceptionMessage": "Đã xảy ra lỗi trong khi xử lý yêu cầu của bạn",
"WatchTakeCodeGeneration": "Xem video \"Khám phá tiềm năng tạo mã: ABP Suite\"!",
"ExtendNow": "Gia hạn/Gia hạn"
"ExtendNow": "Gia hạn/Gia hạn",
"AbpStudio_ComingSoon": "Nếu bạn muốn thử nghiệm ABP Studio trước bất kỳ ai khác, hãy trở thành người thử nghiệm BETA"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json

@ -915,7 +915,6 @@
"AbpStudio_Description": "ABP Studio 仍在开发中。您可以填写下面的表单,成为首批用户之一。",
"AbpStudio_Description1": "ABP Studio 是专为 ABP 开发人员设计的<span class=\"text-highlight-white\">跨平台桌面应用程序</span>。",
"AbpStudio_Description2": "它<span class=\"text-highlight-white\">与 ABP 框架集成得很好</span>,旨在通过<span class=\"text-highlight-white\">自动化、提供有关解决方案的见解</span>,为您提供舒适的开发环境,使<span class=\"text-highlight-white\">开发、运行</span>和<span class=\"text-highlight-white\">部署</span>解决方案变得更加容易。",
"AbpStudio_ComingSoon": "即将发布 <span class=\"opacity-50 ms-0 ms-md-3 d-block d-md-inline-block \">计划测试版发布日期:</span>计划测试版发布日期:2023年第四季度。",
"AbpStudio_PlannedPreviewDate": "计划预览版发布日期:2023 年第四季度。",
"BetaRequest": "测试版请求",
"CreateNewSolutions": "创建新的解决方案",
@ -1110,6 +1109,7 @@
"ErrorExceptionMessage": "处理您的请求时发生错误",
"WatchTakeCodeGeneration": "观看“探索代码生成的潜力:ABP Suite”视频!",
"PreBuiltApplicationModulesTitle": "预构建应用程序 <span class=\"gradient-commercial\">模块</span>",
"RegisterDemo": "登记"
"RegisterDemo": "登记",
"AbpStudio_ComingSoon": "如果您想先于其他人测试 ABP Studio,请成为 BETA 测试员"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json

@ -897,7 +897,6 @@
"AbpStudio_Description": "ABP Studio 仍在開發中。您可以填寫下面的表单,成為第一批使用者之一。",
"AbpStudio_Description1": "ABP Studio 是一款針對 ABP 開發人員的<span class=\"text-highlight-white\">跨平台桌面應用程式</span>。",
"AbpStudio_Description2": "它與 ABP 框架<span class=\"text-highlight-white\">很好地集成</span>,旨在透過<span class=\"text-highlight-white\">自動化為您提供一個舒適的開發環境,提供有關您的解決方案的見解</span>,使<span class=\"text-highlight-white\">開發、運行</span>和<span class=\"text-highlight-white\">部署</span>您的解決方案變得更加容易。",
"AbpStudio_ComingSoon": "即將推出<span class=\"opacity-50 ms-0 ms-md-3 d-block d-md-inline-block \">計劃測試版發布日期:2023 年第四季。</span>",
"AbpStudio_PlannedPreviewDate": "計畫預覽版發布日期:2023 年第四季。",
"BetaRequest": "測試版請求",
"CreateNewSolutions": "建立新的解決方案",
@ -1100,6 +1099,7 @@
"WatchTakeCodeGeneration": "观看“探索代码生成的潜力:ABP Suite”视频!",
"ExtendNow": "延长 / 续订",
"PreBuiltApplicationModulesTitle": "预构建应用程序 <span class=\"gradient-commercial\">模块</span>",
"RegisterDemo": "登记"
"RegisterDemo": "登记",
"AbpStudio_ComingSoon": "如果您想先于其他人测试 ABP Studio,请成为 BETA 测试员"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/ar.json

@ -261,6 +261,7 @@
"VisitVideoCourseDescription": "إذا كنت ترغب في تعلم أساسيات إطار عمل برنامج ABP، فاطلع على دورات الفيديو الخاصة ببرنامج ABP Essentials.",
"VisitPage": "زر الصفحة",
"ConfirmEmailForPost": "لتتمكن من النشر، تحتاج إلى تأكيد بريدك الإلكتروني. انتقل إلى account.abp.io/Account/Manage وتحقق من بريدك الإلكتروني في علامة التبويب \"المعلومات الشخصية\".",
"DailyPostCreateLimitation": "لقد وصلت إلى الحد الأقصى اليومي لإنشاء المنشورات. يمكنك إنشاء مشاركة جديدة في {0}."
"DailyPostCreateLimitation": "لقد وصلت إلى الحد الأقصى اليومي لإنشاء المنشورات. يمكنك إنشاء مشاركة جديدة في {0}.",
"YourAccountDisabled": "تم تعطيل حساب المستخدم الخاص بك!"
}
}

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

@ -263,6 +263,7 @@
"VisitVideoCourseDescription": "If you want to learn the basics of the ABP Framework, check out the ABP Essentials Video courses.",
"EditProfile": "Edit Profile",
"ConfirmEmailForPost": "To be able to post, you need to confirm your email. Go to account.abp.io/Account/Manage and verify your email in the Personal Info tab.",
"DailyPostCreateLimitation": "You have reached the daily post creation limit. You can create a new post in {0}."
"DailyPostCreateLimitation": "You have reached the daily post creation limit. You can create a new post in {0}.",
"YourAccountDisabled": "Your user account is disabled!"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json

@ -246,6 +246,7 @@
"Cancel": "Vazgeç",
"Continue": "Devam",
"ConfirmEmailForPost": "Gönderi paylaşabilmek için e-posta adresinizi onaylamanız gerekir. account.abp.io/Account/Manage adresine gidin ve Kişisel Bilgiler sekmesinden e-posta adresinizi doğrulayın.",
"DailyPostCreateLimitation": "Günlük gönderi paylaşma sınırına ulaştınız. {0}'da yeni bir gönderi paylaşabilirsiniz."
"DailyPostCreateLimitation": "Günlük gönderi paylaşma sınırına ulaştınız. {0}'da yeni bir gönderi paylaşabilirsiniz.",
"YourAccountDisabled": "Kullanıcı hesabınız devre dışı bırakıldı!"
}
}

37
docs/en/API/API-Versioning.md

@ -8,11 +8,10 @@ ABP Framework integrates the [ASPNET-API-Versioning](https://github.com/dotnet/a
```cs
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Show neutral/versionless APIs.
context.Services.AddTransient<IApiControllerFilter, NoControllerFilter>();
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});
@ -239,11 +238,10 @@ public override void ConfigureServices(ServiceConfigurationContext context)
preActions.Configure(options);
});
// Show neutral/versionless APIs.
context.Services.AddTransient<IApiControllerFilter, NoControllerFilter>();
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
@ -263,27 +261,22 @@ public override void ConfigureServices(ServiceConfigurationContext context)
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Show neutral/versionless APIs.
context.Services.AddTransient<IApiControllerFilter, NoControllerFilter>();
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
}).AddApiExplorer(options => {
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
context.Services.AddVersionedApiExplorer(
options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
context.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
context.Services.AddAbpSwaggerGen(
@ -322,7 +315,7 @@ public override void OnApplicationInitialization(ApplicationInitializationContex
app.UseAbpRequestLocalization();
app.UseSwagger();
app.UseSwaggerUI(
app.UseAbpSwaggerUI(
options =>
{
var provider = app.ApplicationServices.GetRequiredService<IApiVersionDescriptionProvider>();

11
docs/en/API/Application-Configuration.md

@ -55,7 +55,16 @@ namespace Acme.BookStore.Web
}
```
Add your contributor instance to the `AbpApplicationConfigurationOptions`
```csharp
Configure<AbpApplicationConfigurationOptions>(options =>
{
options.Contributors.AddIfNotContains(new MyApplicationConfigurationContributor());
});
```
* `IApplicationConfigurationContributor` defines the `ContributeAsync` method to extend the **application-configuration** endpoint with the specified additional data.
* You can inject services and perform any logic needed to extend the endpoint as you wish.
> Application configuration contributors are automatically discovered by the ABP Framework and executed as a part of the application configuration initialization process.
> Application configuration contributors are executed as a part of the application configuration initialization process.

269
docs/en/Blog-Posts/2024-05-21 v8_2_Release/POST.md

@ -0,0 +1,269 @@
# ABP.IO Platform 8.2 RC Has Been Released
Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **8.2 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 v8.2! Thanks to all of you.
## Get Started with the 8.2 RC
Follow the steps below to try version 8.2.0 RC today:
1) **Upgrade** the ABP CLI to version `8.2.0-rc.3` using a command line terminal:
````bash
dotnet tool update Volo.Abp.Cli -g --version 8.2.0-rc.3
````
**or install** it if you haven't before:
````bash
dotnet tool install Volo.Abp.Cli -g --version 8.2.0-rc.3
````
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 8.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/).
## Migration Guides
There are a few breaking changes in this version that may affect your application.
Please see the following migration documents, if you are upgrading from v8.x or earlier:
* [ABP Framework 8.x to 8.2 Migration Guide](https://docs.abp.io/en/abp/8.2/Migration-Guides/Abp-8_2)
* [ABP Commercial 8.x to 8.2 Migration Guide](https://docs.abp.io/en/commercial/8.2/migration-guides/v8_2)
## What's New with ABP Framework 8.2?
In this section, I will introduce some major features released in this version.
Here is a brief list of titles explained in the next sections:
* Blazor Full-Stack Web App UI
* Introducing the `IBlockUiService` for Blazor UI
* Allowing Case-Insensitive Indexes for MongoDB
* Other News...
### Blazor Full-Stack Web App UI
ASP.NET Blazor in .NET 8 allows you to use a single powerful component model to handle all of your web UI needs, including server-side rendering, client-side rendering, streaming rendering, progressive enhancement, and much more!
ABP v8.2.x supports the new Blazor Web App template, which you can directly create with the following command:
```bash
abp new BookStore -t app -u blazor-webapp
```
When you create the project, you will typically see two main projects for Blazor UI, besides other projects:
* **MyCompanyName.MyProjectName.Blazor.WebApp** (startup project of your application, and contains `App.razor` component, which is the root component of your application)
* **MyCompanyName.MyProjectName.Blazor.WebApp.Client**
This new template overcomes the disadvantages of both Blazor WASM and Blazor Server applications and allows you to decide which approaches to use for a specific page or component. Therefore, you can imagine this new web UI as a combination of both Blazor Server and Blazor WASM.
> This approach mainly overcomes the **large binary downloads of Blazor WASM**, and it resolves the Blazor Server's problem, which **always needs to be connected to the server via SignalR**.
> If you are considering migrating your existing Blazor project to Blazor WebApp or want to learn more about this new template, please read the [Migrating to Blazor Web App](https://docs.abp.io/en/abp/8.2/Migration-Guides/Abp-8-2-Blazor-Web-App) guide.
### Introducing the `IBlockUiService` for Blazor UI
In this version, ABP Framework introduces the [`IBlockUiService`](https://docs.abp.io/en/abp/8.2/UI/Blazor/Block-Busy) for Blazor UI. This service uses UI Block API to disable/block the page or a part of the page.
You just need to simply inject the `IBlockUiService` to your page or component and call the `Block` or `UnBlock` method to block/unblock the specified element:
```csharp
namespace MyProject.Blazor.Pages
{
public partial class Index
{
private readonly IBlockUiService _blockUiService;
public Index(IBlockUiService _blockUiService)
{
_blockUiService = blockUiService;
}
public async Task BlockForm()
{
/*
Parameters of Block method:
selectors: A string containing one or more selectors to match. https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#selectors
busy : Set to true to show a progress indicator on the blocked area.
*/
await _blockUiService.Block(selectors: "#MySelectors", busy: true);
//Unblocking the element
await _blockUiService.UnBlock(selectors: "#MySelectors");
}
}
}
```
Here is the resulting UI with all possible options (**block**, **block busy**, and **unblock**):
![](block-ui-service-blazor.gif)
### Allowing Case-Insensitive Indexes for MongoDB
MongoDB allows case-insensitive string comparisons by using case-insensitive indexes. You can create a case-insensitive index by specifying a **collation**.
To do that, you should override the `CreateModal` method, configure the `CreateCollectionOptions`, and specify the **collation** as below:
```csharp
protected override void CreateModel(IMongoModelBuilder modelBuilder)
{
base.CreateModel(modelBuilder);
modelBuilder.Entity<Question>(b =>
{
b.CreateCollectionOptions.Collation = new Collation(locale:"en_US", strength: CollationStrength.Secondary);
b.ConfigureIndexes(indexes =>
{
indexes.CreateOne(
new CreateIndexModel<BsonDocument>(
Builders<BsonDocument>.IndexKeys.Ascending("MyProperty"),
new CreateIndexOptions { Unique = true }
)
);
}
);
});
}
```
After this configuration, a unique index will be created for the `MyProperty` property and then you can perform case-insensitive string comparisons without the need to worry. See [#19073](https://github.com/abpframework/abp/pull/19073) for more information.
### Other News
* Angular package version has been updated to v17.3.0. See [#19915](https://github.com/abpframework/abp/pull/19915) for more info.
* OpenIddict [5.4.0 has been released on March 26](https://github.com/openiddict/openiddict-core/releases/tag/5.4.0). Therefore, we decided to upgrade the OpenIddict packages to v5.4.0. See [#19427](https://github.com/abpframework/abp/issues/19427).
* AutoMapper [13.0.1 was released on February 8](https://github.com/AutoMapper/AutoMapper/releases/tag/v13.0.1) and in this version, we upgraded AutoMapper packages to v13.0.1. See [#19564](https://github.com/abpframework/abp/pull/19564/).
* See other completed tasks in this version: [https://github.com/abpframework/abp/releases?q=8.2.0-rc](https://github.com/abpframework/abp/releases?q=8.2.0-rc&expanded=true)
## What's New with ABP Commercial 8.2?
We've also worked on ABP Commercial to align the features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 8.2.
### Session Management
The [Session Management](https://docs.abp.io/en/commercial/8.2/modules/identity/session-management) feature allows you to prevent concurrent login and manage user sessions. You can allow concurrent login, allow only one session of the same device type, or logout from all other devices when a new session is created, by specifying in the settings page:
![](concurrent-login-settings.png)
Also, you can view and manage users sessions on the `Users` page of the [Identity Module](https://docs.abp.io/en/commercial/8.2/modules/identity):
![](manage-user-sessions-1.png)
![](manage-user-sessions-2.png)
### Suite: File/Image Property
In this version, ABP Suite allows you to add a file/image property for an entity. You can select "File" as the property type for your properties as in the following figure:
![](suite-file-property.png)
Then, when you generate your entity and try to insert a record, you will see the file upload component on the create/update models:
![](suite-file-property-create.png)
You can upload a file with any supported extensions and under 10MB (this can be increased in the generated code, if you wish) and after that, you can download, delete and update the existing file any time you want:
![](suite-file-upload-in-action.gif)
> **Note:** This feature has already been implemented for MVC & Blazor UIs, but not implemented for Angular UI yet. We aim to implement it for Angular UI with v8.2.0.
### Suite: DateOnly & TimeOnly Types
In this version on, ABP Suite provides `DateOnly` and `TimeOnly` types as property types. You can select these types when you create an entity:
![](suite-dateonly-timeonly-properties.png)
Then, all related configurations (including db configurations) will be made by ABP Suite, and you will be able to see the fields in the UI:
![](suite-dateonly-timeonly-ui.png)
> **Note**: The `DateOnly` and `TimeOnly` types were introduced with .NET 6. Therefore, please make sure that all of your projects' target frameworks are .NET8+. With ABP v8.2, all startup templates target a single target framework, which is .NET8, so if you created your project with version 8.2+, you don't need to make any changes.
### Periodic Log Deletion for Audit Logs
In this version, the [Audit Logging Module](https://docs.abp.io/en/commercial/8.2/modules/audit-logging) provides a built-in periodic log deletion system. You can enable/disable the clean-up service system wide, in this way, you can turn off the clean up service for all tenants and their hosts:
![](audit-logging-module-global-settings.png)
> If the system wide clean up service is enabled, you can configure the global *Expired Item Deletion Period* for all tenants and hosts.
When configuring the global settings for the audit log module from the host side in this manner, ensure that each tenant and host uses the global values. If you want to set tenant/host-specific values, you can do so under *Settings* -> *Audit Log* -> *General*. This way, you can disable the clean up service for specific tenants or host. It overrides the global settings:
![](audit-logging-module-general-settings.png)
> **Note**: To view the audit log settings, you need to enable the feature. For the host side, navigate to *Settings* -> *Feature Management* -> *Manage Host Features* -> *Audit Logging* -> *Enable audit log setting management*.
## Community News
### ABP Dotnet Conf 2024 Wrap Up
![](abp-dotnet-conf-2024.png)
We organized [ABP Dotnet Conference 2024](https://abp.io/conference/2024) on May 2024 and we are happy to share the success of the conference, which captivated overwhelmingly interested live viewers from all over the world. 29 great line up of speakers which includes .NET experts and Microsoft MVPs delivered captivating talks that resonated with the audiences. Each of the talks attracted a great amount of interest and a lot of questions, sparking curiosity in the attendees.
Thanks to all speakers and attendees for joining our event. 🙏
> We shared our takeaways in a blog post, which you can read at [https://blog.abp.io/abp/ABP-Dotnet-Conference-2024-Wrap-Up](https://blog.abp.io/abp/ABP-Dotnet-Conference-2024-Wrap-Up).
### DevDays Europe 2024
![](devdays-europe.jpg)
Co-founder of [Volosoft](https://volosoft.com/), [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) gave a speech about "How to Build a Multi-Tenant ASP.NET Core Application" at the [DevDays Europe 2024](https://devdays.lt/) on the 20th of May.
### DevOps Pro Europe 2024
![](devops-pro-europe.jpg)
We are thrilled to announce that the co-founder of [Volosoft](https://volosoft.com/) and Lead Developer of the [ABP Framework](https://abp.io/), [Halil Ibrahim Kalkan](https://x.com/hibrahimkalkan) gave a speech about "Building a Kubernetes Integrated Local Development Environment" in the [DevOps Pro Europe](https://devopspro.lt/) on the 24th of May.
### Devnot Dotnet Conference 2024
We are happy to announce that core team members of the [ABP Framework](https://abp.io/), [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) and [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) will give speeches at the [Devnot Dotnet Conference 2024](https://dotnet.devnot.com/) on 25th of May.
[Alper Ebiçoğlu](https://twitter.com/alperebicoglu) will talk about **"AspNet Core & Multitenancy"**:
![](devnot-dotnet-conference-alper-ebicoglu.png)
On the other hand, [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) will talk about **"Reactive Programming with .NET MAUI"**:
![](devnot-dotnet-conference-enis-necipoglu.png)
### New ABP Community Articles
There are exciting articles contributed by the ABP community as always. I will highlight some of them here:
* [Ahmed Tarek](https://twitter.com/AhmedTarekHasa1) has created **four** new community articles:
* [🤔 When Implementations Affect Abstractions ⁉️](https://community.abp.io/posts/-when-implementations-affect-abstractions--ekx1o5xn)
* [👍 Design Best Practices In .NET C# 👀](https://community.abp.io/posts/design-best-practices-in-.net-c--eg8q8xh0)
* [👍 Chain of Responsibility Design Pattern In .NET C# 👀](https://community.abp.io/posts/chain-of-responsibility-design-pattern-in-.net-c--djmvkug1)
* [Flagged Enumerations: How To Represent Features Combinations Into One Field](https://community.abp.io/posts/flagged-enumerations-how-to-represent-features-combinations-into-one-field-9gj4l670)
* [Engincan Veske](https://github.com/EngincanV) has created **three** new community articles:
* [Performing Case-Insensitive Search in ABP Based-PostgreSQL Application: Using citext and Collation](https://community.abp.io/posts/caseinsensitive-search-in-abp-basedpostgresql-application-c9kb05dc)
* [Sentiment Analysis Within ABP-Based Application](https://community.abp.io/posts/sentiment-analysis-within-abpbased-application-lbsfkoxq)
* [Reusing and Optimizing Machine Learning Models in .NET](https://community.abp.io/posts/reusing-and-optimizing-machine-learning-models-in-.net-qj4ycnwu)
* [Unlocking Modularity in ABP.io A Closer Look at the Contributor Pattern](https://community.abp.io/posts/unlocking-modularity-in-abp.io-a-closer-look-at-the-contributor-pattern-ixf6wgbw) by [Qais Al khateeb](https://community.abp.io/members/qais.alkhateeb@devnas-jo.com)
* [Deploy Your ABP Framework MVC Project to Azure Container Apps](https://community.abp.io/posts/deploy-your-abp-framework-mvc-project-to-azure-container-apps-r93u9c6d) by [Selman Koç](https://community.abp.io/members/selmankoc)
* [How claim type works in ASP NET Core and ABP Framework](https://community.abp.io/posts/how-claim-type-works-in-asp-net-core-and-abp-framework-km5dw6g1) by [Liming Ma](https://github.com/maliming)
* [Using FluentValidation with ABP Framework](https://community.abp.io/posts/using-fluentvalidation-with-abp-framework-2cxuwl70) by [Enes Döner](https://community.abp.io/members/Enes)
* [Using Blob Storage with ABP](https://community.abp.io/posts/using-blob-storage-with-abp-framework-jygtmhn4) by [Emre Kendirli](https://community.abp.io/members/emrekenderli)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/submit) to the ABP Community.
## Conclusion
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/8.2/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v8.2 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/2024-05-21 v8_2_Release/abp-dotnet-conf-2024.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/audit-logging-module-general-settings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/audit-logging-module-global-settings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/block-ui-service-blazor.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/concurrent-login-settings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/devdays-europe.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/devnot-dotnet-conference-alper-ebicoglu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/devnot-dotnet-conference-enis-necipoglu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/devops-pro-europe.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/manage-user-sessions-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/manage-user-sessions-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-dateonly-timeonly-properties.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-dateonly-timeonly-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-file-property-create.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-file-property.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/en/Blog-Posts/2024-05-21 v8_2_Release/suite-file-upload-in-action.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

3
docs/en/Community-Articles/2024-05-07-Azure-Container-Apps-Deployment-with-ABP/POST.md

@ -40,9 +40,10 @@ Secondly, create a new Azure Container Apps Job resource to migrate the database
ConnectionStrings__Default - Server=tcp:demoabpapp.database.windows.net,1433;Initial Catalog=mvcapppro;Persist Security Info=False;User ID=demoapppro;Password={your_password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
OpenIddict__Applications__mvcapppro_Web__RootUrl - https://mvcwebapp.victoriousgrass-8b06438d.northeurope.azurecontainerapps.io
```
To get ConnectionStrings of Sql Database and url of the web app, you can navigate to the Azure portal and check the properties of the Azure SQL database and Azure Container Apps resource.
```
![Azure SQL Database Connection Strings](azure-sql-database-connection-strings.png)

BIN
docs/en/Community-Articles/2024-05-09-what-is-object-to-object-mapping/images/automapper.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
docs/en/Community-Articles/2024-05-09-what-is-object-to-object-mapping/images/swagger1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Community-Articles/2024-05-09-what-is-object-to-object-mapping/images/swagger2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

134
docs/en/Community-Articles/2024-05-09-what-is-object-to-object-mapping/post.md

@ -0,0 +1,134 @@
# What is Object to Object Mapping?
Mapping the properties of one object to the properties of another object is called **Object to Object Mapping**. Most of the time, you don't want to show the data you store in your database to end users as it is. Instead, you only return users the information they need for that operation and reduce the output size.
For example, in database tables that contain relationships, we analyze the relationships and present meaningful data to users. Suppose we have a product and a category object, we keep a property called `categoryId` in the `Product` entity. However, it would be illogical to show the `categoryId ` property to users. Therefore, we can create a DTO (data transfer object) and show the **category name** to the end users, instead of the `categoryId` directly.
DTOs are used to transfer data of objects from one object to another one. We often need to map our entities to DTOs and DTOs to entities. For example, consider the code below:
````csharp
public virtual async Task<CustomerDto> CreateAsync(CustomerCreateDto input)
{
var customer = await _customerManager.CreateAsync(
input.BirthDay, input.MembershipDate, input.FirstName, input.LastName
);
CustomerDto customerDto = new CustomerDto
{
Id = customer.Id,
FirstName = input.FirstName,
LastName = input.LastName,
// ...other
};
return customerDto;
}
````
As can be seen here, it's repetitive and tedious to manually map an object to another similar object. Also, it violates the [DRY principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), makes your code more complicated and reduces readability. Instead of manually mapping objects, you can use the [AutoMapper](https://automapper.org/) library to automatically map two objects' properties:
````csharp
public virtual async Task<CustomerDto> CreateAsync(CustomerCreateDto input)
{
var customer = await _customerManager.CreateAsync(
input.BirthDay, input.MembershipDate, input.FirstName, input.LastName
);
return ObjectMapper.Map<Customer, CustomerDto>(customer);
}
````
The **ObjectMapper.Map** method allows you to convert your `Customer` entity to `CustomerDto`. `IObjectMapper` interface is a service, that comes from the **AutoMapper** library, so let's learn more about **AutoMapper** in the next section.
# What is AutoMapper?
Automapper is a .NET library that automates object-to-object mapping. ABP provides abstractions for object-to-object mapping and has an integration package to use [AutoMapper](http://automapper.org/) as the object mapper.
Automapper is a library that transforms similar objects into each other. We can imagine Automapper as a machine that transforms an apple with a hat into an apple without a hat:
![AutoMapper](./images/automapper.png)
In this chapter, I will show you how to use the AutoMapper library in an ABP-based application. For this reason, I assume that you already have an ABP-based application created. If you have not yet created an ABP-based application, please follow the [Getting Started documentation](https://docs.abp.io/en/abp/latest/Getting-Started-Create-Solution?UI=MVC&DB=EF&Tiered=No).
Create a domain entity similar to this one:
````csharp
public class Customer : FullAuditedAggregateRoot<Guid>
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateTime BirthDay { get; set; }
public DateTime MembershipDate { get; set; }
}
````
`Customer` entity contains some properties (such as `FirstName`, `LastName`, ... and other audited properties coming from the base class - `DeleterId`, `IsDeleted`, `CreationTime` etc. -). Typically, you would not want to show/return all of these properties to end users, at that point, you can create a DTO class and only define the properties that you want to return to the end users.
Let's create the `CustomerGetDto` class in the `*.Application.Contracts` project as follows:
````csharp
public class CustomerGetDto
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateTime BirthDay { get; set; }
}
````
After creating our entity and output DTO classes, now in the application service implementation, we can return the `CustomerGetDto` class, as a result of listing the customers. For that reason, we can write a code as follows:
````csharp
public virtual async Task<PagedResultDto<CustomerGetDto>> GetListAsync(GetCustomersInput input)
{
var totalCount = await _customerRepository.GetCountAsync(input.FilterText, input.FirstName, input.LastName, input.BirthDayMin, input.BirthDayMax, input.MembershipDateMin, input.MembershipDateMax);
var items = await _customerRepository.GetListAsync(input.FilterText, input.FirstName, input.LastName, input.BirthDayMin, input.BirthDayMax, input.MembershipDateMin, input.MembershipDateMax, input.Sorting, input.MaxResultCount, input.SkipCount);
return new PagedResultDto<CustomerGetDto>
{
TotalCount = totalCount,
Items = ObjectMapper.Map<List<Customer>, List<CustomerGetDto>>(items)
};
}
````
In this code, we first get the total number of our customers and all customers according to the specified filters, then we map `List<Customer>` to `List<CustomerGetDto>` using the `ObjectMapper.Map` method from the **ApplicationService** base class. This way we have full control over which properties are returned to the end users.
After mapping the `Customer` entity to the `CustomerGetDto` class. Before running our application, we should define the mappings in the `*AutoMapperProfile` class in the `*.Application` project as follows:
````csharp
public class YourApplicationAutoMapperProfile : Profile
{
public YourApplicationAutoMapperProfile()
{
CreateMap<Customer, CustomerGetDto>();
}
}
````
Finally, we can run our application and navigate to the **/swagger** endpoint to try our endpoint. When we send a request, we should get the result as follows:
![Swagger](./images/swagger1.png)
## Advanced: Mapping Configurations
In some scenarios, you may want to make some customizations when mapping two objects. For example, let's assume that you want to create the `CustomerGetDto` class as follows:
````csharp
public class CustomerGetDto
{
public string? FullName { get; set; }
public int Age { get; set; }
}
````
AutoMapper can't map these properties automatically, because they do not exist in the source object, which is the `Customer` entity. Therefore, you need to specify the exception and update your `YourApplicationAutoMapperProfile` class as follows:
````csharp
CreateMap<Customer, CustomerGetDto>().ForMember(c=>c.FullName,opt=> opt.MapFrom(src => src.FirstName + " " + src.LastName))
.ForMember(c=>c.Age, opt=> opt.MapFrom(src=> DateTime.UtcNow.Year -src.BirthDay.Year));
````
This configuration concatenates and matches **FirstName** and **LastName** properties into the **FullName** property and subtracts **BirthDate** from today's year and assigns it to the customer's **Age**.
After these configurations, if you make a request to the relevant endpoint, the output will look like:
![Swagger](./images/swagger2.png)
For more information on object-to-object mapping with [ABP Framework](https://abp.io/), see the [documentation](https://docs.abp.io/en/abp/latest/Object-To-Object-Mapping).

8
docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md

@ -325,12 +325,16 @@ public class SentimentAnalyzeResult
}
```
Then, finally we can run the application to see the final results:
Then, finally, we can run the application to see the final results:
![](demo.gif)
## Advanced: Reusing And Optimizing Machine Learning Models
Once the model is trained and evaluated, we can save the trained model and use it directly for further use. In this way, you don’t have to retrain the model every time when you want to make predictions. It’s essential to save the trained model for future use and a must for the production-ready code. I created a separate article dedicated to that topic, and if you are interested, you can read it from [here](https://engincanv.github.io/machine-learning/sentiment-analysis/best-practises/2024/05/16/reusing-and-optimizing-machine-learning-models-in-dotnet.html).
## Conclusion
In this article, I briefly explain what sentiment analysis is, created a sample ABP-based application, integrated the CMS Kit Module and finally, applied sentiment analysis to make spam checks whenever a new comment has been submitted or updated. You can get the source code of the demo from [https://github.com/EngincanV/SentimentAnalysisDemo](https://github.com/EngincanV/SentimentAnalysisDemo)
Thanks for reading :)
Thanks for reading :)

228
docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/POST.md

@ -0,0 +1,228 @@
# Deploy Your ABP Framework Angular Project to Azure Kubernetes Service (AKS)
![ABP Framework Angular Project](abp-angular-aks-helm.png)
In my previous article on [Deploy Your ABP Framework MVC Project to Azure Container Apps](https://community.abp.io/posts/deploy-your-abp-framework-mvc-project-to-azure-container-apps-r93u9c6d), I talked about how ABP Mvc project can be easily deployed to Azure Container Apps. Now I will show how we can deploy to kubernetes environment, which looks a bit more complex but is more preferred for production, using a Helm chart.
### Getting Started with ABP Framework Angular and Azure Kubernetes Service
To get started, you will need an ABP Framework Angular project that you want to deploy. If you don't have one, you can [create a new project using the ABP CLI](https://docs.abp.io/en/abp/latest/Startup-Templates/Application). You will also need [an Azure subscription](https://azure.microsoft.com) and [an Azure Kubernetes Service](https://azure.microsoft.com/en-gb/services/kubernetes-service/).
### Configuring Your ABP Framework Angular Project
We have a sample ABP Framework Angular project that we will use for this deployment. Before creating the Docker image and Helm chart, you just need to configure `aspnet-core/src/***.HttpApi.Host/***.HttpApiHostModule.cs` file to allow CORS requests from your frontend application. You can do this by updating the following code to the `ConfigureServices` method:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (!configuration.GetValue<bool>("App:DisablePII"))
{
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}
if (!configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"))
{
Configure<OpenIddictServerAspNetCoreOptions>(options =>
{
options.DisableTransportSecurityRequirement = true;
});
}
context.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForw
```
![Configure CORS](configure-cors.png)
If your ABP Framework Angular project and Azure kubernetes cluster ready, we can start to build the docker images and pushing them to any container registry. In this article, I will use DockerHub as the container registry.
I will also show you how I automated the steps that I originally did manually to make it simpler in the beginning and then automated them in Azure Devops.
### Creating a Docker Image for ABP Framework Angular
To create a Docker image for your ABP Framework Angular project, navigate to `etc/build/build-images-locally.ps1` and fix the script to match your Docker Hub username and image name. Then, run the script to build the Docker image locally.
![Build Docker Image](build-docker-image.png)
At the end of this process, check your Docker Hub repository to confirm that the image has been pushed successfully. My Docker Hub repository looks like this. Also you can use these my public images to test the deployment.
![Docker Hub Repository](docker-hub-repository.png)
### Creating Helm Chart for ABP Framework Angular
To deploy your ABP Framework Angular project to Azure Kubernetes Service, you need to create a Helm chart. Helm is a package manager for Kubernetes that allows you to define, install, and upgrade even the most complex Kubernetes applications. Helm uses a packaging format called charts, which are a collection of files that describe a related set of Kubernetes resources.
These helm charts are prepared to create a deployment, configmap, service and ingress for your ABP Framework Angular project. It is prepared not only for migration, frontend and backend, but also to create the sqlserver and redis that the application needs as a kubernetes service. If you already have redis and sqlserver, you don't need to stand them up in kubernetes, of course.
![Helm Chart](helm-chart.png)
You can find the helm chart in the https://github.com/skoc10/k8s_works/tree/main/demo/helm/k8s/angular repository. You can configure the `values-azure.yaml` file according to your needs.
### Deploying to Azure Kubernetes Service
`Note:` You need to have nginx-ingress-controller and cert-manager installed for letsencrypt certificate in your kubernetes cluster.
Now that you have Docker images for your ABP Framework Angular project and a Helm chart, you can proceed to deploy it to Azure Kubernetes Service. To do this, you need to create a new Azure Kubernetes Service resource and configure the `values-azure.yaml` file according to your needs. If you want, you can deploy a single Helm with `demo/helm/k8s/deploy-staging.ps1` script you can deploy each chart separately.
![Create Azure Kubernetes Service](create-azure-kubernetes-service.png)
After deploying the Helm chart, you can check the deployment status to confirm that the deployment was successful. You can also check the logs of the pods to see if there are any errors.
![Deploy Helm Chart](deploy-helm-chart.png)
Finally, you can navigate to the web url to see your ABP Framework Angular project running in Azure Kubernetes Service.
![ABP Framework Angular Project](abp-angular-aks.png)
### CI/CD with Azure DevOps
I have automated the steps that I originally did manually to make it simpler in the beginning and then automated them in Azure Devops. You can find the `azure-pipelines.yml` file in the https://github.com/skoc10/k8s_works/blob/main/demo/azure/azure-pipelines.yml repository. You can configure the `azure-pipelines.yml` file according to your needs.
```yaml
trigger:
tags:
include:
- "*.*.*"
variables:
dockerRegistryServiceConnection: 'demo-reg'
buildContextBasePath: '$(Build.SourcesDirectory)'
tag: $(Build.BuildNumber)
DOCKER_BUILDKIT: 1
pool:
name: ubuntu
stages:
- stage: Build
displayName: Build
jobs:
- job: CheckChanges
displayName: Check if Angular or ASP.NET Core has changed
pool:
name: ubuntu
steps:
- checkout: self
# Migration
- task: Docker@2
displayName: 'Build Migration Docker image'
inputs:
command: build
repository: demo-angular-apppro/migration
dockerfile: $(buildContextBasePath)/aspnet-core/src/Demo.AzureAppsAngular.DbMigrator/Dockerfile.azure
buildContext: $(buildContextBasePath)/aspnet-core
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
- task: Docker@2
displayName: 'Push Migration Docker image'
inputs:
command: push
repository: demo-angular-apppro/migration
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
- task: HelmDeploy@0
displayName: 'Delete Migrator'
inputs:
connectionType: Kubernetes Service Connection
kubernetesServiceConnection: 'aks-demoms'
namespace: 'angular'
command: delete
arguments: dbmigrator
continueOnError: true
- task: HelmDeploy@0
displayName: 'Deploy Migration to AKS'
inputs:
connectionType: Kubernetes Service Connection
kubernetesServiceConnection: 'aks-demoms'
namespace: 'angular'
command: 'upgrade'
chartType: 'FilePath'
chartPath: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/dbmigrator'
releaseName: 'dbmigrator'
overrideValues: 'image.tag=$(tag)'
valueFile: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/dbmigrator/values.yaml'
waitForExecution: false
# Backend
- task: Docker@2
displayName: 'Build Backend Docker image'
inputs:
command: build
repository: demo-angular-apppro/backend
dockerfile: $(buildContextBasePath)/aspnet-core/src/Demo.AzureAppsAngular.HttpApi.Host/Dockerfile.azure
buildContext: $(buildContextBasePath)/aspnet-core
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
- task: Docker@2
displayName: 'Push Backend Docker image'
inputs:
command: push
repository: demo-angular-apppro/backend
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
- task: HelmDeploy@0
displayName: 'Deploy Backend to AKS'
inputs:
connectionType: Kubernetes Service Connection
kubernetesServiceConnection: 'aks-demoms'
namespace: 'angular'
command: 'upgrade'
chartType: 'FilePath'
chartPath: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/backend'
releaseName: 'backend'
overrideValues: 'image.tag=$(tag)'
valueFile: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/backend/values.yaml'
waitForExecution: false
# Frontend
- task: Docker@2
displayName: 'Build Frontend Docker image'
inputs:
command: build
repository: demo-angular-apppro/frontend
dockerfile: $(buildContextBasePath)/angular/Dockerfile.azure
buildContext: $(buildContextBasePath)/angular
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
- task: Docker@2
displayName: 'Push Frontend Docker image'
inputs:
command: push
repository: demo-angular-apppro/frontend
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
- task: HelmDeploy@0
displayName: 'Deploy Frontend to AKS'
inputs:
connectionType: Kubernetes Service Connection
kubernetesServiceConnection: 'aks-demoms'
namespace: 'angular'
command: 'upgrade'
chartType: 'FilePath'
chartPath: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/angular'
releaseName: 'frontend'
overrideValues: 'image.tag=$(tag)'
valueFile: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/angular/values.yaml'
waitForExecution: false
```
### Conclusion
In this article, I showed you how you can deploy your ABP Framework Angular project to Azure Kubernetes Service using Helm chart. I also showed you how you can automate the deployment process using Azure DevOps. I hope you found this article helpful. If you have any questions or feedback, please feel free to leave a comment below.

BIN
docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks-helm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

BIN
docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/build-docker-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/configure-cors.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/create-azure-kubernetes-service.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/deploy-helm-chart.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

BIN
docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/docker-hub-repository.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/helm-chart.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

4
docs/en/Getting-Started-React-Native.md

@ -1,9 +1,9 @@
```json
````json
//[doc-params]
{
"Tiered": ["No", "Yes"]
}
```
````
# Getting Started with the React Native

61
docs/en/Migration-Guides/Abp-8_2.md

@ -0,0 +1,61 @@
# ABP Version 8.2 Migration Guide
This document is a guide for upgrading ABP v8.x solutions to ABP v8.2. There are some changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application.
## Updated target frameworks to net8.0 for template projects
`TargetFrameworks` of the following template projects are upgraded to **net8.0**:
* `*.Application.Contracts`
* `*.Domain.Shared`
* `*.Domain`
* `*.MongoDB`
* `*.HttpApi.Client`
* `*.Host.Shared` (for module template)
Before this version, all of the projects above were targeting multiple frameworks (**netstandard2.0**, **netstandard2.1** and **net8.0**), with this version, we started to only target **net8.0** for these template projects. Note that, all other shared libraries still target multiple frameworks.
> This change should not affect your pre-existing solutions and you don't need to make any changes in your application. See the PR for more info: https://github.com/abpframework/abp/pull/19565
## Upgraded AutoMapper to 13.0.1
In this version, **AutoMapper** library version upgraded to 13.0.1. See [the release notes of AutoMapper v13.0.1](https://github.com/AutoMapper/AutoMapper/releases/tag/v13.0.1) for more information.
## Added default padding to `.tab-content` class for Basic Theme
In this version, default padding (padding-top: 1.5rem and padding-bottom: 1.5rem) has been added to the tab-content for the Basic Theme. See [#19475](https://github.com/abpframework/abp/pull/19475) for more information.
## Moved members page directory for Blogging Module
With this version on, ABP Framework allows you to use single blog mode, without needing to define a blog and a prefix. With this change, the following namespace changes were done:
* `Volo.Blogging.Pages.Blog` -> `Volo.Blogging.Pages.Blogs`
* `Volo.Blogging.Pages.Members` -> `Volo.Blogging.Pages.Blogs.Members` (members folder)
> If you haven't overridden the pages above, then you don't need to make any additional changes. See [#19418](https://github.com/abpframework/abp/pull/19418) for more information.
## Removed `FlagIcon` property from the `ILanguageInfo`
The `FlagIcon` property has been removed from the `ILanguageInfo` interface since we removed the flag icon library in the earlier versions from all of our themes and none of them using it now.
If the flag icon has been specified while defining the localization languages, then it should be removed:
```diff
Configure<AbpLocalizationOptions>(options =>
{
- options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi", "in"));
+ options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi"));
//...
}
```
## Blazor Full-Stack Web UI
In this version, ABP Framework provides a new UI option called **Blazor Full-Stack WebApp**. We have already created an introduction/migration guide for you to check it: [Migrating to Blazor Web App](Abp-8-2-Blazor-Web-App.md)
> Please read the documentation carefully if you are considering migrating your existing **Blazor** project to **Blazor WebApp**.
## Session Management Infrastructure
The **Session Management** feature allows you to prevent concurrent login and manage user sessions.
In this version, a new entity called `IdentitySession` has been added to the framework and you should create a new migration and apply it to your database.

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).
- [8.x to 8.2](Abp-8_2.md)
- [8.0 to 8.1](Abp-8_1.md)
- [7.x to 8.0](Abp-8_0.md)
- [7.3 to 7.4](Abp-7_4.md)

112
docs/en/Multi-Tenancy.md

@ -1,6 +1,6 @@
# Multi-Tenancy
Multi-Tenancy is a widely used architecture to create **SaaS applications** where the hardware and software **resources are shared by the customers** (tenants). ABP Framework provides all the base functionalities to create **multi tenant applications**.
Multi-Tenancy is a widely used architecture to create **SaaS applications** where the hardware and software **resources are shared by the customers** (tenants). ABP Framework provides all the base functionalities to create **multi tenant applications**.
Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) the multi-tenancy as like that:
@ -10,8 +10,8 @@ Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) the multi-tenanc
There are two main side of a typical SaaS / Multi-tenant application:
* A **Tenant** is a customer of the SaaS application that pays money to use the service.
* **Host** is the company that owns the SaaS application and manages the system.
- A **Tenant** is a customer of the SaaS application that pays money to use the service.
- **Host** is the company that owns the SaaS application and manages the system.
The Host and the Tenant terms will be used for that purpose in the rest of the document.
@ -36,9 +36,9 @@ Configure<AbpMultiTenancyOptions>(options =>
ABP Framework supports all the following approaches to store the tenant data in the database;
* **Single Database**: All tenants are stored in a single database.
* **Database per Tenant**: Every tenant has a separate, dedicated database to store the data related to that tenant.
* **Hybrid**: Some tenants share a single databases while some tenants may have their own databases.
- **Single Database**: All tenants are stored in a single database.
- **Database per Tenant**: Every tenant has a separate, dedicated database to store the data related to that tenant.
- **Hybrid**: Some tenants share a single database while some tenants may have their own databases.
[Tenant management module](Modules/Tenant-Management.md) (which comes pre-installed with the startup projects) allows you to set a connection string for any tenant (as optional), so you can achieve any of the approaches.
@ -48,11 +48,11 @@ Multi-tenancy system is designed to **work seamlessly** and make your applicatio
### IMultiTenant
You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**.
You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**.
**Example: A multi-tenant *Product* entity**
**Example: A multi-tenant _Product_ entity**
````csharp
```csharp
using System;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
@ -68,7 +68,7 @@ namespace MultiTenancyDemo.Products
public float Price { get; set; }
}
}
````
```
`IMultiTenant` interface just defines a `TenantId` property. When you implement this interface, ABP Framework **automatically** [filters](Data-Filtering.md) entities for the current tenant when you query from database. So, you don't need to manually add `TenantId` condition while performing queries. A tenant can not access to data of another tenant by default.
@ -96,9 +96,9 @@ If you set the `TenantId` value for a specific entity object, it will override t
`ICurrentTenant` defines the following properties;
* `Id` (`Guid`): Id of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
* `Name` (`string`): Name of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
* `IsAvailable` (`bool`): Returns `true` if the `Id` is not `null`.
- `Id` (`Guid`): Id of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
- `Name` (`string`): Name of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
- `IsAvailable` (`bool`): Returns `true` if the `Id` is not `null`.
#### Change the Current Tenant
@ -108,7 +108,7 @@ ABP Framework automatically filters the resources (database, cache...) based on
**Example: Get product count of a specific tenant**
````csharp
```csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
@ -134,11 +134,11 @@ namespace MultiTenancyDemo.Products
}
}
}
````
```
* `Change` method can be used in a **nested way**. It restores the `CurrentTenant.Id` to the previous value after the `using` statement.
* When you use `CurrentTenant.Id` inside the `Change` scope, you get the `tenantId` provided to the `Change` method. So, the repository also get this `tenantId` and can filter the database query accordingly.
* Use `CurrentTenant.Change(null)` to change scope to the host context.
- `Change` method can be used in a **nested way**. It restores the `CurrentTenant.Id` to the previous value after the `using` statement.
- When you use `CurrentTenant.Id` inside the `Change` scope, you get the `tenantId` provided to the `Change` method. So, the repository also get this `tenantId` and can filter the database query accordingly.
- Use `CurrentTenant.Change(null)` to change scope to the host context.
> Always use the `Change` method with a `using` statement like done in this example.
@ -148,7 +148,7 @@ As mentioned before, ABP Framework handles data isolation between tenants using
**Example: Get count of products in the database, including all the products of all the tenants.**
````csharp
```csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
@ -181,7 +181,7 @@ namespace MultiTenancyDemo.Products
}
}
````
```
See the [Data Filtering document](Data-Filtering.md) for more.
@ -201,15 +201,15 @@ ABP Framework provides an extensible **Tenant Resolving** system for that purpos
The following resolvers are provided and configured by default;
* `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**.
* `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default.
* `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route.
* `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default.
* `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default.
- `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**.
- `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default.
- `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route.
- `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default.
- `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default.
###### Problems with the NGINX
You may have problems with the `__tenant` in the HTTP Headers if you're using the [nginx](https://www.nginx.com/) as the reverse proxy server. Because it doesn't allow to use underscore and some other special characters in the HTTP headers and you may need to manually configure it. See the following documents please:
You may have problems with the `__tenant` in the HTTP Headers if you're using the [nginx](https://www.nginx.com/) as the reverse proxy server. Because it doesn't allow to use underscore and some other special characters in the HTTP headers and you may need to manually configure it. See the following documents please:
http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers
http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
@ -219,22 +219,24 @@ http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
**Example:**
````csharp
```csharp
services.Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
{
options.TenantKey = "MyTenantKey";
});
````
```
If you change the `TenantKey`, make sure to pass it to `CoreModule` in the Angular client as follows:
If you change the `TenantKey`, make sure to pass it to `provideAbpCore` via `withOptions` method in the Angular client as follows:
```js
@NgModule({
imports: [
CoreModule.forRoot({
// ...
tenantKey: 'MyTenantKey'
}),
providers: [
provideAbpCore(
withOptions({
// ...
tenantKey: "MyTenantKey",
})
),
],
// ...
})
@ -249,7 +251,7 @@ import { TENANT_KEY } from '@abp/ng.core';
class SomeComponent {
constructor(@Inject(TENANT_KEY) private tenantKey: string) {}
}
}
```
> However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then.
@ -277,30 +279,30 @@ In a real application, most of times you will want to determine the current tena
**Example: Add a subdomain resolver**
````csharp
```csharp
Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.mydomain.com");
});
````
```
* `{0}` is the placeholder to determine the current tenant's unique name.
* Add this code to the `ConfigureServices` method of your [module](Module-Development-Basics.md).
* This should be done in the *Web/API Layer* since the URL is a web related stuff.
- `{0}` is the placeholder to determine the current tenant's unique name.
- Add this code to the `ConfigureServices` method of your [module](Module-Development-Basics.md).
- This should be done in the _Web/API Layer_ since the URL is a web related stuff.
Openiddict is the default Auth Server in ABP (since v6.0). When you use OpenIddict, you must add this code to the `PreConfigure` method as well.
```csharp
// using Volo.Abp.OpenIddict.WildcardDomains
PreConfigure<AbpOpenIddictWildcardDomainOptions>(options =>
PreConfigure<AbpOpenIddictWildcardDomainOptions>(options =>
{
options.EnableWildcardDomainSupport = true;
options.WildcardDomainsFormat.Add("https://{0}.mydomain.com");
});
```
You must add this code to the `Configure` method as well.
You must add this code to the `Configure` method as well.
```csharp
// using Volo.Abp.MultiTenancy;
@ -312,7 +314,7 @@ Configure<AbpTenantResolveOptions>(options =>
```
> There is an [example](https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver) that uses the subdomain to determine the current tenant.
> There is an [example](https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver) that uses the subdomain to determine the current tenant.
If you use a sepereted Auth server, you must install `[Owl.TokenWildcardIssuerValidator](https://www.nuget.org/packages/Owl.TokenWildcardIssuerValidator)` on the `HTTPApi.Host` project
@ -332,7 +334,7 @@ context.Services
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
options.Audience = "ExampleProjectName";
// start of added block
options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator;
options.TokenValidationParameters.ValidIssuers = new[]
@ -348,16 +350,16 @@ context.Services
You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below:
````csharp
```csharp
Configure<AbpTenantResolveOptions>(options =>
{
options.TenantResolvers.Add(new MyCustomTenantResolveContributor());
});
````
```
`MyCustomTenantResolveContributor` must inherit from the `TenantResolveContributorBase` (or implement the `ITenantResolveContributor`) as shown below:
````csharp
```csharp
using System.Threading.Tasks;
using Volo.Abp.MultiTenancy;
@ -373,10 +375,10 @@ namespace MultiTenancyDemo.Web
}
}
}
````
```
* A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it.
* `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](Dependency-Injection.md) system.
- A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it.
- `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](Dependency-Injection.md) system.
#### Multi-Tenancy Middleware
@ -384,9 +386,9 @@ Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https:
Multi-Tenancy middleware is typically placed just under the [authentication](https://docs.microsoft.com/en-us/aspnet/core/security/authentication) middleware (`app.UseAuthentication()`):
````csharp
```csharp
app.UseMultiTenancy();
````
```
> This middleware is already configured in the startup templates, so you normally don't need to manually add it.
@ -406,7 +408,7 @@ The [tenant management module](Modules/Tenant-Management) is **included in the s
**Example: Define tenants in appsettings.json**
````json
```json
"Tenants": [
{
"Id": "446a5211-3d72-4339-9adc-845151f8ada0",
@ -422,7 +424,7 @@ The [tenant management module](Modules/Tenant-Management) is **included in the s
}
}
]
````
```
> It is recommended to **use the Tenant Management module**, which is already pre-configured when you create a new application with the ABP startup templates.
@ -440,4 +442,4 @@ The [Tenant Management module](Modules/Tenant-Management.md) provides a basic UI
## See Also
* [Features](Features.md)
- [Features](Features.md)

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

@ -31,20 +31,21 @@ yarn add bootstrap-icons
Note: You should remove the old theme styles from "angular.json" if you are switching from "ThemeBasic" or "Lepton."
Look at the [Theme Configurations](../../UI/Angular/Theme-Configurations) list of styles. Depending on your theme, you can alter your styles in angular.json.
- Finally, remove `ThemeBasicModule` from `app.module.ts`, and import the related modules in `app.module.ts`
- Finally, remove `ThemeBasicModule`, `provideThemeBasicConfig` from `app.module.ts`, and import the related modules in `app.module.ts`
```js
import { ThemeLeptonXModule } from "@abp/ng.theme.lepton-x";
import { SideMenuLayoutModule } from "@abp/ng.theme.lepton-x/layouts";
@NgModule({
imports: [
// ...
// do not forget to remove ThemeBasicModule or other old theme module
// ThemeBasicModule.forRoot(),
ThemeLeptonXModule.forRoot(),
SideMenuLayoutModule.forRoot(),
// ThemeBasicModule
ThemeLeptonXModule.forRoot()
],
providers: [
// do not forget to remove provideThemeBasicConfig or other old theme providers
// provideThemeBasicConfig
],
// ...
})

19
docs/en/UI/Angular/Account-Module.md

@ -7,7 +7,6 @@ If you add the account module to your project;
- "My account" link in the current user dropdown on the top bar will redirect the user to a page in the account module.
- You can switch the authentication flow to the resource owner password flow.
### Account Module Implementation
Install the `@abp/ng.account` NPM package by running the below command:
@ -18,18 +17,18 @@ npm install @abp/ng.account
> Make sure v4.3 or higher version is installed.
Open the `app.module.ts` and add `AccountConfigModule.forRoot()` to the imports array as shown below:
Open the `app.module.ts` and add `provideAccountConfig()` to the providers array as shown below:
```js
// app.module.ts
import { AccountConfigModule } from '@abp/ng.account/config';
import { provideAccountConfig } from "@abp/ng.account/config";
//...
@NgModule({
imports: [
providers: [
//...
AccountConfigModule.forRoot()
provideAccountConfig(),
],
//...
})
@ -57,19 +56,19 @@ The pro startup template comes with `@volo/abp.ng.account` package. You should u
```bash
npm install @volo/abp.ng.account
```
> Make sure v4.3 or higher version is installed.
Open the `app.module.ts` and add `AccountPublicConfigModule.forRoot()` to the imports array as shown below:
> Ensure that the `Account Layout Module` has been added if you are using the Lepton X theme. If you miss the step, you will get an error message that says `Account layout not found. Please check your configuration. If you are using LeptonX, please make sure you have added "AccountLayoutModule.forRoot()" to your app.module configuration.` when you try to access the account pages. Otherwise, you can skip adding the `AccountLayoutModule` step.
```js
// app.module.ts
import { AccountPublicConfigModule } from '@volo/abp.ng.account/public/config';
import { AccountPublicConfigModule } from "@volo/abp.ng.account/public/config";
// if you are using or want to use Lepton X, you should add AccountLayoutModule
// import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account'
// import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account'
//...
@ -104,9 +103,9 @@ Before v4.3, the "My account" link in the current user dropdown on the top bar r
### Personal Info Page Confirm Message
When the user changes their own data on the personal settings tab in My Account, The data can not update the CurrentUser key of Application-Configuration. The information of the user is stored in claims. The only way to apply this information to the CurrentUser of Application-Configuration is user should log out and log in. When the Refresh-Token feature is implemented, it will be fixed. So We've added a confirmation alert.
When the user changes their own data on the personal settings tab in My Account, The data can not update the CurrentUser key of Application-Configuration. The information of the user is stored in claims. The only way to apply this information to the CurrentUser of Application-Configuration is user should log out and log in. When the Refresh-Token feature is implemented, it will be fixed. So We've added a confirmation alert.
If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false
If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false
```js
// app-routing.module.ts

2
docs/en/UI/Angular/Basic-Theme.md

@ -11,7 +11,7 @@ The Basic Theme is a theme implementation for the Angular UI. It is a minimalist
If you need to manually this theme, follow the steps below:
* Install the [@abp/ng.theme.basic](https://www.npmjs.com/package/@abp/ng.theme.basic) NPM package to your Angular project.
* Open the `src/app/app.module.ts` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule.forRoot()` to the `imports` array.
* Open the `src/app/app.module.ts` file, import `ThemeBasicModule`,`provideThemeBasicConfig` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` array and provide `provideThemeBasicConfig()` to the providers array.
* Open the `src/app/shared/shared.module` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` and `exports` array.
The `ThemeBasicModule` is registered own layouts (`ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`) to a service which is exposed by `@abp/ng.core` package on application initialization.

117
docs/en/UI/Angular/Component-Replacement.md

@ -30,7 +30,6 @@ export class AppComponent {
![Example Usage](./images/component-replacement.gif)
## How to Replace a Layout
Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced the same way.
@ -88,11 +87,13 @@ This component should have a 'router-outlet' for dynamic content loading. You ca
```bash
ng generate component new-layout
```
This command will create a new component named `new-layout`. Now, open the new-layout.component.html file and add a `router-outlet` to it:
```html
<router-outlet></router-outlet>
<router-outlet></router-outlet>
```
This 'router-outlet' will act as a placeholder that Angular dynamically fills based on the current router state.
note: (don't forget: you should add the app in the app.module.ts file)
@ -103,10 +104,11 @@ Although this step is optional, it can be useful if you're going to use the layo
```javascript
export const eCustomLayout = {
key: 'CustomLayout',
component: 'CustomLayoutComponent',
key: "CustomLayout",
component: "CustomLayoutComponent",
};
```
In this variable, `key` is a unique identifier for the layout component, and `component` is the name of the layout component.
You can use this variable when you need to refer to the layout component.
@ -120,19 +122,24 @@ Here's how you can do it:
```javascript
export const CUSTOM_LAYOUT_PROVIDERS = [
{ provide: APP_INITIALIZER, useFactory: configureLayoutFn, deps: [ReplaceableComponentsService], multi: true },
{
provide: APP_INITIALIZER,
useFactory: configureLayoutFn,
deps: [ReplaceableComponentsService],
multi: true,
},
];
function configureLayoutFn() {
const service= inject( ReplaceableComponentsService)
return () =>{
service.add({
key: eCustomLayout.component,
component: CustomLayoutComponent,
})
}
const service = inject(ReplaceableComponentsService);
return () => {
service.add({
key: eCustomLayout.component,
component: CustomLayoutComponent,
});
};
}
```
In this code, `configureLayoutFn` is a factory function that adds the new layout component to the `ReplaceableComponentsService`. The `APP_INITIALIZER` provider runs this function when the application starts.
note: (don't forget: you should add the CUSTOM_LAYOUT_PROVIDERS in the app.module.ts file)
@ -149,34 +156,31 @@ export const myDynamicLayouts = new Map<string, string>([...DEFAULT_DYNAMIC_LAYO
#### Step 5: Pass the Dynamic Layouts to the CoreModule
The final step is to pass the dynamic layouts to the `CoreModule` using the `forRoot` method. This method allows you to configure the module with a static method.
The final step is to pass the dynamic layouts to the `provideAbpCore` using the `withOptions` method. This method allows you to configure the module with a static method.
Here's how you can do it:
```javascript
```ts
@NgModule({
declarations: [AppComponent],
imports: [
// other imports...
CoreModule.forRoot({
dynamicLayouts: myDynamicLayouts,
environment,
registerLocaleFn: registerLocale(),
}),
// other imports...
NewLayoutComponent
providers: [
// ...
provideAbpCore(
withOptions({
dynamicLayouts: myDynamicLayouts,
environment,
registerLocaleFn: registerLocale(),
}),
),
],
providers: [APP_ROUTE_PROVIDER, CUSTOM_LAYOUT_PROVIDERS],
bootstrap: [AppComponent],
})
export class AppModule {}
```
In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `CoreModule` using the `forRoot` method.
In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `provideAbpCore` using the `withOptions` method.
Now that you have defined the new layout, you can use it in the router definition. You do this by adding a new route that uses the new layout.
Here's how you can do it:
Here's how you can do it:
```javascript
// route.provider.ts
@ -221,14 +225,13 @@ Run the following command in `angular` folder to create a new component called `
yarn ng generate component logo --inlineTemplate --inlineStyle
```
Open the generated `logo.component.ts` in `src/app/logo` folder and replace its content with the following:
```js
import { Component } from '@angular/core';
import { Component } from "@angular/core";
@Component({
selector: 'app-logo',
selector: "app-logo",
template: `
<a class="navbar-brand" routerLink="/">
<!-- Change the img src -->
@ -284,14 +287,14 @@ yarn ng generate component routes
Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following:
```js
import { Component, HostBinding } from '@angular/core';
import { Component, HostBinding } from "@angular/core";
@Component({
selector: 'app-routes',
templateUrl: 'routes.component.html',
selector: "app-routes",
templateUrl: "routes.component.html",
})
export class RoutesComponent {
@HostBinding('class.mx-auto')
@HostBinding("class.mx-auto")
marginAuto = true;
get smallScreen() {
@ -321,11 +324,14 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" routerLink="/"
><i class="fas fa-home"></i> {%{{{ '::Menu:Home' | abpLocalization }}}%}</a
><i class="fas fa-home"></i> {%{{{ '::Menu:Home' | abpLocalization
}}}%}</a
>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/my-page"><i class="fas fa-newspaper mr-1"></i>My Page</a>
<a class="nav-link" routerLink="/my-page"
><i class="fas fa-newspaper mr-1"></i>My Page</a
>
</li>
<li
#navbarRootDropdown
@ -338,7 +344,11 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac
: (navbarRootDropdown.expand = true)
"
>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
<a
class="nav-link dropdown-toggle"
data-toggle="dropdown"
href="javascript:void(0)"
>
<i class="fas fa-wrench"></i>
{%{{{ 'AbpUiNavigation::Menu:Administration' | abpLocalization }}}%}
</a>
@ -401,7 +411,8 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac
class="btn d-block text-start dropdown-toggle"
>
<i class="fa fa-users"></i>
{%{{{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization }}}%}
{%{{{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization
}}}%}
</a>
</div>
<div
@ -409,7 +420,10 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<div class="dropdown-submenu" *abpPermission="'AbpTenantManagement.Tenants'">
<div
class="dropdown-submenu"
*abpPermission="'AbpTenantManagement.Tenants'"
>
<a class="dropdown-item" routerLink="/tenant-management/tenants">
{%{{{ 'AbpTenantManagement::Tenants' | abpLocalization }}}%}</a
>
@ -554,7 +568,11 @@ Open the generated `nav-items.component.html` in `src/app/nav-items` folder and
```html
<ul class="navbar-nav">
<input type="search" placeholder="Search" class="bg-transparent border-0 text-white" />
<input
type="search"
placeholder="Search"
class="bg-transparent border-0 text-white"
/>
<li class="nav-item d-flex align-items-center">
<div
*ngIf="(dropdownLanguages$ | async)?.length > 0"
@ -592,9 +610,9 @@ Open the generated `nav-items.component.html` in `src/app/nav-items` folder and
</li>
<li class="nav-item d-flex align-items-center">
<ng-template #loginBtn>
<a role="button" class="nav-link pointer" (click)="navigateToLogin()">{%{{{
'AbpAccount::Login' | abpLocalization
}}}%}</a>
<a role="button" class="nav-link pointer" (click)="navigateToLogin()"
>{%{{{ 'AbpAccount::Login' | abpLocalization }}}%}</a
>
</ng-template>
<div
*ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn"
@ -614,8 +632,7 @@ Open the generated `nav-items.component.html` in `src/app/nav-items` folder and
aria-expanded="false"
>
<small *ngIf="(selectedTenant$ | async)?.name as tenantName"
><i>{%{{{ tenantName }}}%}</i
>\</small
><i>{%{{{ tenantName }}}%}</i>\</small
>
<strong>{%{{{ (currentUser$ | async)?.userName }}}%}</strong>
</a>
@ -625,10 +642,12 @@ Open the generated `nav-items.component.html` in `src/app/nav-items` folder and
[class.d-block]="smallScreen && currentUserDropdown.isOpen()"
>
<a class="dropdown-item pointer" (click)="navigateToManageProfile()"
><i class="fa fa-cog mr-1"></i>{%{{{ 'AbpAccount::MyAccount' | abpLocalization }}}%}</a
><i class="fa fa-cog mr-1"></i>{%{{{ 'AbpAccount::MyAccount' |
abpLocalization }}}%}</a
>
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
><i class="fa fa-power-off mr-1"></i>{%{{{ 'AbpUi::Logout' | abpLocalization }}}%}</a
><i class="fa fa-power-off mr-1"></i>{%{{{ 'AbpUi::Logout' |
abpLocalization }}}%}</a
>
</div>
</div>

37
docs/en/UI/Angular/Confirmation-Service.md

@ -71,10 +71,10 @@ const options: Partial<Confirmation.Options> = {
yesText: "Confirm",
messageLocalizationParams: ["Demo"],
titleLocalizationParams: [],
// You can customize icon
// You can customize icon
// icon: 'fa fa-exclamation-triangle', // or
// iconTemplate : '<img src="custom-image-path.jpg" alt=""/>'
}
};
this.confirmation.warn(
"AbpIdentity::RoleDeletionConfirmationMessage",
@ -129,23 +129,26 @@ this.confirmation.clear();
### How to Change Icons of The Confirmation Popup
You can change icons with the token of "confirmationIcons" in ThemeSharedModule in the app.module.ts. The changes will affect all confirmation popup in the project.
```js
...
ThemeSharedModule.forRoot({
confirmationIcons: {
info: 'fa fa-info-circle',
success: 'fa fa-check-circle',
warning: 'fa fa-exclamation-triangle',
error: 'fa fa-times-circle',
default: 'fa fa-question-circle',
},
}),
...
You can change icons with the `withConfirmationIcon()` method of `provideAbpThemeShared` function in the app.module.ts. The changes will affect all confirmation popup in the project.
```ts
import { provideAbpThemeShared, withConfirmationIcon } from '@abp/ng.theme.shared';
@NgModule({
providers: [
// ...
provideAbpThemeShared(withConfirmationIcon({
info: 'fa fa-info-circle',
success: 'fa fa-check-circle',
warning: 'fa fa-exclamation-triangle',
error: 'fa fa-times-circle',
default: 'fa fa-question-circle',
})),
],
})
export class AppModule {}
```
## API
### success

8
docs/en/UI/Angular/Feature-Libraries.md

@ -41,13 +41,13 @@ yarn add @abp/ng.identity
As of ABP v3.0, every lazy-loaded module has a config module available via a secondary entry point on the same package. Importing them in your root module looks like this:
```js
import { IdentityConfigModule } from "@abp/ng.identity/config";
```ts
import { provideIdentityConfig } from "@abp/ng.identity/config";
@NgModule({
imports: [
providers: [
// other imports
IdentityConfigModule.forRoot(),
provideIdentityConfig(),
],
// providers, declarations, and bootstrap
})

52
docs/en/UI/Angular/Form-Validation.md

@ -6,28 +6,20 @@ Reactive forms in ABP Angular UI are validated by [ngx-validate](https://www.npm
## How to Add New Error Messages
You can add a new error message by passing validation options to the `ThemeSharedModule` in your root module.
You can add a new error message by passing validation options to the `withValidationBluePrint` method of `provideAbpThemeShared` function in your root module.
```js
import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core";
import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
```ts
import { provideAbpThemeShared, withValidationBluePrint } from '@abp/ng.theme.shared';
@NgModule({
imports: [
ThemeSharedModule.forRoot({
validation: {
blueprints: {
uniqueUsername: "::AlreadyExists[{%{{{ username }}}%}]",
},
},
// rest of theme shared config
}),
// other imports
providers: [
provideAbpThemeShared(
withValidationBluePrint({
uniqueUsername: "::AlreadyExists[{%{{{ username }}}%}]"
})
),
...
],
// rest of the module metadata
})
export class AppModule {}
```
@ -77,26 +69,16 @@ You can overwrite an existing error message by passing validation options to the
To use this instead of the built-in required input message, all you need to do is the following.
```js
import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core";
import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
```ts
import { provideAbpThemeShared, withValidationBluePrint } from '@abp/ng.theme.shared';
@NgModule({
imports: [
ThemeSharedModule.forRoot({
validation: {
blueprints: {
required: "::RequiredInput",
},
},
// rest of theme shared config
}),
// other imports
providers: [
provideAbpThemeShared(withValidationBluePrint({
required: "::RequiredInput",
})),
...
],
// rest of the module metadata
})
export class AppModule {}
```

22
docs/en/UI/Angular/HTTP-Error-Handling.md

@ -5,22 +5,20 @@
ABP offers a configurations for errors handling like below
```ts
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { provideAbpThemeShared, withHttpErrorConfig } from '@abp/ng.theme.shared';
import { MyCustomRouteErrorComponent } from './my-custom-route.component';
@NgModule({
imports: [
ThemeSharedModule.forRoot({
httpErrorConfig: {
skipHandledErrorCodes: [403],
errorScreen: {
forWhichErrors: [404],
component: CustomErrorComponent,
hideCloseIcon: false
}
providers: [
// ...
provideAbpThemeShared(withHttpErrorConfig({
skipHandledErrorCodes: [403],
errorScreen: {
forWhichErrors: [404],
component: MyCustomRouteErrorComponent,
hideCloseIcon: false
}
}),
...
})),
],
})
export class AppModule {}

183
docs/en/UI/Angular/Localization.md

@ -11,7 +11,7 @@ The Localization key format consists of 2 sections which are **Resource Name** a
const environment = {
//...
localization: {
defaultResourceName: 'MyProjectName',
defaultResourceName: "MyProjectName",
},
};
```
@ -38,7 +38,8 @@ You can also specify a default value as shown below:
```html
<h1>
{%{{{ { key: 'Resource::Key', defaultValue: 'Default Value' } | abpLocalization }}}%}
{%{{{ { key: 'Resource::Key', defaultValue: 'Default Value' } |
abpLocalization }}}%}
</h1>
```
@ -82,17 +83,17 @@ After that, you are able to use localization service.
```js
this.localizationService.instant(
'AbpIdentity::UserDeletionConfirmation',
'John'
"AbpIdentity::UserDeletionConfirmation",
"John"
);
// with fallback value
this.localizationService.instant(
{
key: 'AbpIdentity::UserDeletionConfirmation',
defaultValue: 'Default Value',
key: "AbpIdentity::UserDeletionConfirmation",
defaultValue: "Default Value",
},
'John'
"John"
);
// Output
@ -102,12 +103,12 @@ this.localizationService.instant(
To get a localized text as [_Observable_](https://rxjs.dev/guide/observable) use `get` method instead of `instant`:
```js
this.localizationService.get('Resource::Key');
this.localizationService.get("Resource::Key");
// with fallback value
this.localizationService.get({
key: 'Resource::Key',
defaultValue: 'Default Value',
key: "Resource::Key",
defaultValue: "Default Value",
});
```
@ -118,41 +119,46 @@ Localizations can be determined on backend side. Angular UI gets the localizatio
See an example:
```ts
// app.module.ts
import { provideAbpCore, withOptions } from '@abp/ng.core';
@NgModule({
imports: [
//...other imports
CoreModule.forRoot({
localizations: [
{
culture: 'en',
resources: [
{
resourceName: 'MyProjectName',
texts: {
Administration: 'Administration',
HomePage: 'Home',
providers: [
// ...
provideAbpCore(
withOptions({
...,
localizations: [
{
culture: 'en',
resources: [
{
resourceName: 'MyProjectName',
texts: {
Administration: 'Administration',
HomePage: 'Home',
},
},
},
],
},
{
culture: 'de',
resources: [
{
resourceName: 'MyProjectName',
texts: {
Administration: 'Verwaltung',
HomePage: 'Startseite',
],
},
{
culture: 'de',
resources: [
{
resourceName: 'MyProjectName',
texts: {
Administration: 'Verwaltung',
HomePage: 'Startseite',
},
},
},
],
},
],
}),
]
],
},
],
}),
),
...
],
})
export class AppModule {}
```
...or, you can determine the localizations in a feature module:
@ -256,10 +262,10 @@ Find [styles configuration in angular.json](https://angular.io/guide/workspace-c
If you have created and injected chunks for Fontawesome as seen above, you no longer need the lazy loading in the `AppComponent` which was implemented before v2.9. Simply remove them. The `AppComponent` in the template of the new version looks like this:
```js
import { Component } from '@angular/core';
import { Component } from "@angular/core";
@Component({
selector: 'app-root',
selector: "app-root",
template: `
<abp-loader-bar></abp-loader-bar>
<router-outlet></router-outlet>
@ -274,33 +280,32 @@ Since ABP has more than one language, Angular locale files loads lazily using [W
### registerLocaleFn
`registerLocale` function that exported from `@abp/ng.core/locale` package is a higher order function that accepts `cultureNameLocaleFileMap` object and `errorHandlerFn` function as params and returns Webpack `import` function. A `registerLocale` function must be passed to the `forRoot` of the `CoreModule` as shown below:
```js
// app.module.ts
`registerLocale` function that exported from `@abp/ng.core/locale` package is a higher order function that accepts `cultureNameLocaleFileMap` object and `errorHandlerFn` function as params and returns Webpack `import` function. A `registerLocale` function must be passed to the `withOptions` of the `provideAbpCore` as shown below:
```ts
import { provideAbpCore, withOptions } from '@abp/ng.core';
import { registerLocale } from '@abp/ng.core/locale';
// if you have commercial license and the language management module, add the below import
// import { registerLocale } from '@volo/abp.ng.language-management/locale';
@NgModule({
imports: [
// ...
CoreModule.forRoot({
// ...other options,
registerLocaleFn: registerLocale(
// you can pass the cultureNameLocaleFileMap and errorHandlerFn as optionally
{
cultureNameLocaleFileMap: { 'pt-BR': 'pt' },
errorHandlerFn: ({ resolve, reject, locale, error }) => {
// the error can be handled here
providers: [
provideAbpCore(
withOptions({
...,
registerLocaleFn: registerLocale(
// you can pass the cultureNameLocaleFileMap and errorHandlerFn as optionally
{
cultureNameLocaleFileMap: { 'pt-BR': 'pt' },
errorHandlerFn: ({ resolve, reject, locale, error }) => {
// the error can be handled here
},
},
},
)
}),
//...
]
),
}),
),
...
],
})
export class AppModule {}
```
### Mapping of Culture Name to Angular Locale File Name
@ -320,20 +325,23 @@ import { registerLocale } from '@abp/ng.core/locale';
@NgModule({
imports: [
providers: [
// ...
CoreModule.forRoot({
// ...other options,
registerLocaleFn: registerLocale(
{
cultureNameLocaleFileMap: {
"DotnetCultureName": "AngularLocaleFileName",
"pt-BR": "pt" // example
provideAbpCore(
withOptions({
...,
registerLocaleFn: registerLocale(
{
cultureNameLocaleFileMap: {
"DotnetCultureName": "AngularLocaleFileName",
"pt-BR": "pt" // example
},
},
},
)
}),
//...
)
}),
),
]
})
```
See [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales).
@ -345,12 +353,12 @@ Add the below code to the `app.module.ts` by replacing `your-locale` placeholder
```js
//app.module.ts
import { storeLocaleData } from '@abp/ng.core/locale';
import { storeLocaleData } from "@abp/ng.core/locale";
import(
/* webpackChunkName: "_locale-your-locale-js"*/
/* webpackMode: "eager" */
'@angular/common/locales/your-locale.js'
).then((m) => storeLocaleData(m.default, 'your-locale'));
"@angular/common/locales/your-locale.js"
).then((m) => storeLocaleData(m.default, "your-locale"));
```
...or a custom `registerLocale` function can be passed to the `CoreModule`:
@ -373,14 +381,17 @@ export function registerLocale(locale: string) {
import { registerLocale } from './register-locale';
@NgModule({
imports: [
providers: [
// ...
CoreModule.forRoot({
// ...other options,
registerLocaleFn: registerLocale
}),
provideAbpCore(
withOptions({
...,
registerLocaleFn: registerLocale,
}),
),
//...
]
})
```
After this custom `registerLocale` function, since the en and fr added to the `webpackInclude`, only en and fr locale files will be created as chunks:
@ -391,5 +402,5 @@ Which locale files you add to `webpackInclude` magic comment, they will be inclu
## See Also
* [Localization in ASP.NET Core](../../Localization.md)
* [Video tutorial](https://abp.io/video-courses/essentials/localization)
- [Localization in ASP.NET Core](../../Localization.md)
- [Video tutorial](https://abp.io/video-courses/essentials/localization)

9
docs/en/UI/Angular/OAuth-Module.md

@ -1,9 +1,12 @@
# ABP OAuth Package
The authentication functionality has been moved from @abp/ng.core to @abp/ng.ouath since v7.0.
If your app is version 7.0 or higher, you should include "AbpOAuthModule.forRoot()" in your app.module.ts as an import after "CoreModule.forRoot(...)".
If your app is version 8.3 or higher, you should include "provideAbpOAuth()" in your app.module.ts as an providers after "provideAbpCore()
".
Those abstractions can be found in the @abp/ng-core packages.
- `AuthService` (the class that implements the IAuthService interface).
- `NAVIGATE_TO_MANAGE_PROFILE` Inject token.
- `ApiInterceptor` (the class that implements the IApiInterceptor interface).
@ -11,9 +14,9 @@ Those abstractions can be found in the @abp/ng-core packages.
Those base classes are overridden by the "AbpOAuthModule" for oAuth. There are also three functions provided with AbpOAuthModule.
- `PIPE_TO_LOGIN_FN_KEY` a provide that calls a function when the user is not authenticated. The function should be PipeToLoginFn type.
- `SET_TOKEN_RESPONSE_TO_STORAGE_FN_KEY` a provide that calls a function when the user is authenticated. The function should be SetTokenResponseToStorageFn type.
- `SET_TOKEN_RESPONSE_TO_STORAGE_FN_KEY` a provide that calls a function when the user is authenticated. The function should be SetTokenResponseToStorageFn type.
- `CHECK_AUTHENTICATION_STATE_FN_KEY` a provide that calls a function when the user is authenticated and stores the auth state. The function should be CheckAuthenticationStateFn type.
The tokens and interfaces are in the `@abp/ng.core` package but the implementation of these interfaces is in the `@abp/ng.oauth` package.
The tokens and interfaces are in the `@abp/ng.core` package but the implementation of these interfaces is in the `@abp/ng.oauth` package.
If you want to make your own authentication system, you must also change these 'abstract' classes.

59
docs/en/UI/Angular/Title-Strategy-Service.md

@ -0,0 +1,59 @@
# Title Strategy For Angular
## **ABP has a default title strategy for Angular UI**.
This strategy is based on the title property. Provide a title property when setting a new route.
**Example**
```ts
{
path: 'customers',
component: CustomersComponent,
title: 'AbpCustomers::Roles'
},
```
- It is better to use localized text in the title property. It will be translated by **LocalizationService**.
- The **`title`** property is already set in **ABP internal packages**.
## How it looks
When you create a new route and provide a **`title`** property, it will look like this **`<title> | <projectName>`**
### What is `projectName` and How to Customize
- **`projectName`** is the name of your application. By default, ABP sets a [**`projectName`**](https://github.com/abpframework/abp/blob/f48f78618a326644843c01424b093f0d79448769/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/en.json#L4) for your application. This localization text is added for customization and can be changed for different languages.
### Disable `projectName`
- If you don't want to show **`projectName`** in the title, you can disable it.
**app.module.ts**
```ts
import { DISABLE_PROJECT_NAME } from '@abp/ng.core';
providers: [
...,
{ provide: DISABLE_PROJECT_NAME, useValue: true}
],
```
- Now only title will be shown.
## Override ABP's Default Title Strategy
**app.module.ts**
```ts
import { TitleStrategy } from '@angular/router';
import { YourCustomStrategy } from './title-strategy.service.ts';
providers: [
...,
{ provide: TitleStrategy, useExisting: YourCustomStrategy },
],
```
- You can check [Angular Documentation](https://angular.io/api/router/TitleStrategy) to write a custom **`TitleStrategy`**.

4
docs/en/docs-nav.json

@ -1188,6 +1188,10 @@
{
"text": "Abp Window Service",
"path": "UI/Angular/Abp-Window-Service.md"
},
{
"text": "Title Strategy",
"path": "UI/Angular/Title-Strategy-Service.md"
}
]
},

37
docs/zh-Hans/API/API-Versioning.md

@ -7,11 +7,10 @@ ABP框架集成了[ASPNET-API-版本控制](https://github.com/dotnet/aspnet-api
```cs
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Show neutral/versionless APIs.
context.Services.AddTransient<IApiControllerFilter, NoControllerFilter>();
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});
@ -238,11 +237,10 @@ public override void ConfigureServices(ServiceConfigurationContext context)
preActions.Configure(options);
});
// Show neutral/versionless APIs.
context.Services.AddTransient<IApiControllerFilter, NoControllerFilter>();
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
@ -262,27 +260,22 @@ public override void ConfigureServices(ServiceConfigurationContext context)
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Show neutral/versionless APIs.
context.Services.AddTransient<IApiControllerFilter, NoControllerFilter>();
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
}).AddApiExplorer(options => {
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
context.Services.AddVersionedApiExplorer(
options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
context.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
context.Services.AddAbpSwaggerGen(
@ -321,7 +314,7 @@ public override void OnApplicationInitialization(ApplicationInitializationContex
app.UseAbpRequestLocalization();
app.UseSwagger();
app.UseSwaggerUI(
app.UseAbpSwaggerUI(
options =>
{
var provider = app.ApplicationServices.GetRequiredService<IApiVersionDescriptionProvider>();

27
docs/zh-Hans/UI/Angular/Localization.md

@ -205,16 +205,23 @@ export class AppComponent {}
// app.module.ts
@NgModule({
imports: [
// other imports
CoreModule.forRoot({
// other options
cultureNameLocaleFileMap: {
"DotnetCultureName": "AngularLocaleFileName",
"pt-BR": "pt" // example
}
})
//...
providers: [
// ...
provideAbpCore(
withOptions({
...,
registerLocaleFn: registerLocale(
{
cultureNameLocaleFileMap: {
"DotnetCultureName": "AngularLocaleFileName",
"pt-BR": "pt" // example
},
},
)
}),
),
]
})
```
查看 [Angular中所有的语言环境文件](https://github.com/angular/angular/tree/master/packages/common/locales).

26
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs

@ -26,31 +26,19 @@ public class ClientProxyExceptionEventHandler : ILocalEventHandler<ClientProxyEx
{
using (var scope = ServiceProvider.CreateScope())
{
switch (eventData.StatusCode)
if (eventData.StatusCode == 401)
{
case 401:
var options = scope.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreComponentsWebOptions>>();
if (!options.Value.IsBlazorWebApp)
{
var options = scope.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreComponentsWebOptions>>();
if (!options.Value.IsBlazorWebApp)
{
var authenticationOptions = scope.ServiceProvider.GetRequiredService<IOptions<AbpAuthenticationOptions>>();
var navigationManager = scope.ServiceProvider.GetRequiredService<NavigationManager>();
navigationManager.NavigateToLogout(authenticationOptions.Value.LogoutUrl, "/");
}
else
{
var jsRuntime = scope.ServiceProvider.GetRequiredService<IJSRuntime>();
await jsRuntime.InvokeVoidAsync("eval", "setTimeout(function(){location.assign('/')}, 2000)");
}
break;
var authenticationOptions = scope.ServiceProvider.GetRequiredService<IOptions<AbpAuthenticationOptions>>();
var navigationManager = scope.ServiceProvider.GetRequiredService<NavigationManager>();
navigationManager.NavigateToLogout(authenticationOptions.Value.LogoutUrl, "/");
}
case 403:
else
{
var jsRuntime = scope.ServiceProvider.GetRequiredService<IJSRuntime>();
await jsRuntime.InvokeVoidAsync("eval", "setTimeout(function(){location.assign('/')}, 2000)");
break;
}
}
}

57
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/CachedBundleDynamicFileProvider.cs

@ -0,0 +1,57 @@
using System;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
[Dependency(ReplaceServices = true)]
public class CachedBundleDynamicFileProvider : DynamicFileProvider
{
protected IDistributedCache<InMemoryFileInfoCacheItem> Cache { get; }
protected IOptions<AbpBundlingOptions> BundlingOptions { get; }
public CachedBundleDynamicFileProvider(
IDistributedCache<InMemoryFileInfoCacheItem> cache,
IOptions<AbpBundlingOptions> bundlingOptions)
{
Cache = cache;
BundlingOptions = bundlingOptions;
}
public override IFileInfo GetFileInfo(string? subpath)
{
var fileInfo = base.GetFileInfo(subpath);
if (!subpath.IsNullOrWhiteSpace() && fileInfo is NotFoundFileInfo &&
subpath.Contains(BundlingOptions.Value.BundleFolderName, StringComparison.OrdinalIgnoreCase))
{
var filePath = NormalizePath(subpath);
var cacheItem = Cache.Get(filePath);
if (cacheItem == null)
{
return fileInfo;
}
fileInfo = new InMemoryFileInfo(filePath, cacheItem.FileContent, cacheItem.Name);
DynamicFiles.AddOrUpdate(filePath, fileInfo, (key, value) => fileInfo);
}
return fileInfo;
}
public override void AddOrUpdate(IFileInfo fileInfo)
{
var filePath = fileInfo.GetVirtualOrPhysicalPathOrNull();
Cache.GetOrAdd(filePath!, () => new InMemoryFileInfoCacheItem(filePath!, fileInfo.ReadBytes(), fileInfo.Name));
base.AddOrUpdate(fileInfo);
}
public override bool Delete(string filePath)
{
Cache.Remove(filePath);
return base.Delete(filePath);
}
}

22
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/InMemoryFileInfoCacheItem.cs

@ -0,0 +1,22 @@
using System;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
[Serializable]
[IgnoreMultiTenancy]
public class InMemoryFileInfoCacheItem
{
public InMemoryFileInfoCacheItem(string dynamicPath, byte[] fileContent, string name)
{
DynamicPath = dynamicPath;
Name = name;
FileContent = fileContent;
}
public string DynamicPath { get; set; }
public string Name { get; set; }
public byte[] FileContent { get; set; }
}

6
framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningExtensions.cs

@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection;
public static class AbpApiVersioningExtensions
{
public static IServiceCollection AddAbpApiVersioning(
public static IApiVersioningBuilder AddAbpApiVersioning(
this IServiceCollection services,
Action<ApiVersioningOptions>? apiVersioningOptionsSetupAction = null,
Action<MvcApiVersioningOptions>? mvcApiVersioningOptionsSetupAction = null)
@ -21,9 +21,7 @@ public static class AbpApiVersioningExtensions
apiVersioningOptionsSetupAction ??= _ => { };
mvcApiVersioningOptionsSetupAction ??= _ => { };
services.AddApiVersioning(apiVersioningOptionsSetupAction).AddMvc(mvcApiVersioningOptionsSetupAction);
return services;
return services.AddApiVersioning(apiVersioningOptionsSetupAction).AddMvc(mvcApiVersioningOptionsSetupAction);
}
public static void ConfigureAbp(this MvcApiVersioningOptions options, AbpAspNetCoreMvcOptions mvcOptions)

1
framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj

@ -32,6 +32,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" />
<PackageReference Include="Asp.Versioning.Mvc" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" />
</ItemGroup>
</Project>

7
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditActionFilter.cs

@ -29,14 +29,17 @@ public class AbpAuditActionFilter : IAsyncActionFilter, IAbpFilter, ITransientDe
{
var result = await next();
if (result.Exception != null && !result.ExceptionHandled)
if (result.Exception != null && !auditLog!.Exceptions.Contains(result.Exception))
{
auditLog!.Exceptions.Add(result.Exception);
}
}
catch (Exception ex)
{
auditLog!.Exceptions.Add(ex);
if (!auditLog!.Exceptions.Contains(ex))
{
auditLog!.Exceptions.Add(ex);
}
throw;
}
finally

7
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs

@ -34,14 +34,17 @@ public class AbpAuditPageFilter : IAsyncPageFilter, IAbpFilter, ITransientDepend
{
var result = await next();
if (result.Exception != null && !result.ExceptionHandled)
if (result.Exception != null && !auditLog!.Exceptions.Contains(result.Exception))
{
auditLog!.Exceptions.Add(result.Exception);
}
}
catch (Exception ex)
{
auditLog!.Exceptions.Add(ex);
if (!auditLog!.Exceptions.Contains(ex))
{
auditLog!.Exceptions.Add(ex);
}
throw;
}
finally

12
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs

@ -24,19 +24,15 @@ public class AbpDynamicClaimsMiddleware : AbpMiddlewareBase, ITransientDependenc
if (context.User.Identity?.IsAuthenticated == false)
{
var authenticationSchemeProvider = context.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();
if (!authenticationType.IsNullOrWhiteSpace())
{
var authenticationSchemeProvider = context.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();
var scheme = await authenticationSchemeProvider.GetSchemeAsync(authenticationType);
if (scheme != null)
var authenticationScheme = await authenticationSchemeProvider.GetSchemeAsync(authenticationType);
if (authenticationScheme != null && typeof(IAuthenticationSignOutHandler).IsAssignableFrom(authenticationScheme.HandlerType))
{
await context.SignOutAsync(scheme.Name);
await context.SignOutAsync(authenticationScheme.Name);
}
}
else
{
await context.SignOutAsync();
}
}
}
}

14
framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor

@ -45,9 +45,9 @@
<EntityAction TItem="TItem"
Color="@(action.Color != null ? (Blazorise.Color)action.Color : Blazorise.Color.Primary)"
Icon="@action.Icon"
Clicked="async () => await action.Clicked(context)"
ConfirmationMessage="() => action.ConfirmationMessage.Invoke(context)"
Visible="@(action.Visible != null ? action.Visible(context) : true)"
Clicked="async () => await action.Clicked(context!)"
ConfirmationMessage="() => action.ConfirmationMessage.Invoke(context!)"
Visible="@(action.Visible != null ? action.Visible(context!) : true)"
Text="@action.Text"
Disabled="@action.Disabled">
</EntityAction>
@ -55,10 +55,10 @@
else
{
<EntityAction TItem="TItem"
Clicked="async () => await action.Clicked(context)"
Clicked="async () => await action.Clicked(context!)"
Color="@(action.Color != null ? (Blazorise.Color)action.Color : Blazorise.Color.Primary)"
Icon="@action.Icon"
Visible="@(action.Visible != null ? action.Visible(context) : true)"
Visible="@(action.Visible != null ? action.Visible(context!) : true)"
Text="@action.Text"
Disabled="@action.Disabled">
</EntityAction>
@ -76,7 +76,7 @@
{
<DataGridColumn TItem="TItem" Field="@column.Data" Caption="@column.Title" Sortable="@column.Sortable" DisplayFormat="@column.DisplayFormat" DisplayFormatProvider="@column.DisplayFormatProvider" Displayable="column.Visible" >
<DisplayTemplate>
@RenderCustomTableColumnComponent(column.Component, context)
@RenderCustomTableColumnComponent(column.Component, context!)
</DisplayTemplate>
</DataGridColumn>
}
@ -84,7 +84,7 @@
{
<DataGridColumn TItem="TItem" Field="@column.Data" Caption="@column.Title" Sortable="@column.Sortable" Displayable="column.Visible">
<DisplayTemplate>
@RenderCustomTableColumnComponent(column.Component, context)
@RenderCustomTableColumnComponent(column.Component, context!)
</DisplayTemplate>
</DataGridColumn>
}

2
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor

@ -10,7 +10,7 @@
TItem="SelectItem<object>"
TValue="object"
TextField="item => item?.Text!"
ValueField="item => item?.Value"
ValueField="item => item?.Value!"
SelectedValue="@SelectedValue"
SelectedValueChanged="@SelectedValueChanged"
SearchChanged="@SearchFilterChangedAsync"

1
framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj

@ -15,7 +15,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
<PackageReference Include="NuGet.Versioning" />
<PackageReference Include="System.Text.Encoding.CodePages" />
<PackageReference Include="System.Security.Permissions" />

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs

@ -107,7 +107,7 @@ public class InstallLibsService : IInstallLibsService, ITransientDependency
{
return Directory.GetFiles(directory, "*.csproj", SearchOption.AllDirectories)
.Union(Directory.GetFiles(directory, "angular.json", SearchOption.AllDirectories))
.Where(file => ExcludeDirectory.All(x => file.IndexOf(x, StringComparison.OrdinalIgnoreCase) == -1))
.Where(file => ExcludeDirectory.All(x => file.IndexOf(x + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) == -1))
.Where(file =>
{
if (file.EndsWith(".csproj"))

11
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs

@ -158,9 +158,16 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
throw new Exception("There is no version found with given version: " + version);
}
var nugetVersion = version;
var nugetVersion = await GetTemplateNugetVersionAsync(name, type, version) ?? version;
var localCacheFile = Path.Combine(CliPaths.TemplateCache, name.Replace("/", ".") + ".zip");
if (!string.IsNullOrWhiteSpace(templateSource) && !IsNetworkSource(templateSource))
{
Logger.LogInformation("Using local " + type + ": " + name + ", version: " + version);
return new TemplateFile(File.ReadAllBytes(Path.Combine(templateSource, name + "-" + version + ".zip")),
version, latestVersion, nugetVersion);
}
var localCacheFile = Path.Combine(CliPaths.TemplateCache, name.Replace("/", ".") + "-" + version + ".zip");
#if DEBUG
if (File.Exists(localCacheFile) && templateSource.IsNullOrWhiteSpace())

21
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -192,9 +192,9 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
{
try
{
if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges)
foreach (var entityEntry in AbpEfCoreNavigationHelper.GetChangedEntityEntries())
{
foreach (var entityEntry in AbpEfCoreNavigationHelper.GetChangedEntityEntries())
if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges)
{
if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As<ISoftDelete>().IsDeleted)
{
@ -205,6 +205,23 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity);
}
}
else if (entityEntry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
{
if (entityEntry.Properties.Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey()))
{
// Skip `PublishEntityDeletedEvent/PublishEntityUpdatedEvent` if only foreign keys have changed.
break;
}
if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As<ISoftDelete>().IsDeleted)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity);
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity);
}
}
}
var auditLog = AuditingManager?.Current?.Log;

6
framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DynamicFileProvider.cs

@ -29,14 +29,14 @@ public class DynamicFileProvider : DictionaryBasedFileProvider, IDynamicFileProv
DynamicFiles = new ConcurrentDictionary<string, IFileInfo>();
}
public void AddOrUpdate(IFileInfo fileInfo)
public virtual void AddOrUpdate(IFileInfo fileInfo)
{
var filePath = fileInfo.GetVirtualOrPhysicalPathOrNull();
DynamicFiles.AddOrUpdate(filePath!, fileInfo, (key, value) => fileInfo);
ReportChange(filePath!);
}
public bool Delete(string filePath)
public virtual bool Delete(string filePath)
{
if (!DynamicFiles.TryRemove(filePath, out _))
{
@ -65,7 +65,7 @@ public class DynamicFileProvider : DictionaryBasedFileProvider, IDynamicFileProv
return tokenInfo.ChangeToken;
}
private void ReportChange(string filePath)
protected virtual void ReportChange(string filePath)
{
if (FilePathTokenLookup.TryRemove(filePath, out var tokenInfo))
{

8
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs

@ -28,6 +28,8 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; }
public DbSet<AppEntityWithNavigationsForeign> AppEntityWithNavigationsForeign { get; set; }
public TestMigrationsDbContext(DbContextOptions<TestMigrationsDbContext> options)
: base(options)
{
@ -74,6 +76,7 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOne>(x => x.Id);
b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId);
b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
b.HasOne<AppEntityWithNavigationsForeign>().WithMany().HasForeignKey(x => x.AppEntityWithNavigationForeignId).IsRequired(false);
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToOne>(b =>
@ -87,5 +90,10 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
b.ConfigureByConvention();
b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId);
});
modelBuilder.Entity<AppEntityWithNavigationsForeign>(b =>
{
b.ConfigureByConvention();
});
}
}

8
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs

@ -36,6 +36,8 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; }
public DbSet<AppEntityWithNavigationsForeign> AppEntityWithNavigationsForeign { get; set; }
public TestAppDbContext(DbContextOptions<TestAppDbContext> options)
: base(options)
{
@ -101,6 +103,7 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOne>(x => x.Id);
b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId);
b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
b.HasOne<AppEntityWithNavigationsForeign>().WithMany().HasForeignKey(x => x.AppEntityWithNavigationForeignId).IsRequired(false);
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToOne>(b =>
@ -115,6 +118,11 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId);
});
modelBuilder.Entity<AppEntityWithNavigationsForeign>(b =>
{
b.ConfigureByConvention();
});
modelBuilder.TryConfigureObjectExtensions<TestAppDbContext>();
}
}

19
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs

@ -23,6 +23,7 @@ public class AppEntityWithNavigations : AggregateRoot<Guid>
public string FullName { get; set; }
public AppEntityWithValueObjectAddress AppEntityWithValueObjectAddress { get; set; }
public virtual AppEntityWithNavigationChildOneToOne OneToOne { get; set; }
@ -30,6 +31,8 @@ public class AppEntityWithNavigations : AggregateRoot<Guid>
public virtual List<AppEntityWithNavigationChildOneToMany> OneToMany { get; set; }
public virtual List<AppEntityWithNavigationChildManyToMany> ManyToMany { get; set; }
public virtual Guid? AppEntityWithNavigationForeignId { get; set; }
}
public class AppEntityWithValueObjectAddress : ValueObject
@ -88,3 +91,19 @@ public class AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany
public Guid AppEntityWithNavigationChildManyToManyId { get; set; }
}
public class AppEntityWithNavigationsForeign : AggregateRoot<Guid>
{
protected AppEntityWithNavigationsForeign()
{
}
public AppEntityWithNavigationsForeign(Guid id, string name)
: base(id)
{
Name = name;
}
public string Name { get; set; }
}

101
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs

@ -191,11 +191,13 @@ public abstract class AbpEntityChangeOptions_DomainEvents_Tests<TStartupModule>
where TStartupModule : IAbpModule
{
protected readonly IRepository<AppEntityWithNavigations, Guid> AppEntityWithNavigationsRepository;
protected readonly IRepository<AppEntityWithNavigationsForeign, Guid> AppEntityWithNavigationForeignRepository;
protected readonly ILocalEventBus LocalEventBus;
protected AbpEntityChangeOptions_DomainEvents_Tests()
{
AppEntityWithNavigationsRepository = GetRequiredService<IRepository<AppEntityWithNavigations, Guid>>();
AppEntityWithNavigationForeignRepository = GetRequiredService<IRepository<AppEntityWithNavigationsForeign, Guid>>();
LocalEventBus = GetRequiredService<ILocalEventBus>();
}
@ -288,4 +290,103 @@ public abstract class AbpEntityChangeOptions_DomainEvents_Tests<TStartupModule>
});
entityUpdatedEventTriggered.ShouldBeFalse();
}
[Fact]
public async Task Should_Trigger_EntityUpdatedEvent_For_Aggregate_Root_When_Property_And_Navigation_Changes_Tests()
{
var entityId = Guid.NewGuid();
await AppEntityWithNavigationsRepository.InsertAsync(new AppEntityWithNavigations(entityId, "TestEntity"));
var entityWithNavigationForeignId = Guid.NewGuid();
var entityWithNavigationForeignId2 = Guid.NewGuid();
await AppEntityWithNavigationForeignRepository.InsertAsync(new AppEntityWithNavigationsForeign(entityWithNavigationForeignId, "TestEntityWithNavigationForeign"));
await AppEntityWithNavigationForeignRepository.InsertAsync(new AppEntityWithNavigationsForeign(entityWithNavigationForeignId2, "TestEntityWithNavigationForeign2"));
var entityUpdatedEventTriggered = false;
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigations>>(data =>
{
entityUpdatedEventTriggered = !entityUpdatedEventTriggered;
return Task.CompletedTask;
});
// Test with simple property with foreign key
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.Name = Guid.NewGuid().ToString();
entity.AppEntityWithNavigationForeignId = entityWithNavigationForeignId;
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
// Test only foreign key changed
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.AppEntityWithNavigationForeignId = entityWithNavigationForeignId2;
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeFalse();
// Test with simple property with value object
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.Name = Guid.NewGuid().ToString();
entity.AppEntityWithValueObjectAddress = new AppEntityWithValueObjectAddress("Turkey");
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
// Test with simple property with one to one
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.Name = Guid.NewGuid().ToString();
entity.OneToOne = new AppEntityWithNavigationChildOneToOne
{
ChildName = "ChildName"
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
// Test with simple property with one to many
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.Name = Guid.NewGuid().ToString();
entity.OneToMany = new List<AppEntityWithNavigationChildOneToMany>()
{
new AppEntityWithNavigationChildOneToMany
{
AppEntityWithNavigationId = entity.Id,
ChildName = "ChildName1"
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
// Test with simple property with many to many
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.Name = Guid.NewGuid().ToString();
entity.ManyToMany = new List<AppEntityWithNavigationChildManyToMany>()
{
new AppEntityWithNavigationChildManyToMany
{
ChildName = "ChildName1"
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
}
}

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

Loading…
Cancel
Save