Browse Source

Merge branch 'dev' into ApplyAbpConceptsForModifiedEntity

pull/20012/head
maliming 2 years ago
parent
commit
aed63c7fd1
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 6
      Directory.Packages.props
  2. 101
      README.md
  3. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en-GB.json
  4. 20
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  5. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json
  6. 67
      docs/en/Blog-Posts/2024-06-27 v8_2_Release_Stable/POST.md
  7. BIN
      docs/en/Blog-Posts/2024-06-27 v8_2_Release_Stable/cover-image.png
  8. 2
      docs/en/CLI.md
  9. 302
      docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/How to use Aspire with ABP framework.md
  10. BIN
      docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-dashboard.png
  11. BIN
      docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-metrics.png
  12. BIN
      docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-structured-logs.png
  13. BIN
      docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-traces.png
  14. 130
      docs/en/Community-Articles/2024-07-01-Use_User-Defined_Function_Mapping_For_Global_Filter/POST.md
  15. 83
      docs/en/Data-Filtering.md
  16. 6
      docs/en/Modules/Cms-Kit/Comments.md
  17. 10
      docs/en/Modules/Docs.md
  18. 16
      docs/en/Road-Map.md
  19. 4
      docs/en/UI/AspNetCore/Static-JavaScript-Proxies.md
  20. 4
      docs/en/UI/Blazor/Pwa-Configuration.md
  21. 4
      docs/en/docs-nav.json
  22. BIN
      docs/en/images/book-mastering-abp-framework.png
  23. BIN
      docs/en/images/cmskit-module-comments-settings.png
  24. BIN
      docs/en/images/db-options.png
  25. BIN
      docs/en/images/ddd-microservice-simple.png
  26. BIN
      docs/en/images/ui-options.png
  27. 83
      docs/zh-Hans/Data-Filtering.md
  28. 12
      framework/src/Volo.Abp.AspNetCore.Abstractions/Volo/Abp/AspNetCore/Authentication/AbpAspNetCoreTokenUnauthorizedErrorInfo.cs
  29. 28
      framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Microsoft/Extensions/DependencyInjection/AbpJwtBearerExtensions.cs
  30. 1
      framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj
  31. 3
      framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/AbpAspNetCoreAuthenticationJwtBearerModule.cs
  32. 1
      framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalScriptContributor.cs
  33. 2
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAspNetCoreComponentsWebModule.cs
  34. 2
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAspNetCoreComponentsWebOptions.cs
  35. 1
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs
  36. 1
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs
  37. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Card/AbpCardBodyTagHelperService.cs
  38. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs
  39. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  40. 16
      framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs
  41. 4
      framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs
  42. 5
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs
  43. 21
      framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs
  44. 14
      framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs
  45. 9
      framework/src/Volo.Abp.BlazoriseUI/Components/UiPageProgress.razor.cs
  46. 169
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs
  47. 16
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisExtensions.cs
  48. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs
  49. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs
  50. 13
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
  51. 13
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveFileStep.cs
  52. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppNoLayersTemplateBase.cs
  53. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
  54. 15
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
  55. 11
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs
  56. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs
  57. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Version/PackageVersionCheckerService.cs
  58. 30
      framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
  59. 16
      framework/src/Volo.Abp.Core/Volo/Abp/Check.cs
  60. 2
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs
  61. 8
      framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/MySQL/AbpEntityFrameworkCoreMySQLModule.cs
  62. 6
      framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs
  63. 6
      framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs
  64. 8
      framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs
  65. 8
      framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/SqlServer/AbpEntityFrameworkCoreSqlServerModule.cs
  66. 9
      framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs
  67. 21
      framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs
  68. 68
      framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs
  69. 41
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  70. 62
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs
  71. 49
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs
  72. 6
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs
  73. 59
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs
  74. 21
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs
  75. 25
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs
  76. 10
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs
  77. 16
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs
  78. 11
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs
  79. 8
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/AbpExceptionHandlingConsts.cs
  80. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ar.json
  81. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/cs.json
  82. 5
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/de.json
  83. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/el.json
  84. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en-GB.json
  85. 9
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en.json
  86. 5
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/es.json
  87. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/fa.json
  88. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/fi.json
  89. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/fr.json
  90. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/hi.json
  91. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/hr.json
  92. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/hu.json
  93. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/is.json
  94. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/it.json
  95. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/nl.json
  96. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/pl-PL.json
  97. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/pt-BR.json
  98. 5
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ro-RO.json
  99. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ru.json
  100. 7
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/sk.json

6
Directory.Packages.props

@ -38,8 +38,8 @@
<PackageVersion Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" />
<PackageVersion Include="FluentValidation" Version="11.8.0" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.6" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.6" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.14" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.14" />
<PackageVersion Include="HtmlSanitizer" Version="8.0.746" />
<PackageVersion Include="IdentityModel" Version="6.2.0" />
<PackageVersion Include="IdentityServer4" Version="4.1.2" />
@ -174,4 +174,4 @@
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="Fody" Version="6.8.0" />
</ItemGroup>
</Project>
</Project>

101
README.md

@ -1,22 +1,18 @@
# ABP Framework
![build and test](https://img.shields.io/github/actions/workflow/status/abpframework/abp/build-and-test.yml?branch=dev&style=flat-square)
[![codecov](https://codecov.io/gh/abpframework/abp/branch/dev/graph/badge.svg?token=jUKLCxa6HF)](https://codecov.io/gh/abpframework/abp)
[![NuGet](https://img.shields.io/nuget/v/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
[![NuGet (with prereleases)](https://img.shields.io/nuget/vpre/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
[![MyGet (nightly builds)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.svg?style=flat-square)](https://docs.abp.io/en/abp/latest/Nightly-Builds)
[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
[![Code of Conduct](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](https://github.com/abpframework/abp/blob/dev/CODE_OF_CONDUCT.md)
[![CLA Signed](https://cla-assistant.io/readme/badge/abpframework/abp)](https://cla-assistant.io/abpframework/abp)
[![ABP Discord server](https://img.shields.io/discord/951497912645476422?label=Discord)](https://discord.gg/abp)
ABP Framework is a complete **infrastructure** based on **ASP.NET Core** to create **modern web applications** and **APIs** by following the software development **best practices** and the **latest technologies**. Check out https://abp.io
![build and test](https://img.shields.io/github/actions/workflow/status/abpframework/abp/build-and-test.yml?branch=dev&style=flat-square) 🔹 [![codecov](https://codecov.io/gh/abpframework/abp/branch/dev/graph/badge.svg?token=jUKLCxa6HF)](https://codecov.io/gh/abpframework/abp) 🔹 [![NuGet](https://img.shields.io/nuget/v/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![NuGet (with prereleases)](https://img.shields.io/nuget/vpre/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![MyGet (nightly builds)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.svg?style=flat-square)](https://docs.abp.io/en/abp/latest/Nightly-Builds) 🔹
[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![Code of Conduct](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](https://github.com/abpframework/abp/blob/dev/CODE_OF_CONDUCT.md) 🔹 [![CLA Signed](https://cla-assistant.io/readme/badge/abpframework/abp)](https://cla-assistant.io/abpframework/abp) 🔹 [![Discord Shield](https://discord.com/api/guilds/951497912645476422/widget.png?style=shield)](https://discord.gg/abp)
ABP Framework is a complete **infrastructure** based on **ASP.NET Core** that creates **modern web applications** and **APIs** by following the software development **best practices** and the **latest technologies**.
[![ABP Platform](https://github.com/abpframework/abp/assets/9526587/47531496-4088-406d-9c69-63cb0ffec2ba)](https://abp.io)
## Getting Started
- [Quick Start](https://docs.abp.io/en/abp/latest/Tutorials/Todo/Index) is a single-part, quick-start tutorial to build a simple application with the ABP Framework. Start with this tutorial if you want to quickly understand how ABP works.
- [Getting Started guide](https://docs.abp.io/en/abp/latest/Getting-Started) can be used to create and run ABP based solutions with different options and details.
- [Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1) is a complete tutorial to develop a full stack web application with all aspects of a real-life solution.
- [Quick Start](https://docs.abp.io/en/abp/latest/Tutorials/Todo/Index) is a single-part, quick-start tutorial to build a simple application with the ABP Framework. Start with this tutorial if you want to understand how ABP works quickly.
- [Getting Started guide](https://docs.abp.io/en/abp/latest/Getting-Started) can be used to create and run ABP-based solutions with different options and details.
- [Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1) is a complete tutorial on developing a full-stack web application with all aspects of a real-life solution.
### Quick Start
@ -34,28 +30,26 @@ Create a new solution:
> See the [CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all available options.
### UI Framework Options
<img width="500" src="docs/en/images/ui-options.png">
### Database Provider Options
<img width="500" src="docs/en/images/db-options.png">
## The Book: Mastering ABP Framework
Written by the creator of ABP Framework, this book will help you to gain a complete understanding of the ABP Framework and modern web application development techniques.
* [Buy on Amazon](https://www.amazon.com/gp/product/B097Z2DM8Q)
* [Buy on Packt Publishing](https://www.packtpub.com/product/mastering-abp-framework/9781801079242)
* [More details about the book](https://abp.io/books/mastering-abp-framework)
![book-mastering-abp-framework](docs/en/images/book-mastering-abp-framework.png)
## What ABP Provides?
ABP provides a **full stack developer experience**.
### Architecture
<img src="docs/en/images/ddd-microservice-simple.png">
@ -64,30 +58,48 @@ ABP offers a complete, **modular** and **layered** software architecture based o
ABP Framework is suitable for **[microservice solutions](https://docs.abp.io/en/abp/latest/Microservice-Architecture)** as well as monolithic applications.
### Infrastructure
There are a lot of features provided by the ABP Framework to achieve real world scenarios easier, like [Event Bus](https://docs.abp.io/en/abp/latest/Event-Bus), [Background Job System](https://docs.abp.io/en/abp/latest/Background-Jobs), [Audit Logging](https://docs.abp.io/en/abp/latest/Audit-Logging), [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing), [Data Seeding](https://docs.abp.io/en/abp/latest/Data-Seeding), [Data Filtering](https://docs.abp.io/en/abp/latest/Data-Filtering), etc.
There are a lot of features provided by the ABP Framework to achieve real-world scenarios easier, like [Event Bus](https://docs.abp.io/en/abp/latest/Event-Bus), [Background Job System](https://docs.abp.io/en/abp/latest/Background-Jobs), [Audit Logging](https://docs.abp.io/en/abp/latest/Audit-Logging), [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing), [Data Seeding](https://docs.abp.io/en/abp/latest/Data-Seeding), [Data Filtering](https://docs.abp.io/en/abp/latest/Data-Filtering), etc.
### Cross-Cutting Concerns
ABP also simplifies (and even automates wherever possible) cross-cutting concerns and common non-functional requirements like [Exception Handling](https://docs.abp.io/en/abp/latest/Exception-Handling), [Validation](https://docs.abp.io/en/abp/latest/Validation), [Authorization](https://docs.abp.io/en/abp/latest/Authorization), [Localization](https://docs.abp.io/en/abp/latest/Localization), [Caching](https://docs.abp.io/en/abp/latest/Caching), [Dependency Injection](https://docs.abp.io/en/abp/latest/Dependency-Injection), [Setting Management](https://docs.abp.io/en/abp/latest/Settings), etc.
### Cross Cutting Concerns
ABP also simplifies (and even automates wherever possible) cross cutting concerns and common non-functional requirements like [Exception Handling](https://docs.abp.io/en/abp/latest/Exception-Handling), [Validation](https://docs.abp.io/en/abp/latest/Validation), [Authorization](https://docs.abp.io/en/abp/latest/Authorization), [Localization](https://docs.abp.io/en/abp/latest/Localization), [Caching](https://docs.abp.io/en/abp/latest/Caching), [Dependency Injection](https://docs.abp.io/en/abp/latest/Dependency-Injection), [Setting Management](https://docs.abp.io/en/abp/latest/Settings), etc.
### Application Modules
ABP is a modular framework and the Application Modules provide **pre-built application functionalities**;
- [**Account**](https://docs.abp.io/en/abp/latest/Modules/Account): Provides UI for the account management and allows user to login/register to the application.
- **[Identity](https://docs.abp.io/en/abp/latest/Modules/Identity)**: Manages organization units, roles, users and their permissions, based on the Microsoft Identity library.
- **[Identity](https://docs.abp.io/en/abp/latest/Modules/Identity)**: Manages organization units, roles, users and their permissions based on the Microsoft Identity library.
- [**OpenIddict**](https://docs.abp.io/en/abp/latest/Modules/OpenIddict): Integrates to OpenIddict.
- [**Tenant Management**](https://docs.abp.io/en/abp/latest/Modules/Tenant-Management): Manages tenants for a [multi-tenant](https://docs.abp.io/en/abp/latest/Multi-Tenancy) (SaaS) application.
See the [Application Modules](https://docs.abp.io/en/abp/latest/Modules/Index) document for all pre-built modules.
### Startup Templates
The [Startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Index) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**.
## ABP Community
## Mastering ABP Framework Book
This book will help you to gain a complete understanding of the ABP Framework and modern web application development techniques. It is written by the creator and team lead of the ABP Framework. You can buy from [Amazon](https://www.amazon.com/gp/product/B097Z2DM8Q) or [Packt Publishing](https://www.packtpub.com/product/mastering-abp-framework/9781801079242). Find further info about the book at [abp.io/books/mastering-abp-framework](https://abp.io/books/mastering-abp-framework).
![book-mastering-abp-framework](docs/en/images/book-mastering-abp-framework.png)
## The Community
### ABP Community Web Site
@ -103,28 +115,31 @@ See the [sample projects](https://docs.abp.io/en/abp/latest/Samples/Index) built
### Want to Contribute?
ABP is a community-driven open source project. See [the contribution guide](https://docs.abp.io/en/abp/latest/Contribution/Index) if you want to be a part of this project.
ABP is a community-driven open-source project. See [the contribution guide](https://docs.abp.io/en/abp/latest/Contribution/Index) if you want to participate in this project.
## Official Links
* <a href="https://abp.io/" target="_blank">Main Web Site</a>
* <a href="https://abp.io/get-started" target="_blank">Get Started</a>
* <a href="https://abp.io/features" target="_blank">Features</a>
* <a href="https://docs.abp.io/" target="_blank">Documentation</a>
* <a href="https://docs.abp.io/en/abp/latest/Samples/Index" target="_blank">Samples</a>
* <a href="https://blog.abp.io/" target="_blank">Blog</a>
* <a href="https://community.abp.io/" target="_blank">Community</a>
* <a href="https://stackoverflow.com/questions/tagged/abp" target="_blank">Stack overflow</a>
* <a href="https://twitter.com/abpframework" target="_blank">Twitter</a>
* [Home Website](https://abp.io)
* [Get Started](https://abp.io/get-started)
* [Features](https://abp.io/features)
* [Documents](https://docs.abp.io/)
* [Samples](https://docs.abp.io/en/abp/latest/Samples/Index)
* [Blog](https://blog.abp.io/)
* [Community](https://community.abp.io/)
* [Stackoverflow](https://stackoverflow.com/questions/tagged/abp)
* [Twitter](https://twitter.com/abpframework)
## Support ABP
## Support the ABP Framework
GitHub repository stars are an important indicator of popularity and the size of the community. If you like ABP Framework, support us by clicking the star :star: on the repository.
Love ABP Framework? **Please give a star** to this repository :star:
## Discord Channel
You can use this link to join the ABP Community Discord Server: https://discord.gg/abp
## Discord Server
## ABP Commercial
We have a Discord server where you can chat with other ABP users. Share your ideas, report technical issues, showcase your creations, share the tips that worked for you and catch up with the latest news and announcements about ABP Framework. Join 👉 https://discord.gg/abp.
See also [ABP Commercial](https://commercial.abp.io/) if you are looking for pre-built application modules, professional themes, code generation tooling and premium support for the ABP Framework.

2
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en-GB.json

@ -59,7 +59,7 @@
"MilestoneErrorMessage": "Couldn't get the current milestone details from Github.",
"QuestionItemErrorMessage": "Couldn't get the latest question details from Stackoverflow.",
"Oops": "Oops!",
"CreatePostSuccessMessage": "The Post has been successfully submitted. It will be published once it has been reviewed by the site admin.",
"CreatePostSuccessMessage": "Your post has been successfully submitted. It will be published after review by the site administrator.",
"ChooseCoverImage": "Choose a cover image...",
"CoverImage": "Cover Image",
"ShareYourExperiencesWithTheABPFramework": "Share your experiences with the ABP Framework!",

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

@ -16,7 +16,7 @@
"Title": "Title",
"CreationTime": "Creation time",
"Save": "Save",
"SameUrlAlreadyExist": "Same url already exists if you want to add this post, you should change the url!",
"SameUrlAlreadyExist": "Same URL already exists! You must change the URL!",
"UrlIsNotValid": "Url is not valid.",
"UrlNotFound": "Url not found.",
"UrlContentNotFound": "Url content not found.",
@ -33,7 +33,7 @@
"FeatureRequest": "Feature Request",
"CreatePostTitleInfo": "Title of the post to be shown on the post list.",
"CreatePostSummaryInfo": "A short summary of the post to be shown on the post list. Maximum length: <b>{0}</b>",
"CreatePostCoverInfo": "For creating an effective post, add a cover photo. Upload 16:9 aspect ratio pictures for the best view.<br/> Maximum file size: <b>1MB.</b>",
"CreatePostCoverInfo": "To create an effective post, add a cover photo. Upload <b>16:9 aspect ratio</b> pictures for the best view.<br/> Maximum file size is <b>1MB.</b>",
"ThisExtensionIsNotAllowed": "This extension is not allowed.",
"TheFileIsTooLarge": "The file is too large.",
"GoToThePost": "Go to the Post",
@ -56,7 +56,7 @@
"MilestoneErrorMessage": "Could not get the current milestone details from Github.",
"QuestionItemErrorMessage": "Could not get the latest question details from Stackoverflow.",
"Oops": "Oops!",
"CreatePostSuccessMessage": "The Post has been successfully submitted. It will be published after a review from the site admin.",
"CreatePostSuccessMessage": "Your post has been successfully submitted. It will be published after review by the site administrator.",
"Browse": "Browse",
"CoverImage": "Cover Image",
"ShareYourExperiencesWithTheABPFramework": "Share your experiences with the ABP Framework!",
@ -125,7 +125,7 @@
"YoutubeVideoSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> Submit the video URL using the form.",
"YoutubeVideoSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> Visitors will be able to watch your video content directly on this website.",
"ExternalContent": "External Content",
"ExternalContentSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> Create a content on any public platform (Medium, your own blog or anywhere you like).",
"ExternalContentSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> Create content on any public platform (Medium, your own blog or anywhere you like).",
"ExternalContentSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> Submit your content URL using the form.",
"ExternalContentSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> Visitors are redirected to the content on the original website.",
"ChooseYourContentType": "Please choose the way you want to add your content.",
@ -135,12 +135,12 @@
"GitHubUserNameValidationMessage": "Your Github username can not include whitespace, please make sure your Github username is correct.",
"PersonalSiteUrlValidationMessage": "Your personal site URL can not include whitespace, please make sure your personal site URL is correct.",
"TwitterUserNameValidationMessage": "Your Twitter username can not include whitespace, please make sure your Twitter username is correct.",
"LinkedinUrlValidationMessage": "Your Linkedin URL can not include whitespace, please make sure your Linkedin URL is correct.",
"LinkedinUrlValidationMessage": "Your LinkedIn URL can not include whitespace, please make sure your LinkedIn URL is correct.",
"NoPostsFound": "No posts found!",
"SearchInPosts": "Search in posts...",
"MinimumSearchContent": "You must enter at least 3 characters!",
"Volo.AbpIo.Domain:060001": "Source URL(\"{PostUrl}\") is not Github URL",
"Volo.AbpIo.Domain:060002": "Post Content is not available from Github(\"{PostUrl}\") resource.",
"Volo.AbpIo.Domain:060002": "Post Content is not available from GitHub(\"{PostUrl}\") resource.",
"Volo.AbpIo.Domain:060003": "No post content found!",
"SeeMore": "See More",
"JoinTheABPCommunity": "Join the ABP Community",
@ -171,14 +171,14 @@
"Discord_Page_Announce": "We are happy to announce ABP Community Discord Server!",
"Discord_Page_Description_1": "ABP Community has been growing since day one. We wanted to take it to the next step by creating an official ABP Discord server so the ABP Community can interact with each other using the wonders of instant messaging.",
"Discord_Page_Description_2": "ABP Community Discord Server is the place where you can showcase your creations using ABP Framework, share the tips that worked for you, catch up with the latest news and announcements about ABP Framework, just chat with community members to exchange ideas, and have fun!",
"Discord_Page_Description_3": "This ABP Community Discord Server is the official one with the ABP Core Team is present on the server to monitor.",
"Discord_Page_Description_3": "This ABP Community Discord Server is the official one with the ABP Core Team present on the server to monitor.",
"Discord_Page_JoinToServer": "Join ABP Discord Server",
"Events_Page_MetaTitle": "ABP Community Events",
"Events_Page_MetaDescription": "The live shows, hosted by the ABP Team, are casual sessions full of community content, demos, Q&A, and discussions around what's happening in ABP.",
"Events_Page_Title": "ABP<span class=\"gradient-community\"> Community</span> Talks",
"Members_Page_WritingFromUser": "Read writing from {0} on ABP Community.",
"Post_Create_Page_MetaTitle": "New Post",
"Post_Create_Page_MetaDescription": "Create your post for sharing your experiences about ABP framework and contributing the ABP Community.",
"Post_Create_Page_MetaDescription": "Create your post for sharing your experiences about the ABP framework and contributing to the ABP Community.",
"Post_Create_Page_CreateNewPost": "Create New Post",
"Post_Index_Page_MetaDescription": "ABP Community's purpose is to create a contribution environment for developers who use the ABP framework.",
"Layout_Title": "{0} | ABP Community",
@ -193,7 +193,7 @@
"AbpCommunityTitleContent": "ABP Community - Open Source ABP Framework",
"CommunitySlogan": "A unique community platform for <span class=\"{0}\">ABP Lovers</span>",
"RaffleIsNotActive": "Raffle is not active",
"YouAreAlreadyJoinedToThisRaffle": "You already joined to this raffle!",
"YouAreAlreadyJoinedToThisRaffle": "You already joined this raffle!",
"InvalidSubscriptionCode": "Invalid subscription code",
"Raffle:{0}": "Raffle: {0}",
"Join": "Join",
@ -266,4 +266,4 @@
"DailyPostCreateLimitation": "You have reached the daily post creation limit. You can create a new post in {0}.",
"YourAccountDisabled": "Your user account is disabled!"
}
}
}

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

@ -158,7 +158,7 @@
"IndexPageHeroSection": "<span class=\"first-line shine\"><strong>open source</strong></span><span class=\"second-line text-uppercase\">Web Application<br />Framework</span><span class=\"third-line shine2\"><strong>for asp.net core</strong></span>",
"UiFramework": "UI框架",
"EmailAddress": "电子邮件地址",
"Mobile": "移动电话",
"Mobile": "选择移动应用程序框架",
"ReactNative": "React Native",
"Strong": "强大",
"Complete": "完整",
@ -476,4 +476,4 @@
"ABPVideoCoursesPageDescription": "通过ABP团队创建的视频课程学习ABP框架的基础知识。在本视频系列中,您将学习ABP框架的基本主题。每个视频上的数字都是根据学习流程编写的。",
"ABPVideoCoursesTitle": "ABP 基本视频"
}
}
}

67
docs/en/Blog-Posts/2024-06-27 v8_2_Release_Stable/POST.md

@ -0,0 +1,67 @@
# ABP.IO Platform 8.2 Final Has Been Released!
[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 8.2 versions have been released today.
## What's New With Version 8.2?
All the new features were explained in detail in the [8.2 RC Announcement Post](https://blog.abp.io/abp/announcing-abp-8-2-release-candidate), so there is no need to review them again. You can check it out for more details.
## Getting Started with 8.2
### Creating New Solutions
You can create a new solution with the ABP Framework version 8.2 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started).
> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more.
### How to Upgrade an Existing Solution
#### Install/Update the ABP CLI
First, install the ABP CLI or upgrade it to the latest version.
If you haven't installed it yet:
```bash
dotnet tool install -g Volo.Abp.Cli
```
To update the existing CLI:
```bash
dotnet tool update -g Volo.Abp.Cli
```
#### Upgrading Existing Solutions with the ABP Update Command
[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command:
```bash
abp update
```
Run this command in the root folder of your solution.
## 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)
## Community News
### New ABP Community Posts
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here:
* [How to use Angular Material with Form Validation on ABP](https://community.abp.io/posts/how-to-use-angular-material-with-form-validation-on-abp-jtheajj3) by [Mahmut Gündoğdu](https://x.com/mahmutgundogdu)
* [Tunnel your local host address to a public URL with ngrok](https://community.abp.io/posts/tunnel-your-local-host-address-to-a-public-url-with-ngrok-4cywnocj) by [Bart Van Hoey](https://github.com/bartvanhoey)
* [Antiforgery Token Validation When Angular and HTTP API Runs on the Same Server](https://community.abp.io/posts/antiforgery-token-validation-when-angular-and-http-api-runs-on-the-same-server-mzf5ppdq) by [dignite](https://x.com/dignite_adu)
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/articles/submit) to the ABP Community.
## About the Next Version
The next feature version will be 8.3. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version.

BIN
docs/en/Blog-Posts/2024-06-27 v8_2_Release_Stable/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

2
docs/en/CLI.md

@ -118,6 +118,8 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample
* `angular`: Angular UI. There are some additional options for this template:
* `--separate-auth-server`: The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side.
* `--pwa`: Specifies the project as Progressive Web Application.
* `blazor-webapp`: Blazor Web App UI. There are some additional options for this template:
* `--tiered`: The Auth Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *AuthServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project.
* `blazor`: Blazor UI. There are some additional options for this template:
* `--separate-auth-server`The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side.
* `--pwa`: Specifies the project as Progressive Web Application.

302
docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/How to use Aspire with ABP framework.md

@ -0,0 +1,302 @@
# How to use .NET Aspire with ABP framework
[.NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview) is an opinionated, cloud-ready stack designed for building observable, production-ready, and distributed applications. On the other hand, the [ABP framework](https://docs.abp.io/en/abp/latest) offers a complete, modular and layered software architecture based on Domain Driven Design principles and patterns. This guide explores how to combine .NET Aspire with ABP, enabling developers to create observable, and feature-rich applications.
## When to Use .NET Aspire?
Using .NET Aspire with the ABP framework can be beneficial in various scenarios where you need to combine the strengths of both technologies. Here are some situations when using .NET Aspire with ABP can be advantageous:
- **Enterprise Web Applications:** ABP is well-suited for building enterprise web applications with its opinionated architecture and best practices. When combined with .NET Aspire, you can leverage ABP's features for rapid development of user interfaces, backend services, and business logic while benefiting from .NET Aspire's cloud-native capabilities and observability features.
- **Observability and Monitoring:** .NET Aspire's emphasis on observability, including logging, monitoring, and tracing, can enhance ABP applications by providing deeper insights into system behavior, performance metrics, and diagnostics, which is key for maintaining and optimizing enterprise-grade applications.
## Creating a new ABP Solution
To demonstrate the usage of .NET Aspire with the ABP framework, I've created an ABP solution. If you want to create the same solution from scratch, follow the steps below:
Install the ABP CLI if you haven't installed it before:
```bash
dotnet tool install -g Volo.Abp.Cli
```
Create a new solution with the ABP framework's Application Startup Template with Tiered MVC UI and EF Core database:
```bash
abp new AspirationalAbp -u mvc --database-provider ef -dbms PostgreSQL --csf --tiered
```
> The startup template selection matters for this article. I chose these options so that the demo solution can cover complex scenarios.
**Disclaimer-I:** This article is based on version `8.0.1` of .NET Aspire and version `8.2.0` of ABP Framework.
**Disclaimer-II:** ABP and .NET Aspire may not be fully compatible in some respects. This article aims to explain how these two technologies can be used together in the simplest way possible, even if they are not fully compatible.
## Add .NET Aspire
After creating the solution, run the following commands in the `src` folder of your solution to add .NET Aspire:
```bash
// Adding AppHost
dotnet new aspire-apphost -n AspirationalAbp.AppHost
dotnet sln ../AspirationalAbp.sln add ./AspirationalAbp.AppHost/AspirationalAbp.AppHost.csproj
// Adding ServiceDefaults
dotnet new aspire-servicedefaults -n AspirationalAbp.ServiceDefaults
dotnet sln ../AspirationalAbp.sln add ./AspirationalAbp.ServiceDefaults/AspirationalAbp.ServiceDefaults.csproj
```
These commands add two new projects to the solution:
- **AspirationalAbp.AppHost**: An orchestrator project designed to connect and configure the different projects and services of your app.
- **AspirationalAbp.ServiceDefaults**: A .NET Aspire shared project to manage configurations that are reused across the projects in your solution related to [resilience](https://learn.microsoft.com/en-us/dotnet/core/resilience/http-resilience), [service discovery](https://learn.microsoft.com/en-us/dotnet/aspire/service-discovery/overview), and [telemetry](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/telemetry).
We have added .NET Aspire to our ABP based solution, but we have not registered our projects in the .NET Aspire orchestration. Now, let's enroll our projects, which implement the db migrator, web user interface, API, and auth, in .NET Aspire orchestration.
## Registering projects to .NET Aspire orchestration
First of all, we need to add the reference of related projects to the `AspirationalAbp.AppHost` project. For this, add the following `ItemGroups` to the `AspirationalAbp.AppHost/AspirationalAbp.AppHost.csproj` file:
```csharp
<ItemGroup>
<ProjectReference Include="..\AspirationalAbp.AuthServer\AspirationalAbp.AuthServer.csproj" />
<ProjectReference Include="..\AspirationalAbp.HttpApi.Host\AspirationalAbp.HttpApi.Host.csproj" />
<ProjectReference Include="..\AspirationalAbp.Web\AspirationalAbp.Web.csproj"/>
<ProjectReference Include="..\AspirationalAbp.DbMigrator\AspirationalAbp.DbMigrator.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="8.0.1" />
<PackageReference Include="Aspire.Hosting.Redis" Version="8.0.1" />
</ItemGroup>
```
With the first `ItemGroup`, we added the references of `AuthServer`, `HttpApi.Host`, `Web`, and `DbMigrator` projects to the app host project. So, we can orchestrate them within the app model.
With the second `ItemGroup`, to model the **PostgreSQL** server resource and **Redis** resource in the app host, installed the `Aspire.Hosting.PostgreSQL` and `Aspire.Hosting.Redis`
Now let's update the `Program` class of the `AspirationalAbp.AppHost` project as follows:
```csharp
using Microsoft.Extensions.Hosting;
var builder = DistributedApplication.CreateBuilder(args);
var postgres = builder
.AddPostgres("postgres")
.AddDatabase("AspirationalAbp");
var redis = builder.AddRedis("redis");
// DbMigrator
if (builder.Environment.IsDevelopment())
{
builder
.AddProject<Projects.AspirationalAbp_DbMigrator>("dbMigrator")
.WithReference(postgres, "Default")
.WithReference(redis, "Redis")
.WithReplicas(1);
}
// AuthServer
var authServerLaunchProfile = "AspirationalAbp.AuthServer";
builder
.AddProject<Projects.AspirationalAbp_AuthServer>("authserver", launchProfileName: authServerLaunchProfile)
.WithExternalHttpEndpoints()
.WithReference(postgres, "Default")
.WithReference(redis);
// HttpApi.Host
var httpApiHostLaunchProfile = "AspirationalAbp.HttpApi.Host";
builder
.AddProject<Projects.AspirationalAbp_HttpApi_Host>("httpapihost", launchProfileName: httpApiHostLaunchProfile)
.WithExternalHttpEndpoints()
.WithReference(postgres, "Default")
.WithReference(redis);
// Web
builder
.AddProject<Projects.AspirationalAbp_Web>("web", "AspirationalAbp.Web")
.WithReference(redis);
builder.Build().Run();
```
With the code above, the following operations were performed below:
1. Creates an `IDistributedApplicationBuilder` instance by calling `DistributedApplication.CreateBuilder(args)`.
2. Adds PostgreSQL and the `AspirationalAbp` database.
3. Adds Redis.
4. Adds the `DbMigrator` project with references to PostgreSQL and Redis, ensuring one replica in development.
5. Adds the `AuthServer` project with external HTTP endpoints, referencing PostgreSQL and Redis.
6. Adds the `HttpApi.Host` project with external HTTP endpoints, referencing PostgreSQL and Redis.
7. Adds the `Web` project, referencing Redis.
8. Builds and runs the application.
Now let's make the projects we added to the app host compatible with .NET Aspire.
## Configuring Projects for Aspire
To make the `AspirationalAbp.DbMigrator`, `AspirationalAbp.AuthServer`, `AspirationalAbp.HttpApi.Host`, and `AspirationalAbp.Web` projects compatible with .NET Aspire, we need to add and configure several packages. For that, we need to add the `Aspire.StackExchange.Redis` package to all these projects and the `Aspire.Npgsql.EntityFrameworkCore.PostgreSQL` package to the `AspirationalAbp.EntityFrameworkCore` project. Additionally, we will add the `AspirationalAbp.ServiceDefaults` reference to host projects except `AspirationalAbp.DbMigrator`. Also, we need to convert [Serilog](https://serilog.net/) events into [OpenTelemetry](https://opentelemetry.io/) `LogRecord`s, for that we will add a `Serilog.Sinks.OpenTelemetry` reference to host projects. Let's begin with configuring `AspirationalAbp.DbMigrator`.
### AspirationalAbp.DbMigrator
First, let's add the `Aspire.StackExchange.Redis`, and `Serilog.Sinks.OpenTelemetry` packages to the `AspirationalAbp.DbMigrator` project. For this, let's run the following .NET CLI command inside the `AspirationalAbp.DbMigrator` project:
```bash
dotnet add package Aspire.StackExchange.Redis --version 8.0.1
dotnet add package Serilog.Sinks.OpenTelemetry --version 4.0.0-dev-00313
```
Then let's override the `PreConfigureServices` method in `AspirationalAbpDbMigratorModule` as below:
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
configuration["Redis:Configuration"] = configuration["ConnectionStrings:Redis"];
}
```
To use the **OpenTelemetry** sink we have installed the `Serilog.Sinks.OpenTelemetry` package and now let's enable the sink. For this, let's write the following code block just before calling the `CreateLogger` method in the logger configuration in `Program.cs`:
```csharp
/// .WriteTo.Async(c => c.Console())
.WriteTo.Async(c => c.OpenTelemetry())
/// .CreateLogger();
```
Now let's continue with `AspirationalAbp.EntityFrameworkCore`.
### AspirationalAbp.EntityFrameworkCore
Now let's add the `Aspire.Npgsql.EntityFrameworkCore.PostgreSQL` package to the `AspirationalAbp.EntityFrameworkCore` project. For this, you can run the following command in the `AspirationalAbp.EntityFrameworkCore` project:
```bash
dotnet add package Aspire.Npgsql.EntityFrameworkCore.PostgreSQL --version 8.0.1
```
Now let's continue with `AspirationalAbp.AuthServer`.
### AspirationalAbp.AuthServer
First, let's add the `Serilog.Sinks.OpenTelemetry`, `Aspire.StackExchange.Redis` and `AspirationalAbp.ServiceDefaults` packages to the `AspirationalAbp.AuthServer` project. For this, let's run the following .NET CLI command inside the `AspirationalAbp.AuthServer` project:
```bash
dotnet add package Aspire.StackExchange.Redis --version 8.0.1
dotnet add reference ../AspirationalAbp.ServiceDefaults/AspirationalAbp.ServiceDefaults.csproj
dotnet add package Serilog.Sinks.OpenTelemetry --version 4.0.0-dev-00313
```
Then add the following code block after defining the builder variable in `Program.cs`:
```csharp
builder.AddServiceDefaults();
builder.AddRedisClient("redis");
builder.AddNpgsqlDbContext<AspirationalAbpDbContext>("Default",
options =>
{
options.DisableRetry = true;
});
```
Then add the following code to the `PreConfigureServices` method in the `AspirationalAbpAuthServerModule` class:
```csharp
configuration["Redis:Configuration"] = configuration["ConnectionStrings:Redis"];
```
To use the **OpenTelemetry** sink we have installed the `Serilog.Sinks.OpenTelemetry` package and now let's enable the sink. For this, let's write the following code block just before calling the `CreateLogger` method in logger configuration in `Program.cs`:
```csharp
/// .WriteTo.Async(c => c.Console())
.WriteTo.Async(c => c.OpenTelemetry())
/// .CreateLogger();
```
So far we have made `AspirationalAbp.DbMigrator`, `AspirationalAbp.EntityFrameworkCore`, and `AspirationalAbp.AuthServer` compatible with .NET Aspire. Now let's continue with `AspirationalAbp.HttpApi.Host`.
### AspirationalAbp.HttpApi.Host
First, let's add the `Serilog.Sinks.OpenTelemetry`, `Aspire.StackExchange.Redis` and `AspirationalAbp.ServiceDefaults` packages to the `AspirationalAbp.HttpApi.Host` project. For this, let's run the following .NET CLI command inside the `AspirationalAbp.HttpApi.Host` project:
```bash
dotnet add package Aspire.StackExchange.Redis --version 8.0.1
dotnet add reference ../AspirationalAbp.ServiceDefaults/AspirationalAbp.ServiceDefaults.csproj
dotnet add package Serilog.Sinks.OpenTelemetry --version 4.0.0-dev-00313
```
Then add the following code block after defining the builder variable in `Program.cs`:
```csharp
builder.AddServiceDefaults();
builder.AddRedisClient("redis");
builder.AddNpgsqlDbContext<AspirationalAbpDbContext>("Default",
options =>
{
options.DisableRetry = true;
});
```
Then let's override the `PreConfigureServices` method in `AspirationalAbpHttpApiHostModule` as below:
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
configuration["Redis:Configuration"] = configuration["ConnectionStrings:Redis"];
}
```
To use the **OpenTelemetry** sink we have installed the `Serilog.Sinks.OpenTelemetry` package and now let's enable the sink. For this, let's write the following code block just before calling the `CreateLogger` method in the logger configuration in `Program.cs`:
```csharp
/// .WriteTo.Async(c => c.Console())
.WriteTo.Async(c => c.OpenTelemetry())
/// .CreateLogger();
```
Finally, let's make `AspirationalAbp.Web` compatible with .NET Aspire.
### AspirationalAbp.Web
First, let's add the `Serilog.Sinks.OpenTelemetry`, `Aspire.StackExchange.Redis` and `AspirationalAbp.ServiceDefaults` packages to the `AspirationalAbp.Web` project. For this, let's run the following .NET CLI command inside the `AspirationalAbp.Web` project:
```bash
dotnet add package Aspire.StackExchange.Redis --version 8.0.1
dotnet add reference ../AspirationalAbp.ServiceDefaults/AspirationalAbp.ServiceDefaults.csproj
dotnet add package Serilog.Sinks.OpenTelemetry --version 4.0.0-dev-00313
```
Then add the following code block after defining the builder variable in `Program.cs`:
```csharp
builder.AddServiceDefaults();
builder.AddRedisClient("redis");
```
Then add the following code to the `PreConfigureServices` method in the `AspirationalAbpWebModule` class:
```bash
var configuration = context.Services.GetConfiguration();
configuration["Redis:Configuration"] = configuration["ConnectionStrings:Redis"];
```
To use the **OpenTelemetry** sink we have installed the `Serilog.Sinks.OpenTelemetry` package and now let's enable the sink. For this, let's write the following code block just before calling the `CreateLogger` method in logger configuration in `Program.cs`:
```csharp
/// .WriteTo.Async(c => c.Console())
.WriteTo.Async(c => c.OpenTelemetry())
/// .CreateLogger();
```
After making all our changes, we can run the `AspirationalAbp.AppHost` project.
![aspire-dashboard](aspire-dashboard.png)
![aspire-structured-logs](aspire-structured-logs.png)
![aspire-traces](aspire-traces.png)
![aspire-metrics](aspire-metrics.png)
## Conclusion
Combining .NET Aspire with the ABP framework creates a powerful setup for building robust, observable, and feature-rich applications. By integrating Aspire's observability and cloud capabilities with ABP's approach of focusing on your business without repeating yourself, you can develop feature-rich, scalable applications with enhanced monitoring and seamless cloud integration. This guide provides a clear path to set up and configure these technologies, ensuring your applications are well-structured, maintainable, and ready for modern cloud environments.

BIN
docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-dashboard.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-metrics.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-structured-logs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-traces.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

130
docs/en/Community-Articles/2024-07-01-Use_User-Defined_Function_Mapping_For_Global_Filter/POST.md

@ -0,0 +1,130 @@
# Use User-Defined Function Mapping for Global Filter
## Introduction
ABP provides data filters that can filter queries automatically based on some rules. This feature is useful for implementing multi-tenancy, soft delete, and other global filters. It uses [EF Core's Global Query Filters system](https://learn.microsoft.com/en-us/ef/core/querying/filters) for the EF Core Integration.
EF Core Global Query Filters generate filter conditions and apply them to SQL queries. ABP controls whether this filter condition takes effect through a variable. However, this variable may cause performance losses in some scenarios.
## The Filter Condition Variable
Think of a scenario with a global filter `IIsActive`, which filters out inactive entities:
```csharp
public class Book : IIsActive
{
public string Name { get; set; }
public bool IsActive { get; set; }
}
```
The SQL generated by the [EF Core Global Query Filters](https://learn.microsoft.com/en-us/ef/core/querying/filters) is as follows:
```SQL
SELECT * FROM [AppBooks] AS [a]
WHERE (@__ef_filter__p_0 = CAST(1 AS bit) OR [a].[IsActive] = CAST(1 AS bit))
```
> The `__ef_filter__p_0` variable controls whether the filter condition takes effect.
The generated SQL is not optimal, and some databases do not optimize it well.
## Using User-defined function mapping for global filters
In the [upcoming preview version of ABP, v8.3.0-rc.1](https://github.com/abpframework/abp/pull/20065), we start the [User-defined function mapping](https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping) to implement global filters more efficiently. This feature is enabled by default, so you don't need to make any changes if you create a new solution and start from scratch. Otherwise, you can enable it easily by following the instructions below.
To use this new feature for your custom global filters, you need to change your `DbContext` as follows:
````csharp
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;
protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
if (UseDbFunction())
{
isActiveFilter = e => IsActiveFilter(((IIsActive)e).IsActive, true);
var abpEfCoreCurrentDbContext = this.GetService<AbpEfCoreCurrentDbContext>();
modelBuilder.HasDbFunction(typeof(MyProjectNameDbContext).GetMethod(nameof(IsActiveFilter))!)
.HasTranslation(args =>
{
// (bool isActive, bool boolParam)
var isActive = args[0];
var boolParam = args[1];
if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<IIsActive>() == true)
{
// isActive == true
return new SqlBinaryExpression(
ExpressionType.Equal,
isActive,
new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping),
boolParam.Type,
boolParam.TypeMapping);
}
// empty where sql
return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping);
});
}
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
}
return expression;
}
public static bool IsActiveFilter(bool isActive, bool boolParam)
{
throw new NotSupportedException(AbpEfCoreDataFilterDbFunctionMethods.NotSupportedExceptionMessage);
}
public override string GetCompiledQueryCacheKey()
{
return $"{base.GetCompiledQueryCacheKey()}:{IsActiveFilterEnabled}";
}
````
After these changes, the SQL generated by the EF Core Global Query Filters will be as follows:
Enabling the `IIsActive` filter:
```SQL
SELECT * FROM [AppBooks] AS [a] WHERE
[a].[IsActive] = CAST(1 AS bit)
```
Disabling the `IIsActive` filter:
```SQL
SELECT * FROM [AppBooks] AS [a]
```
## Conclusion
We have implemented global filters using [User-defined function mapping](https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping), which can generate more efficient SQL and thus improve performance.
Upgrade to the latest ABP version and enjoy the performance improvement!
## References
- [ABP Framework Data Filtering](https://docs.abp.io/en/abp/latest/Data-Filtering)
- [EF Core's Global Query Filters system](https://learn.microsoft.com/en-us/ef/core/querying/filters)
- [User-defined function mapping](https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping)

83
docs/en/Data-Filtering.md

@ -217,17 +217,14 @@ protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityTyp
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>();
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> isActiveFilter =
e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
expression = expression == null
? isActiveFilter
: QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
}
return expression;
@ -251,6 +248,78 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
}
````
#### Using User-defined function mapping for global filters
Using [User-defined function mapping](https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping) for global filters will gain performance improvements.
To use this feature, you need to change your DbContext like below:
````csharp
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;
protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
if (UseDbFunction())
{
isActiveFilter = e => IsActiveFilter(((IIsActive)e).IsActive, true);
var abpEfCoreCurrentDbContext = this.GetService<AbpEfCoreCurrentDbContext>();
modelBuilder.HasDbFunction(typeof(MyProjectNameDbContext).GetMethod(nameof(IsActiveFilter))!)
.HasTranslation(args =>
{
// (bool isActive, bool boolParam)
var isActive = args[0];
var boolParam = args[1];
if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<IIsActive>() == true)
{
// isActive == true
return new SqlBinaryExpression(
ExpressionType.Equal,
isActive,
new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping),
boolParam.Type,
boolParam.TypeMapping);
}
// empty where sql
return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping);
});
}
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
}
return expression;
}
public static bool IsActiveFilter(bool isActive, bool boolParam)
{
throw new NotSupportedException(AbpEfCoreDataFilterDbFunctionMethods.NotSupportedExceptionMessage);
}
public override string GetCompiledQueryCacheKey()
{
return $"{base.GetCompiledQueryCacheKey()}:{IsActiveFilterEnabled}";
}
````
### MongoDB
ABP abstracts the `IMongoDbRepositoryFilterer` interface to implement data filtering for the [MongoDB Integration](MongoDB.md), it works only if you use the repositories properly. Otherwise, you should manually filter the data.

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

@ -80,6 +80,12 @@ You can also view and manage replies on this page.
![comments-detail](../../images/cmskit-module-comments-detail.png)
## Settings
You can configure the approval status of comments using the "Comment" tab under the "Cms" section on the Settings page. When this feature is enabled, you can approve and reject comments. In this way, users can only see the comments that you approve. By default, this feature is set to "false."
![comments-settings](../../images/cmskit-module-comments-settings.png)
## Internals
### Domain Layer

10
docs/en/Modules/Docs.md

@ -34,7 +34,7 @@ abp new Acme.MyProject
### 2- Running The Empty Application
After you download the project, extract the ZIP file and open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Application.Contracts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `HttpApi`, `HttpApi.Client` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**.
After you created the project, open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Application.Contracts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `HttpApi`, `HttpApi.Client` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**.
![Create a new project](../images/docs-module_solution-explorer.png)
@ -332,7 +332,7 @@ Open `DocsProjects` in your database, and insert a new record with the following
* **DocumentStoreType**: The source of the documents (for GitHub:`GitHub`, for file system`FileSystem`)
* **ExtraProperties**: A serialized `JSON` that stores special configuration for the selected `DocumentStoreType`.
* **MainWebsiteUrl**: The URL when user clicks to the logo of the Docs module page. You can simply set as `/` to link to your website root address.
* **LatestVersionBranchName**: This is a config for GitHub. It's the branch name which to retrieve the docs. You can set it as `master`.
* **LatestVersionBranchName**: This is a config for GitHub. It's the branch name which to retrieve the docs. You can set it as `dev`.
#### Sample Project Record for "GitHub"
@ -367,7 +367,7 @@ You can use [ABP Framework](https://github.com/abpframework/abp/) GitHub documen
For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table:
```mssql
INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL, N'', N'12f21123e08e4f15bedbae0b2d939659')
INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"","GitHubUserAgent":""}', N'/', N'dev', N'', N'12f21123e08e4f15bedbae0b2d939659')
```
Be aware that `GitHubAccessToken` is masked. It's a private token and you must get your own token and replace the `***` string.
@ -402,12 +402,12 @@ You can use [ABP Framework](https://github.com/abpframework/abp/) GitHub documen
- MainWebsiteUrl: `/`
- LatestVersionBranchName: `<NULL>`
- LatestVersionBranchName: `latest`
For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table:
```mssql
INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL, N'')
INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', N'latest', N'', N'12f21123e08e4f15bedbae0b2d939659')
```
Add one of the sample projects above and run the application. In the menu you will see `Documents` link, click the menu link to open the documents page.

16
docs/en/Road-Map.md

@ -4,17 +4,17 @@ This document provides a road map, release schedule, and planned features for th
## Next Versions
### v8.2
### v8.3
The next version will be 8.2 and planned to release the stable 8.2 version in June 2024. We will be mostly working on the following topics:
The next version will be 8.3 and planned to release the stable 8.3 version in August 2024. We will be mostly working on the following topics:
* Blazor Full-Stack UI ([#18289](https://github.com/abpframework/abp/issues/18289))
* Angular Universal ([#15782](https://github.com/abpframework/abp/issues/15782))
* Upgrading React Native template to the latest major release 0.72.7 ([#18191](https://github.com/abpframework/abp/issues/18191))
* Deployment Documents Improvements ([#15034](https://github.com/abpframework/abp/issues/15034))
* Angular generate-proxy root namespace options ([#18932](https://github.com/abpframework/abp/issues/18932))
* Blazor global JS & CSS at runtime ([#19963](https://github.com/abpframework/abp/issues/19963))
* CMS Kit - Improvement in editing approval system for comments ([#19976](https://github.com/abpframework/abp/issues/19976))
* Improvements on the existing features and provide more guides.
See the [8.2 milestone](https://github.com/abpframework/abp/milestone/95) for all the issues we've planned to work on.
See the [8.3 milestone](https://github.com/abpframework/abp/milestone/101) for all the issues we've planned to work on.
## Backlog Items
@ -23,7 +23,7 @@ The *Next Versions* section above shows the main focus of the planned versions.
Here is a list of major items in the backlog we are considering working on in the next versions.
* [#86](https://github.com/abpframework/abp/issues/86) / GrapQL Integration
* [#236](https://github.com/abpframework/abp/issues/236) / Resource based authorization system
* [#236](https://github.com/abpframework/abp/issues/236) / Resource-based authorization system
* [#2882](https://github.com/abpframework/abp/issues/2882) / Providing a gRPC integration infrastructure (while it is [already possible](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) to create or consume gRPC endpoints for your application, we plan to create endpoints for the [standard application modules](https://docs.abp.io/en/abp/latest/Modules/Index))
* [#57](https://github.com/abpframework/abp/issues/57) / Built-in CQRS infrastructure
* [#4223](https://github.com/abpframework/abp/issues/4223) / WebHook system
@ -35,7 +35,7 @@ Here is a list of major items in the backlog we are considering working on in th
* [#16756](https://github.com/abpframework/abp/issues/16756) / Blob Storing - Provider configuration UI
* [#16744](https://github.com/abpframework/abp/issues/16744) / State Management API
You can always check the milestone planning and the prioritized backlog issues on [the GitHub repository](https://github.com/abpframework/abp/milestones) for a detailed road map. The backlog items are subject to change. We are adding new items and changing priorities based on the community feedbacks and goals of the project.
You can always check the milestone planning and the prioritized backlog issues on [the GitHub repository](https://github.com/abpframework/abp/milestones) for a detailed road map. The backlog items are subject to change. We are adding new items and changing priorities based on the community feedback and goals of the project.
## Feature Requests

4
docs/en/UI/AspNetCore/Static-JavaScript-Proxies.md

@ -66,7 +66,7 @@ acme.bookStore.authors.author.get = function(id, ajaxParams) {
};
````
> `generate-proxy` command generates proxies for only the APIs you've defined in your application (assumes `app` as the module name). If you are developing a modular application, you can specify the `-m` (or `--module`) parameter to specify the module you want to generate proxies. See the *generate-proxy* section in the [ABP CLI](../CLI.md) documentation for other options.
> `generate-proxy` command generates proxies for only the APIs you've defined in your application (assumes `app` as the module name). If you are developing a modular application, you can specify the `-m` (or `--module`) parameter to specify the module you want to generate proxies. See the *generate-proxy* section in the [ABP CLI](../../CLI.md) documentation for other options.
### Using the Proxy Functions
@ -154,4 +154,4 @@ See the [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) documentation for all
* [Dynamic JavaScript API Client Proxies](Dynamic-JavaScript-Proxies.md)
* [Auto API Controllers](../../API/Auto-API-Controllers.md)
* [Web Application Development Tutorial](../../Tutorials/Part-1.md)
* [Web Application Development Tutorial](../../Tutorials/Part-1.md)

4
docs/en/UI/Blazor/Pwa-Configuration.md

@ -59,7 +59,7 @@ First, you need to create a JSON file named **manifest.json** under the **wwwroo
You can add some icons for your application to be seen in specific screen sizes and define in which screen sizes icons should be displayed in the **manifest.json** file. You can see the **icons** section in the **manifest.json** file as an example above.
> You can use, default icons from our [template](https://github.com/abpframework/abp/tree/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot).
> You can use, default icons from our [template](https://github.com/abpframework/abp/tree/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/wwwroot).
### 3-) Configure Service Workers
@ -67,7 +67,7 @@ You can add some icons for your application to be seen in specific screen sizes
You need to create `service-worker.js` and `service-worker.published.js` files under the **wwwroot** folder of your project. These files will be used by your project to determine which PWA features you want to use.
You can get the simple configurations for the [service-worker.js](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/service-worker.js) and [service-worker.published.js](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/service-worker.published.js) files from our [template](https://github.com/abpframework/abp/tree/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot).
You can get the simple configurations for the [service-worker.js](https://github.com/abpframework/abp/tree/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/wwwroot/service-worker.js) and [service-worker.published.js](https://github.com/abpframework/abp/tree/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/wwwroot/service-worker.published.js) files from our [template](https://github.com/abpframework/abp/tree/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/wwwroot).
After the related service worker files are added, then we need to define them in our `.csproj` file to notify our application. So open your `*.csproj` file and add the following content:

4
docs/en/docs-nav.json

@ -1165,6 +1165,10 @@
"text": "Ellipsis",
"path": "UI/Angular/Ellipsis-Directive.md"
},
{
"text": "Projection Strategy",
"path": "UI/Angular/Projection-Strategy.md"
},
{
"text": "Context Strategy",
"path": "UI/Angular/Context-Strategy.md"

BIN
docs/en/images/book-mastering-abp-framework.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 74 KiB

BIN
docs/en/images/cmskit-module-comments-settings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/images/db-options.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/en/images/ddd-microservice-simple.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 60 KiB

BIN
docs/en/images/ui-options.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 41 KiB

83
docs/zh-Hans/Data-Filtering.md

@ -166,17 +166,14 @@ protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityTyp
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>();
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> isActiveFilter =
e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
expression = expression == null
? isActiveFilter
: QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
}
return expression;
@ -186,6 +183,78 @@ protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntit
* 添加 `IsActiveFilterEnabled` 属性用于检查是否启用了 `IIsActive` . 内部使用了之前介绍到的 `IDataFilter` 服务.
* 重写 `ShouldFilterEntity` 和 `CreateFilterExpression` 方法检查给定实体是否实现 `IIsActive` 接口,在必要时组合表达式.
#### 使用用户定义的函数映射
使用[用户定义的函数映射](https://learn.microsoft.com/zh-cnn/ef/core/querying/user-defined-function-mapping) 可以获取性能提升.
要使用此功能,请更改DbContext如下所示:
````csharp
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;
protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
if (UseDbFunction())
{
isActiveFilter = e => IsActiveFilter(((IIsActive)e).IsActive, true);
var abpEfCoreCurrentDbContext = this.GetService<AbpEfCoreCurrentDbContext>();
modelBuilder.HasDbFunction(typeof(MyProjectNameDbContext).GetMethod(nameof(IsActiveFilter))!)
.HasTranslation(args =>
{
// (bool isActive, bool boolParam)
var isActive = args[0];
var boolParam = args[1];
if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<IIsActive>() == true)
{
// isActive == true
return new SqlBinaryExpression(
ExpressionType.Equal,
isActive,
new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping),
boolParam.Type,
boolParam.TypeMapping);
}
// empty where sql
return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping);
});
}
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
}
return expression;
}
public static bool IsActiveFilter(bool isActive, bool boolParam)
{
throw new NotSupportedException(AbpEfCoreDataFilterDbFunctionMethods.NotSupportedExceptionMessage);
}
public override string GetCompiledQueryCacheKey()
{
return $"{base.GetCompiledQueryCacheKey()}:{IsActiveFilterEnabled}";
}
````
### MongoDB
ABP抽象了 `IMongoDbRepositoryFilterer` 接口为[MongoDB 集成](MongoDB.md)实现数据过滤, 只有正确的使用仓储,它才会工作. 否则你需要手动过滤数据.

12
framework/src/Volo.Abp.AspNetCore.Abstractions/Volo/Abp/AspNetCore/Authentication/AbpAspNetCoreTokenUnauthorizedErrorInfo.cs

@ -0,0 +1,12 @@
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Authentication;
public class AbpAspNetCoreTokenUnauthorizedErrorInfo : IScopedDependency
{
public string? Error { get; set; }
public string? ErrorDescription { get; set; }
public string? ErrorUri { get; set; }
}

28
framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Microsoft/Extensions/DependencyInjection/AbpJwtBearerExtensions.cs

@ -1,6 +1,7 @@
using System;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.AspNetCore.Authentication;
using Volo.Abp.Security.Claims;
namespace Microsoft.Extensions.DependencyInjection;
@ -31,6 +32,33 @@ public static class AbpJwtBearerExtensions
return builder.AddJwtBearer(authenticationScheme, displayName, options =>
{
configureOptions?.Invoke(options);
options.Events ??= new JwtBearerEvents();
var previousOnChallenge = options.Events.OnChallenge;
options.Events.OnChallenge = async eventContext =>
{
await previousOnChallenge(eventContext);
if (eventContext.Handled ||
!string.IsNullOrEmpty(eventContext.Error) ||
!string.IsNullOrEmpty(eventContext.ErrorDescription) ||
!string.IsNullOrEmpty(eventContext.ErrorUri))
{
return;
}
var tokenUnauthorizedErrorInfo = eventContext.HttpContext.RequestServices.GetRequiredService<AbpAspNetCoreTokenUnauthorizedErrorInfo>();
if (string.IsNullOrEmpty(tokenUnauthorizedErrorInfo.Error) &&
string.IsNullOrEmpty(tokenUnauthorizedErrorInfo.ErrorDescription) &&
string.IsNullOrEmpty(tokenUnauthorizedErrorInfo.ErrorUri))
{
return;
}
eventContext.Error = tokenUnauthorizedErrorInfo.Error;
eventContext.ErrorDescription = tokenUnauthorizedErrorInfo.ErrorDescription;
eventContext.ErrorUri = tokenUnauthorizedErrorInfo.ErrorUri;
};
});
}
}

1
framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj

@ -19,6 +19,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />
<ProjectReference Include="..\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Abstractions\Volo.Abp.AspNetCore.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>

3
framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/AbpAspNetCoreAuthenticationJwtBearerModule.cs

@ -7,7 +7,7 @@ using Volo.Abp.Security.Claims;
namespace Volo.Abp.AspNetCore.Authentication.JwtBearer;
[DependsOn(typeof(AbpSecurityModule), typeof(AbpCachingModule))]
[DependsOn(typeof(AbpSecurityModule), typeof(AbpCachingModule), typeof(AbpAspNetCoreAbstractionsModule))]
public class AbpAspNetCoreAuthenticationJwtBearerModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
@ -21,5 +21,6 @@ public class AbpAspNetCoreAuthenticationJwtBearerModule : AbpModule
context.Services.AddTransient<WebRemoteDynamicClaimsPrincipalContributor>();
context.Services.AddTransient<WebRemoteDynamicClaimsPrincipalContributorCache>();
}
}
}

1
framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalScriptContributor.cs

@ -1,6 +1,7 @@
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Components.Server.Theming.Bundling;

2
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAspNetCoreComponentsWebModule.cs

@ -2,7 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.AspNetCore.Components.DependencyInjection;
using Volo.Abp.AspNetCore.Components.Server;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.Modularity;
using Volo.Abp.UI;

2
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAspNetCoreComponentsWebOptions.cs

@ -1,4 +1,4 @@
namespace Volo.Abp.AspNetCore.Components.Server;
namespace Volo.Abp.AspNetCore.Components.Web;
public class AbpAspNetCoreComponentsWebOptions
{

1
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs

@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Volo.Abp.AspNetCore.Components.Server;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.AspNetCore.Components.Web.ExceptionHandling;
using Volo.Abp.AspNetCore.Components.Web.Security;

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

@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
using Volo.Abp.AspNetCore.Components.Server;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Card/AbpCardBodyTagHelperService.cs

@ -32,7 +32,7 @@ public class AbpCardBodyTagHelperService : AbpTagHelperService<AbpCardBodyTagHel
if (!TagHelper.Subtitle.IsNullOrWhiteSpace())
{
var cardSubtitle = new TagBuilder(AbpCardSubtitleTagHelper.DefaultHeading.ToHtmlTag());
cardSubtitle.AddCssClass("card-subtitle text-muted mb-2");
cardSubtitle.AddCssClass("card-subtitle mb-2");
cardSubtitle.InnerHtml.AppendHtml(TagHelper.Subtitle!);
output.PreContent.AppendHtml(cardSubtitle);
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs

@ -250,7 +250,7 @@ public class AbpSelectTagHelperService : AbpTagHelperService<AbpSelectTagHelper>
var small = new TagBuilder("small");
small.Attributes.Add("id", idAttr?.Value?.ToString() + "InfoText");
small.AddCssClass("form-text text-muted");
small.AddCssClass("form-text");
small.InnerHtml.Append(localizedText);
return small.ToHtmlString();

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js

@ -90,7 +90,7 @@ var abp = abp || {};
return "";
}
var $button = $('<button type="button" class="btn btn-primary abp-action-button"></button>');
var $button = $('<button type="button" class="btn btn-primary btn-sm abp-action-button"></button>');
if (firstItem.displayNameHtml) {
$button.html(abp.utils.isFunction(firstItem.text) ? firstItem.text(record, tableInstance) : firstItem.text);

16
framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs

@ -50,14 +50,18 @@ public static class AbpRegistrationBuilderExtensions
ServiceDescriptor serviceDescriptor)
where TActivatorData : ReflectionActivatorData
{
registrationBuilder.OnActivated(context =>
var actions = activatedActionList.GetActions(serviceDescriptor);
if (actions.Any())
{
var serviceActivatedContext = new OnServiceActivatedContext(context.Instance!);
foreach (var action in activatedActionList.GetActions(serviceDescriptor))
registrationBuilder.OnActivated(context =>
{
action.Invoke(serviceActivatedContext);
}
});
var serviceActivatedContext = new OnServiceActivatedContext(context.Instance!);
foreach (var action in actions)
{
action.Invoke(serviceActivatedContext);
}
});
}
return registrationBuilder;
}

4
framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs

@ -94,6 +94,10 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet
{
cron = $"0 */{time.TotalHours} * * *";
}
else if(time.TotalDays <= 31)
{
cron = $"0 0 0 1/{time.TotalDays} * *";
}
else
{
throw new AbpException(

5
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs

@ -22,8 +22,9 @@ public abstract class BackgroundWorkerBase : IBackgroundWorker
protected ILogger Logger => LazyServiceProvider.LazyGetService<ILogger>(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance);
protected CancellationTokenSource StoppingTokenSource { get; }
protected CancellationToken StoppingToken { get; }
protected CancellationTokenSource StoppingTokenSource { get; set; }
protected CancellationToken StoppingToken { get; set; }
public BackgroundWorkerBase()
{

21
framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs

@ -480,7 +480,12 @@ public abstract class AbpCrudPageBase<
await GetEntitiesAsync();
await InvokeAsync(CreateModal!.Hide);
await Notify.Success(L["SavedSuccessfully"]);
await Notify.Success(GetCreateMessage());
}
protected virtual string GetCreateMessage()
{
return UiLocalizer["CreatedSuccessfully"];
}
protected virtual async Task UpdateEntityAsync()
@ -519,7 +524,12 @@ public abstract class AbpCrudPageBase<
await GetEntitiesAsync();
await InvokeAsync(EditModal!.Hide);
await Notify.Success(L["SavedSuccessfully"]);
await Notify.Success(GetUpdateMessage());
}
protected virtual string GetUpdateMessage()
{
return UiLocalizer["SavedSuccessfully"];
}
protected virtual async Task DeleteEntityAsync(TListViewModel entity)
@ -546,7 +556,12 @@ public abstract class AbpCrudPageBase<
{
await GetEntitiesAsync();
await InvokeAsync(StateHasChanged);
await Notify.Success(L["DeletedSuccessfully"]);
await Notify.Success(GetDeleteMessage());
}
protected virtual string GetDeleteMessage()
{
return UiLocalizer["DeletedSuccessfully"];
}
protected virtual string GetDeleteConfirmationMessage(TListViewModel entity)

14
framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs

@ -46,6 +46,12 @@ public static class BlazoriseUiObjectExtensionPropertyInfoExtensions
public static string? GetDateEditInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property)
{
var dataFormatString = property.GetDataFormatStringOrNull();
if (dataFormatString != null)
{
return dataFormatString;
}
if (property.IsDate())
{
return "{0:yyyy-MM-dd}";
@ -59,6 +65,14 @@ public static class BlazoriseUiObjectExtensionPropertyInfoExtensions
return null;
}
public static string? GetDataFormatStringOrNull(this IBasicObjectExtensionPropertyInfo property)
{
return property
.Attributes
.OfType<DisplayFormatAttribute>()
.FirstOrDefault()?.DataFormatString;
}
public static string? GetTextInputValueOrNull(this IBasicObjectExtensionPropertyInfo property, object? value)
{
if (value == null)

9
framework/src/Volo.Abp.BlazoriseUI/Components/UiPageProgress.razor.cs

@ -7,7 +7,7 @@ namespace Volo.Abp.BlazoriseUI.Components;
public partial class UiPageProgress : ComponentBase, IDisposable
{
protected PageProgress PageProgressRef { get; set; } = default!;
protected PageProgress? PageProgressRef { get; set; }
protected int? Percentage { get; set; }
@ -33,8 +33,11 @@ public partial class UiPageProgress : ComponentBase, IDisposable
Visible = e.Percentage == null || (e.Percentage >= 0 && e.Percentage <= 100);
Color = GetColor(e.Options.Type);
await PageProgressRef.SetValueAsync(e.Percentage);
if(PageProgressRef != null)
{
await PageProgressRef.SetValueAsync(e.Percentage);
}
await InvokeAsync(StateHasChanged);
}

169
framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
@ -19,6 +20,8 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
protected static readonly string SlidingExpirationKey;
protected static readonly string DataKey;
protected static readonly long NotPresent;
protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpirationData;
protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpiration;
private readonly static FieldInfo SetScriptField;
private readonly static FieldInfo RedisDatabaseField;
@ -27,29 +30,29 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
private readonly static MethodInfo MapMetadataMethod;
private readonly static MethodInfo GetAbsoluteExpirationMethod;
private readonly static MethodInfo GetExpirationInSecondsMethod;
private readonly static MethodInfo OnRedisErrorMethod;
protected IDatabase RedisDatabase => GetRedisDatabase()!;
private IDatabase? _redisDatabase;
protected string Instance { get; }
protected RedisKey InstancePrefix { get; }
static AbpRedisCache()
{
var type = typeof(RedisCache);
RedisDatabaseField = Check.NotNull(type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic), nameof(RedisDatabaseField))!;
RedisDatabaseField = Check.NotNull(type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic), nameof(RedisDatabaseField));
SetScriptField = Check.NotNull(type.GetField("_setScript", BindingFlags.Instance | BindingFlags.NonPublic), nameof(SetScriptField))!;
SetScriptField = Check.NotNull(type.GetField("_setScript", BindingFlags.Instance | BindingFlags.NonPublic), nameof(SetScriptField));
ConnectMethod = Check.NotNull(type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectMethod))!;
ConnectMethod = Check.NotNull(type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectMethod));
ConnectAsyncMethod = Check.NotNull(type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectAsyncMethod))!;
ConnectAsyncMethod = Check.NotNull(type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectAsyncMethod));
MapMetadataMethod = Check.NotNull(type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static), nameof(MapMetadataMethod))!;
MapMetadataMethod = Check.NotNull(type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static), nameof(MapMetadataMethod));
GetAbsoluteExpirationMethod = Check.NotNull(type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetAbsoluteExpirationMethod))!;
GetAbsoluteExpirationMethod = Check.NotNull(type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetAbsoluteExpirationMethod));
GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod))!;
GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod));
OnRedisErrorMethod = Check.NotNull(type.GetMethod("OnRedisError", BindingFlags.Instance | BindingFlags.NonPublic), nameof(OnRedisErrorMethod));
AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!;
@ -58,32 +61,29 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!;
NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.To<int>();
HashMembersAbsoluteExpirationSlidingExpirationData = [AbsoluteExpirationKey, SlidingExpirationKey, DataKey];
HashMembersAbsoluteExpirationSlidingExpiration = [AbsoluteExpirationKey, SlidingExpirationKey];
}
public AbpRedisCache(IOptions<RedisCacheOptions> optionsAccessor)
: base(optionsAccessor)
{
Instance = optionsAccessor.Value.InstanceName ?? string.Empty;
}
protected virtual void Connect()
{
if (GetRedisDatabase() != null)
var instanceName = optionsAccessor.Value.InstanceName;
if (!string.IsNullOrEmpty(instanceName))
{
return;
InstancePrefix = (RedisKey)Encoding.UTF8.GetBytes(instanceName);
}
}
ConnectMethod.Invoke(this, Array.Empty<object>());
protected virtual IDatabase Connect()
{
return (IDatabase)ConnectMethod.Invoke(this, Array.Empty<object>())!;
}
protected virtual async ValueTask<IDatabase> ConnectAsync(CancellationToken token = default)
{
var redisDatabase = GetRedisDatabase();
if (redisDatabase != null)
{
return redisDatabase;
}
return await (ValueTask<IDatabase>)ConnectAsyncMethod.Invoke(this, new object[] { token })!;
}
@ -108,9 +108,17 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
IEnumerable<KeyValuePair<string, byte[]>> items,
DistributedCacheEntryOptions options)
{
Connect();
var cache = Connect();
Task.WaitAll(PipelineSetMany(items, options));
try
{
Task.WaitAll(PipelineSetMany(cache, items, options));
}
catch (Exception ex)
{
OnRedisError(ex, cache);
throw;
}
}
public async Task SetManyAsync(
@ -120,9 +128,17 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{
token.ThrowIfCancellationRequested();
await ConnectAsync(token);
var cache = await ConnectAsync(token);
await Task.WhenAll(PipelineSetMany(items, options));
try
{
await Task.WhenAll(PipelineSetMany(cache, items, options));
}
catch (Exception ex)
{
OnRedisError(ex, cache);
throw;
}
}
public void RefreshMany(
@ -146,9 +162,17 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{
keys = Check.NotNull(keys, nameof(keys));
Connect();
var cache = Connect();
RedisDatabase.KeyDelete(keys.Select(key => (RedisKey)(Instance + key)).ToArray());
try
{
cache.KeyDelete(keys.Select(key => InstancePrefix.Append(key)).ToArray());
}
catch (Exception ex)
{
OnRedisError(ex, cache);
throw;
}
}
public async Task RemoveManyAsync(IEnumerable<string> keys, CancellationToken token = default)
@ -156,33 +180,40 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
keys = Check.NotNull(keys, nameof(keys));
token.ThrowIfCancellationRequested();
await ConnectAsync(token);
var cache = await ConnectAsync(token);
await RedisDatabase.KeyDeleteAsync(keys.Select(key => (RedisKey)(Instance + key)).ToArray());
try
{
await cache.KeyDeleteAsync(keys.Select(key => InstancePrefix.Append(key)).ToArray());
}
catch (Exception ex)
{
OnRedisError(ex, cache);
throw;
}
}
protected virtual byte[]?[] GetAndRefreshMany(
IEnumerable<string> keys,
bool getData)
{
Connect();
var cache = Connect();
var keyArray = keys.Select(key => Instance + key).ToArray();
RedisValue[][] results;
var keyArray = keys.Select(key => InstancePrefix.Append( key)).ToArray();
byte[]?[] bytes;
if (getData)
try
{
results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey,
SlidingExpirationKey, DataKey);
var results = cache.HashMemberGetMany(keyArray, GetHashFields(getData));
Task.WaitAll(PipelineRefreshManyAndOutData(cache, keyArray, results, out bytes));
}
else
catch (Exception ex)
{
results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey,
SlidingExpirationKey);
OnRedisError(ex, cache);
throw;
}
Task.WaitAll(PipelineRefreshManyAndOutData(keyArray, results, out var bytes));
return bytes;
}
@ -193,29 +224,28 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{
token.ThrowIfCancellationRequested();
await ConnectAsync(token);
var cache = await ConnectAsync(token);
var keyArray = keys.Select(key => Instance + key).ToArray();
RedisValue[][] results;
var keyArray = keys.Select(key => InstancePrefix.Append(key)).ToArray();
byte[]?[] bytes;
if (getData)
try
{
results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey,
SlidingExpirationKey, DataKey);
var results = await cache.HashMemberGetManyAsync(keyArray, GetHashFields(getData));
await Task.WhenAll(PipelineRefreshManyAndOutData(cache, keyArray, results, out bytes));
}
else
catch (Exception ex)
{
results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey,
SlidingExpirationKey);
OnRedisError(ex, cache);
throw;
}
await Task.WhenAll(PipelineRefreshManyAndOutData(keyArray, results, out var bytes));
return bytes;
}
protected virtual Task[] PipelineRefreshManyAndOutData(
string[] keys,
IDatabase cache,
RedisKey[] keys,
RedisValue[][] results,
out byte[]?[] bytes)
{
@ -242,7 +272,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
expr = sldExpr;
}
tasks[i] = RedisDatabase.KeyExpireAsync(keys[i], expr);
tasks[i] = cache.KeyExpireAsync(keys[i], expr);
}
else
{
@ -264,6 +294,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
}
protected virtual Task[] PipelineSetMany(
IDatabase cache,
IEnumerable<KeyValuePair<string, byte[]>> items,
DistributedCacheEntryOptions options)
{
@ -277,14 +308,13 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
for (var i = 0; i < itemArray.Length; i++)
{
tasks[i] = RedisDatabase.ScriptEvaluateAsync(GetSetScript(), new RedisKey[] { Instance + itemArray[i].Key },
new RedisValue[]
{
absoluteExpiration?.Ticks ?? NotPresent,
tasks[i] = cache.ScriptEvaluateAsync(GetSetScript(), new RedisKey[] { InstancePrefix.Append(itemArray[i].Key) },
[
absoluteExpiration?.Ticks ?? NotPresent,
options.SlidingExpiration?.Ticks ?? NotPresent,
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent,
itemArray[i].Value
});
]);
}
return tasks;
@ -317,14 +347,21 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{
return (DateTimeOffset?)GetAbsoluteExpirationMethod.Invoke(null, new object[] { creationTime, options });
}
private IDatabase? GetRedisDatabase()
protected virtual void OnRedisError(Exception ex, IDatabase cache)
{
return _redisDatabase ??= RedisDatabaseField.GetValue(this) as IDatabase;
OnRedisErrorMethod.Invoke(this, [ex, cache]);
}
private string GetSetScript()
{
return SetScriptField.GetValue(this)!.ToString()!;
}
private static RedisValue[] GetHashFields(bool getData)
{
return getData
? HashMembersAbsoluteExpirationSlidingExpirationData
: HashMembersAbsoluteExpirationSlidingExpiration;
}
}

16
framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisExtensions.cs

@ -8,16 +8,15 @@ public static class AbpRedisExtensions
{
public static RedisValue[][] HashMemberGetMany(
this IDatabase cache,
string[] keys,
params string[] members)
RedisKey[] keys,
RedisValue[] fields)
{
var tasks = new Task<RedisValue[]>[keys.Length];
var fields = members.Select(member => (RedisValue)member).ToArray();
var results = new RedisValue[keys.Length][];
for (var i = 0; i < keys.Length; i++)
{
tasks[i] = cache.HashGetAsync((RedisKey)keys[i], fields);
tasks[i] = cache.HashGetAsync(keys[i], fields);
}
for (var i = 0; i < tasks.Length; i++)
@ -28,17 +27,16 @@ public static class AbpRedisExtensions
return results;
}
public static async Task<RedisValue[][]> HashMemberGetManyAsync(
public async static Task<RedisValue[][]> HashMemberGetManyAsync(
this IDatabase cache,
string[] keys,
params string[] members)
RedisKey[] keys,
RedisValue[] fields)
{
var tasks = new Task<RedisValue[]>[keys.Length];
var fields = members.Select(member => (RedisValue)member).ToArray();
for (var i = 0; i < keys.Length; i++)
{
tasks[i] = cache.HashGetAsync((RedisKey)keys[i], fields);
tasks[i] = cache.HashGetAsync(keys[i], fields);
}
return await Task.WhenAll(tasks);

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs

@ -83,6 +83,7 @@ public class CliService : ITransientDependency
catch (CliUsageException usageException)
{
Logger.LogWarning(usageException.Message);
Environment.ExitCode = 1;
}
catch (Exception ex)
{

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs

@ -84,7 +84,7 @@ public class UpdateCommand : IConsoleCommand, ITransientDependency
return;
}
var project = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.csproj").FirstOrDefault();
var project = Directory.GetFiles(directory, "*.csproj").FirstOrDefault();
if (project != null)
{

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

@ -37,7 +37,9 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
{
var defaultThemeName = context.BuildArgs.TemplateName is AppTemplate.TemplateName or AppNoLayersTemplate.TemplateName
? LeptonXLite : LeptonX;
new RemoveFilesStep($"/Themes/{defaultThemeName}").Execute(context);
ChangeThemeToBasicForMvcProjects(context, defaultThemeName);
ChangeThemeToBasicForBlazorProjects(context, defaultThemeName);
ChangeThemeToBasicForBlazorServerProjects(context, defaultThemeName);
@ -627,11 +629,16 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
private static void ChangeThemeToBasicForMvcProjects(ProjectBuildContext context, string defaultThemeName)
{
var projectNames = new[]
var projectNames = new List<string>
{
".Web", ".HttpApi.Host", ".AuthServer", ".Web.Public", ".Web.Public.Host",
".Web", ".AuthServer", ".Web.Public", ".Web.Public.Host",
"" //for app-nolayers-mvc
};
if(!context.Symbols.Contains("tiered"))
{
projectNames.Add(".HttpApi.Host");
}
foreach (var projectName in projectNames)
{

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

@ -1,18 +1,21 @@
using System;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
public class RemoveFileStep : ProjectBuildPipelineStep
{
private readonly string _filePath;
public RemoveFileStep(string filePath)
private readonly bool _fullPath;
public RemoveFileStep(string filePath, bool fullPath = true)
{
_filePath = filePath;
_fullPath = fullPath;
}
public override void Execute(ProjectBuildContext context)
{
var fileToRemove = context.Files.Find(x => x.Name == _filePath);;
var fileToRemove = _fullPath
? context.Files.Find(x => x.Name == _filePath)
: context.Files.Find(x => x.Name.EndsWith(_filePath));
if (fileToRemove != null)
{
context.Files.Remove(fileToRemove);

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

@ -249,6 +249,11 @@ public abstract class AppNoLayersTemplateBase : TemplateInfo
steps.Add(new ChangeThemeStyleStep());
}
if (context.BuildArgs.Theme != Theme.LeptonX)
{
steps.Add(new RemoveFileStep("LeptonXFooter.razor", false));
}
RemoveThemeLogoFolders(context, steps);
if (IsDefaultThemeForTemplate(context.BuildArgs))

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

@ -235,6 +235,10 @@ public abstract class AppTemplateBase : TemplateInfo
{
steps.Add(new ChangeThemeStyleStep());
}
else
{
steps.Add(new RemoveFileStep("LeptonXFooter.razor", false));
}
RemoveThemeLogoFolders(context, steps);

15
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs

@ -49,6 +49,10 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
steps.Add(new ChangeThemeStyleStep());
return;
}
else
{
steps.Add(new RemoveFileStep("LeptonXFooter.razor", false));
}
steps.Add(new ChangeThemeStep());
ReplaceLeptonXThemePackagesFromPackageJsonFiles(steps, uiFramework: context.BuildArgs.UiFramework, theme: context.BuildArgs.Theme, version: context.BuildArgs.Version ?? context.TemplateFile.Version);
@ -172,6 +176,8 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor", null,
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Client", null,
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor.Client"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.ProductService.Blazor",
"/services/product/MyCompanyName.MyProjectName.ProductService.sln",
"/services/product/src/MyCompanyName.MyProjectName.ProductService.Blazor"));
@ -217,6 +223,13 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor",
null,
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Client",
"/apps/blazor/MyCompanyName.MyProjectName.Blazor.sln",
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor.Client"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Client",
null,
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor.Client"));
steps.Add(new RemoveProjectFromTyeStep("blazor"));
steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.Blazor.Server",
@ -230,6 +243,8 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
case UiFramework.NotSpecified:
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor", null,
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Client", null,
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor.Client"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server", null,
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.ProductService.Blazor",

11
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs

@ -250,6 +250,17 @@ public class NpmPackagesUpdater : ITransientDependency
return false;
}
var prefix = string.Empty;
if (package.Value.ToString().StartsWith("~"))
{
prefix = "~";
}
else if (package.Value.ToString().StartsWith("^"))
{
prefix = "^";
}
version = prefix + version.RemovePreFix("~", "^");
package.Value.Replace(version);
Logger.LogInformation(

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

@ -12,12 +12,12 @@ public class CmdHelper : ICmdHelper, ITransientDependency
private const int SuccessfulExitCode = 0;
protected AbpCliOptions CliOptions { get; }
public CmdHelper(IOptionsSnapshot<AbpCliOptions> cliOptions)
{
CliOptions = cliOptions.Value;
}
public void Open(string pathOrUrl)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -43,7 +43,7 @@ public class CmdHelper : ICmdHelper, ITransientDependency
{
HideNewCommandWindow(procStartInfo);
}
Process.Start(procStartInfo)?.WaitForExit();
}
@ -74,6 +74,7 @@ public class CmdHelper : ICmdHelper, ITransientDependency
process?.WaitForExit();
exitCode = process.ExitCode;
Environment.ExitCode = exitCode;
}
}
@ -144,6 +145,7 @@ public class CmdHelper : ICmdHelper, ITransientDependency
process.WaitForExit();
exitCode = process.ExitCode;
Environment.ExitCode = exitCode;
}
return output.Trim();
@ -160,7 +162,7 @@ public class CmdHelper : ICmdHelper, ITransientDependency
{
procStartInfo.WorkingDirectory = workingDirectory;
}
if (CliOptions.AlwaysHideExternalCommandOutput)
{
HideNewCommandWindow(procStartInfo);

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Version/PackageVersionCheckerService.cs

@ -62,7 +62,7 @@ public class PackageVersionCheckerService : ITransientDependency
public async Task<LatestVersionInfo> GetLatestVersionOrNullAsync(string packageId, bool includeNightly = false, bool includeReleaseCandidates = false)
{
if (!includeNightly && !includeReleaseCandidates && !packageId.Contains("LeptonX"))
if (!includeNightly && !includeReleaseCandidates && !packageId.Contains("LeptonX") && !packageId.StartsWith("Volo.Abp.Studio."))
{
return await GetLatestStableVersionFromGithubAsync();
}

30
framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs

@ -400,6 +400,36 @@ public static class AbpStringExtensions
}
}
public static string ToSha256(this string str)
{
using (var sha = SHA256.Create())
{
var data = sha.ComputeHash(Encoding.UTF8.GetBytes(str));
var sb = new StringBuilder();
foreach (var d in data)
{
sb.Append(d.ToString("x2"));
}
return sb.ToString();
}
}
public static string ToSha512(this string str)
{
using (var sha = SHA512.Create())
{
var data = sha.ComputeHash(Encoding.UTF8.GetBytes(str));
var sb = new StringBuilder();
foreach (var d in data)
{
sb.Append(d.ToString("x2"));
}
return sb.ToString();
}
}
/// <summary>
/// Converts camelCase string to PascalCase string.
/// </summary>

16
framework/src/Volo.Abp.Core/Volo/Abp/Check.cs

@ -10,7 +10,7 @@ public static class Check
{
[ContractAnnotation("value:null => halt")]
public static T NotNull<T>(
T? value,
[System.Diagnostics.CodeAnalysis.NotNull] T? value,
[InvokerParameterName][NotNull] string parameterName)
{
if (value == null)
@ -23,7 +23,7 @@ public static class Check
[ContractAnnotation("value:null => halt")]
public static T NotNull<T>(
T? value,
[System.Diagnostics.CodeAnalysis.NotNull] T? value,
[InvokerParameterName][NotNull] string parameterName,
string message)
{
@ -37,7 +37,7 @@ public static class Check
[ContractAnnotation("value:null => halt")]
public static string NotNull(
string? value,
[System.Diagnostics.CodeAnalysis.NotNull] string? value,
[InvokerParameterName][NotNull] string parameterName,
int maxLength = int.MaxValue,
int minLength = 0)
@ -62,7 +62,7 @@ public static class Check
[ContractAnnotation("value:null => halt")]
public static string NotNullOrWhiteSpace(
string? value,
[System.Diagnostics.CodeAnalysis.NotNull] string? value,
[InvokerParameterName][NotNull] string parameterName,
int maxLength = int.MaxValue,
int minLength = 0)
@ -87,7 +87,7 @@ public static class Check
[ContractAnnotation("value:null => halt")]
public static string NotNullOrEmpty(
string? value,
[System.Diagnostics.CodeAnalysis.NotNull] string? value,
[InvokerParameterName][NotNull] string parameterName,
int maxLength = int.MaxValue,
int minLength = 0)
@ -111,7 +111,9 @@ public static class Check
}
[ContractAnnotation("value:null => halt")]
public static ICollection<T> NotNullOrEmpty<T>(ICollection<T>? value, [InvokerParameterName][NotNull] string parameterName)
public static ICollection<T> NotNullOrEmpty<T>(
[System.Diagnostics.CodeAnalysis.NotNull] ICollection<T>? value,
[InvokerParameterName][NotNull] string parameterName)
{
if (value == null || value.Count <= 0)
{
@ -339,7 +341,7 @@ public static class Check
}
public static T NotDefaultOrNull<T>(
T? value,
[System.Diagnostics.CodeAnalysis.NotNull] T? value,
[InvokerParameterName][NotNull] string parameterName)
where T : struct
{

2
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs

@ -60,7 +60,7 @@ public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider
interfaceName = interfaceName.Right(interfaceName.Length - 1);
}
if (type.Name.EndsWith(interfaceName))
if (type.Name.EndsWith(interfaceName, StringComparison.OrdinalIgnoreCase))
{
serviceTypes.Add(interfaceType);
}

8
framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/MySQL/AbpEntityFrameworkCoreMySQLModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.Guids;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
namespace Volo.Abp.EntityFrameworkCore.MySQL;
@ -17,5 +18,10 @@ public class AbpEntityFrameworkCoreMySQLModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

6
framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
@ -18,5 +19,10 @@ public class AbpEntityFrameworkCoreOracleDevartModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

6
framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
@ -16,5 +17,10 @@ public class AbpEntityFrameworkCoreOracleModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

8
framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.Guids;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
namespace Volo.Abp.EntityFrameworkCore.PostgreSql;
@ -17,5 +18,10 @@ public class AbpEntityFrameworkCorePostgreSqlModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

8
framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/SqlServer/AbpEntityFrameworkCoreSqlServerModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.Guids;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
namespace Volo.Abp.EntityFrameworkCore.SqlServer;
@ -17,5 +18,10 @@ public class AbpEntityFrameworkCoreSqlServerModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAtEnd;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

9
framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs

@ -1,3 +1,4 @@
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Modularity;
namespace Volo.Abp.EntityFrameworkCore.Sqlite;
@ -7,5 +8,11 @@ namespace Volo.Abp.EntityFrameworkCore.Sqlite;
)]
public class AbpEntityFrameworkCoreSqliteModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

21
framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs

@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Volo.Abp.EntityFrameworkCore;
namespace Microsoft.Extensions.DependencyInjection;
public static class AbpEfCoreDbContextOptionsBuilderExtensions
{
public static DbContextOptionsBuilder AddAbpDbContextOptionsExtension(this DbContextOptionsBuilder optionsBuilder)
{
((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(new AbpDbContextOptionsExtension());
return optionsBuilder;
}
public static DbContextOptionsBuilder<TContext> AddAbpDbContextOptionsExtension<TContext>(this DbContextOptionsBuilder<TContext> optionsBuilder)
where TContext : DbContext
{
((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(new AbpDbContextOptionsExtension());
return optionsBuilder;
}
}

68
framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs

@ -0,0 +1,68 @@
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.MultiTenancy;
namespace Microsoft.Extensions.DependencyInjection;
public static class AbpEfCoreModelBuilderExtensions
{
public static ModelBuilder ConfigureSoftDeleteDbFunction(this ModelBuilder modelBuilder, MethodInfo methodInfo, AbpEfCoreCurrentDbContext abpEfCoreCurrentDbContext)
{
modelBuilder.HasDbFunction(methodInfo)
.HasTranslation(args =>
{
// (bool isDeleted, bool boolParam)
var isDeleted = args[0];
var boolParam = args[1];
if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<ISoftDelete>() == true)
{
// IsDeleted == false
return new SqlBinaryExpression(
ExpressionType.Equal,
isDeleted,
new SqlConstantExpression(Expression.Constant(false), boolParam.TypeMapping),
boolParam.Type,
boolParam.TypeMapping);
}
// empty where sql
return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping);
});
return modelBuilder;
}
public static ModelBuilder ConfigureMultiTenantDbFunction(this ModelBuilder modelBuilder, MethodInfo methodInfo, AbpEfCoreCurrentDbContext abpEfCoreCurrentDbContext)
{
modelBuilder.HasDbFunction(methodInfo)
.HasTranslation(args =>
{
// (Guid? tenantId, int? currentTenantId)
var tenantId = args[0];
var currentTenantId = args[1];
var boolParam = args[2];
if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<IMultiTenant>() == true)
{
// TenantId == CurrentTenantId
return new SqlBinaryExpression(
ExpressionType.Equal,
tenantId,
currentTenantId,
boolParam.Type,
boolParam.TypeMapping);
}
// empty where sql
return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping);
});
return modelBuilder;
}
}

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

@ -11,11 +11,12 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
@ -24,6 +25,7 @@ using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EntityFrameworkCore.ChangeTrackers;
using Volo.Abp.EntityFrameworkCore.EntityHistory;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.EntityFrameworkCore.ValueConverters;
using Volo.Abp.EventBus.Distributed;
@ -37,7 +39,7 @@ using Volo.Abp.Uow;
namespace Volo.Abp.EntityFrameworkCore;
public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext, ITransientDependency
public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext, IAbpEfCoreDbFunctionContext, ITransientDependency
where TDbContext : DbContext
{
public IAbpLazyServiceProvider LazyServiceProvider { get; set; } = default!;
@ -78,6 +80,8 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
public IOptions<AbpDbContextOptions> Options => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDbContextOptions>>();
public IOptions<AbpEfCoreGlobalFilterOptions> GlobalFilterOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpEfCoreGlobalFilterOptions>>();
private static readonly MethodInfo ConfigureBasePropertiesMethodInfo
= typeof(AbpDbContext<TDbContext>)
.GetMethod(
@ -99,9 +103,12 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
BindingFlags.Instance | BindingFlags.NonPublic
)!;
protected readonly DbContextOptions DbContextOptions;
protected AbpDbContext(DbContextOptions<TDbContext> options)
: base(options)
{
DbContextOptions = options;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
@ -729,7 +736,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
{
if (mutableEntityType.BaseType == null && ShouldFilterEntity<TEntity>(mutableEntityType))
{
var filterExpression = CreateFilterExpression<TEntity>();
var filterExpression = CreateFilterExpression<TEntity>(modelBuilder);
if (filterExpression != null)
{
modelBuilder.Entity<TEntity>().HasAbpQueryFilter(filterExpression);
@ -798,22 +805,44 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
return false;
}
protected virtual Expression<Func<TEntity, bool>>? CreateFilterExpression<TEntity>()
protected virtual Expression<Func<TEntity, bool>>? CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
where TEntity : class
{
Expression<Func<TEntity, bool>>? expression = null;
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(e, "IsDeleted");
var softDeleteColumnName = modelBuilder.Entity<TEntity>().Metadata.FindProperty(nameof(ISoftDelete.IsDeleted))?.GetColumnName() ?? "IsDeleted";
expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(e, softDeleteColumnName);
if (UseDbFunction())
{
expression = e => AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilter(((ISoftDelete)e).IsDeleted, true);
modelBuilder.ConfigureSoftDeleteDbFunction(AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilterMethodInfo, this.GetService<AbpEfCoreCurrentDbContext>());
}
}
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(e, "TenantId") == CurrentTenantId;
var multiTenantColumnName = modelBuilder.Entity<TEntity>().Metadata.FindProperty(nameof(IMultiTenant.TenantId))?.GetColumnName() ?? "TenantId";
Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(e, multiTenantColumnName) == CurrentTenantId;
if (UseDbFunction())
{
multiTenantFilter = e => AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilter(((IMultiTenant)e).TenantId, CurrentTenantId, true);
modelBuilder.ConfigureMultiTenantDbFunction(AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilterMethodInfo, this.GetService<AbpEfCoreCurrentDbContext>());
}
expression = expression == null ? multiTenantFilter : QueryFilterExpressionHelper.CombineExpressions(expression, multiTenantFilter);
}
return expression;
}
protected virtual bool UseDbFunction()
{
return LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction && DbContextOptions.FindExtension<AbpDbContextOptionsExtension>() != null;
}
public virtual string GetCompiledQueryCacheKey()
{
return $"{CurrentTenantId?.ToString() ?? "Null"}:{IsSoftDeleteFilterEnabled}:{IsMultiTenantFilterEnabled}";
}
}

62
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
namespace Volo.Abp.EntityFrameworkCore;
public class AbpDbContextOptionsExtension : IDbContextOptionsExtension
{
public void ApplyServices(IServiceCollection services)
{
var serviceDescriptor = services.FirstOrDefault(x => x.ServiceType == typeof(ICompiledQueryCacheKeyGenerator));
if (serviceDescriptor != null && serviceDescriptor.ImplementationType != null)
{
services.Remove(serviceDescriptor);
services.AddScoped(serviceDescriptor.ImplementationType);
services.Add(ServiceDescriptor.Scoped<ICompiledQueryCacheKeyGenerator>(provider =>
ActivatorUtilities.CreateInstance<AbpCompiledQueryCacheKeyGenerator>(provider,
provider.GetRequiredService(serviceDescriptor.ImplementationType)
.As<ICompiledQueryCacheKeyGenerator>())));
}
services.Replace(ServiceDescriptor.Scoped<IAsyncQueryProvider, AbpEntityQueryProvider>());
services.AddSingleton(typeof(AbpEfCoreCurrentDbContext));
}
public void Validate(IDbContextOptions options)
{
}
public DbContextOptionsExtensionInfo Info => new AbpOptionsExtensionInfo(this);
private class AbpOptionsExtensionInfo : DbContextOptionsExtensionInfo
{
public AbpOptionsExtensionInfo(IDbContextOptionsExtension extension)
: base(extension)
{
}
public override bool IsDatabaseProvider => false;
public override int GetServiceProviderHashCode()
{
return 0;
}
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
{
return other is AbpOptionsExtensionInfo;
}
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
{
}
public override string LogFragment => "AbpOptionsExtension";
}
}

49
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs

@ -0,0 +1,49 @@
using System.Linq.Expressions;
using System.Threading;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
namespace Volo.Abp.EntityFrameworkCore;
#pragma warning disable EF1001
public class AbpEntityQueryProvider : EntityQueryProvider
{
protected AbpEfCoreCurrentDbContext AbpEfCoreCurrentDbContext { get; }
protected ICurrentDbContext CurrentDbContext { get; }
public AbpEntityQueryProvider(
IQueryCompiler queryCompiler,
AbpEfCoreCurrentDbContext abpEfCoreCurrentDbContext,
ICurrentDbContext currentDbContext)
: base(queryCompiler)
{
AbpEfCoreCurrentDbContext = abpEfCoreCurrentDbContext;
CurrentDbContext = currentDbContext;
}
public override object Execute(Expression expression)
{
using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext))
{
return base.Execute(expression);
}
}
public override TResult Execute<TResult>(Expression expression)
{
using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext))
{
return base.Execute<TResult>(expression);
}
}
public override TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = new CancellationToken())
{
using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext))
{
return base.ExecuteAsync<TResult>(expression, cancellationToken);
}
}
}
#pragma warning restore EF1001

6
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs

@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.EntityFrameworkCore.DependencyInjection;
@ -27,6 +28,11 @@ public static class DbContextOptionsFactory
PreConfigure(options, context);
Configure(options, context);
if (serviceProvider.GetRequiredService<IOptions<AbpEfCoreGlobalFilterOptions>>().Value.UseDbFunction)
{
context.DbContextOptions.AddAbpDbContextOptionsExtension();
}
return context.DbContextOptions.Options;
}

59
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs

@ -0,0 +1,59 @@
using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
namespace Volo.Abp.EntityFrameworkCore.GlobalFilters;
public class AbpCompiledQueryCacheKeyGenerator : ICompiledQueryCacheKeyGenerator
{
protected ICompiledQueryCacheKeyGenerator InnerCompiledQueryCacheKeyGenerator { get; }
protected ICurrentDbContext CurrentContext { get; }
public AbpCompiledQueryCacheKeyGenerator(
ICompiledQueryCacheKeyGenerator innerCompiledQueryCacheKeyGenerator,
ICurrentDbContext currentContext)
{
InnerCompiledQueryCacheKeyGenerator = innerCompiledQueryCacheKeyGenerator;
CurrentContext = currentContext;
}
public virtual object GenerateCacheKey(Expression query, bool async)
{
var cacheKey = InnerCompiledQueryCacheKeyGenerator.GenerateCacheKey(query, async);
if (CurrentContext.Context is IAbpEfCoreDbFunctionContext abpEfCoreDbFunctionContext)
{
return new AbpCompiledQueryCacheKey(cacheKey, abpEfCoreDbFunctionContext.GetCompiledQueryCacheKey());
}
return cacheKey;
}
private readonly struct AbpCompiledQueryCacheKey : IEquatable<AbpCompiledQueryCacheKey>
{
private readonly object _compiledQueryCacheKey;
private readonly string _currentFilterCacheKey;
public AbpCompiledQueryCacheKey(object compiledQueryCacheKey, string currentFilterCacheKey)
{
_compiledQueryCacheKey = compiledQueryCacheKey;
_currentFilterCacheKey = currentFilterCacheKey;
}
public override bool Equals(object? obj)
{
return obj is AbpCompiledQueryCacheKey key && Equals(key);
}
public bool Equals(AbpCompiledQueryCacheKey other)
{
return _compiledQueryCacheKey.Equals(other._compiledQueryCacheKey) &&
_currentFilterCacheKey == other._currentFilterCacheKey;
}
public override int GetHashCode()
{
return HashCode.Combine(_compiledQueryCacheKey, _currentFilterCacheKey);
}
}
}

21
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs

@ -0,0 +1,21 @@
using System;
using System.Threading;
namespace Volo.Abp.EntityFrameworkCore.GlobalFilters;
public class AbpEfCoreCurrentDbContext
{
private readonly AsyncLocal<IAbpEfCoreDbFunctionContext?> _current = new AsyncLocal<IAbpEfCoreDbFunctionContext?>();
public IAbpEfCoreDbFunctionContext? Context => _current.Value;
public IDisposable Use(IAbpEfCoreDbFunctionContext? context)
{
var previousValue = Context;
_current.Value = context;
return new DisposeAction(() =>
{
_current.Value = previousValue;
});
}
}

25
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs

@ -0,0 +1,25 @@
using System;
using System.Reflection;
namespace Volo.Abp.EntityFrameworkCore.GlobalFilters;
public static class AbpEfCoreDataFilterDbFunctionMethods
{
public const string NotSupportedExceptionMessage = "Your EF Core database provider does not support 'User-defined function mapping'." +
"Please set 'UseDbFunction' of 'AbpEfCoreGlobalFilterOptions' to false to disable it." +
"See https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping for more information." ;
public static bool SoftDeleteFilter(bool isDeleted, bool boolParam)
{
throw new NotSupportedException(NotSupportedExceptionMessage);
}
public static MethodInfo SoftDeleteFilterMethodInfo => typeof(AbpEfCoreDataFilterDbFunctionMethods).GetMethod(nameof(SoftDeleteFilter))!;
public static bool MultiTenantFilter(Guid? tenantId, Guid? currentTenantId, bool boolParam)
{
throw new NotSupportedException(NotSupportedExceptionMessage);
}
public static MethodInfo MultiTenantFilterMethodInfo => typeof(AbpEfCoreDataFilterDbFunctionMethods).GetMethod(nameof(MultiTenantFilter))!;
}

10
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs

@ -0,0 +1,10 @@
namespace Volo.Abp.EntityFrameworkCore.GlobalFilters;
public class AbpEfCoreGlobalFilterOptions
{
/// <summary>
/// Use User-defined function mapping to filter data.
/// https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping
/// </summary>
public bool UseDbFunction { get; set; }
}

16
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs

@ -0,0 +1,16 @@
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.EntityFrameworkCore.GlobalFilters;
public interface IAbpEfCoreDbFunctionContext
{
IAbpLazyServiceProvider LazyServiceProvider { get; set; }
ICurrentTenant CurrentTenant { get; }
IDataFilter DataFilter { get; }
string GetCompiledQueryCacheKey();
}

11
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs

@ -80,7 +80,16 @@ public class DefaultExceptionToErrorInfoConverter : IExceptionToErrorInfoConvert
if (exception is AbpRemoteCallException remoteCallException && remoteCallException.Error != null)
{
return remoteCallException.Error;
var remoteServiceErrorInfo = remoteCallException.Error;
if (remoteServiceErrorInfo.Message == AbpExceptionHandlingConsts.Unauthorized)
{
remoteServiceErrorInfo.Message = L[AbpExceptionHandlingConsts.Unauthorized];
}
if (remoteServiceErrorInfo.Details == AbpExceptionHandlingConsts.SessionExpired)
{
remoteServiceErrorInfo.Details = L[AbpExceptionHandlingConsts.SessionExpired];
}
return remoteServiceErrorInfo;
}
if (exception is AbpDbConcurrencyException)

8
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/AbpExceptionHandlingConsts.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.ExceptionHandling;
public static class AbpExceptionHandlingConsts
{
public const string Unauthorized = "Unauthorized";
public const string InvalidToken = "invalid_token";
public const string SessionExpired = "SessionExpired";
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ar.json

@ -21,6 +21,9 @@
"404Message": "الصفحة غير موجودة",
"500Message": "خطأ في الخادم الداخلي",
"403MessageDetail": "أنت غير مصرح لك لإجراء هذه العملية!",
"404MessageDetail": "عذرا ، لا يوجد شيء في هذا العنوان."
"404MessageDetail": "عذرا ، لا يوجد شيء في هذا العنوان.",
"Unauthorized": "غير مصرح",
"invalid_token": "الرمز غير صالح",
"SessionExpired": "انتهت جلستك. يرجى تسجيل الدخول مرة أخرى لمتابعة التطبيق."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/cs.json

@ -21,6 +21,9 @@
"404Message": "Stránka nenalezena",
"500Message": "Interní chyba serveru",
"403MessageDetail": "K provedení této operace nemáte oprávnění!",
"404MessageDetail": "Je nám líto, na této adrese nic není."
"404MessageDetail": "Je nám líto, na této adrese nic není.",
"Unauthorized": "Neoprávněno",
"invalid_token": "Neplatný token",
"SessionExpired": "Vaše relace vypršela. Přihlaste se znovu, abyste mohli pokračovat v aplikaci."
}
}
}

5
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/de.json

@ -21,6 +21,9 @@
"404Message": "Seite nicht gefunden",
"500Message": "Interner Serverfehler",
"403MessageDetail": "Sie sind nicht berechtigt, diesen Vorgang durchzuführen!",
"404MessageDetail": "Entschuldigung, unter dieser Adresse ist nichts."
"404MessageDetail": "Entschuldigung, unter dieser Adresse ist nichts.",
"Unauthorized": "Nicht autorisiert",
"invalid_token": "Ungültiges Token",
"SessionExpired": "Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an, um in der Anwendung fortzufahren."
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/el.json

@ -21,6 +21,9 @@
"404Message": "Η σελίδα δεν βρέθηκε",
"500Message": "Εσωτερικό Σφάλμα Διακομιστή",
"403MessageDetail": "Δεν είστε εξουσιοδοτημένοι να εκτελέσετε αυτήν τη λειτουργία!",
"404MessageDetail": "Λυπούμαστε, δεν υπάρχει τίποτα σε αυτή τη διεύθυνση."
"404MessageDetail": "Λυπούμαστε, δεν υπάρχει τίποτα σε αυτή τη διεύθυνση.",
"Unauthorized": "Μη εξουσιοδοτημένος",
"invalid_token": "Το token δ",
"SessionExpired": "Η συνεδρία σας έχει λήξει. Παρακαλώ συνδεθείτε ξανά για να συνεχίσετε στην εφαρμογή."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en-GB.json

@ -20,6 +20,9 @@
"404Message": "Page not found",
"500Message": "Internal Server Error",
"403MessageDetail": "You are not authorised to perform this operation!",
"404MessageDetail": "Sorry, there's nothing at this address."
"404MessageDetail": "Sorry, there's nothing at this address.",
"Unauthorized": "Unauthorized",
"invalid_token": "Invalid token",
"SessionExpired": "Your session has expired. Please login again to continue in the application."
}
}
}

9
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en.json

@ -5,7 +5,7 @@
"ValidationErrorMessage": "Your request is not valid!",
"ValidationNarrativeErrorMessageTitle": "The following errors were detected during validation.",
"DefaultErrorMessage": "An error has occurred!",
"DefaultErrorMessageDetail": "Error detail not sent by server.",
"DefaultErrorMessageDetail": "Error detail not sent by the server.",
"DefaultErrorMessage401": "You are not authenticated!",
"DefaultErrorMessage401Detail": "You should sign in to perform this operation.",
"DefaultErrorMessage403": "You are not authorized!",
@ -13,7 +13,7 @@
"DefaultErrorMessage404": "Resource not found!",
"DefaultErrorMessage404Detail": "The resource requested could not be found on the server!",
"EntityNotFoundErrorMessage": "There is no entity {0} with id = {1}!",
"AbpDbConcurrencyErrorMessage": "The data you have submitted has already changed by another user/client. Please discard the changes you've done and try from the beginning.",
"AbpDbConcurrencyErrorMessage": "The data you have submitted has already been changed by another user. Discard your changes and try again.",
"Error": "Error",
"UnhandledException": "Unhandled exception!",
"401Message": "Unauthorized",
@ -21,6 +21,9 @@
"404Message": "Page not found",
"500Message": "Internal Server Error",
"403MessageDetail": "You are not authorized to perform this operation!",
"404MessageDetail": "Sorry, there's nothing at this address."
"404MessageDetail": "Sorry, there's nothing at this address.",
"Unauthorized": "Unauthorized",
"invalid_token": "Invalid token",
"SessionExpired": "Your session has expired. Please login again to continue in the application."
}
}

5
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/es.json

@ -21,6 +21,9 @@
"404Message": "Página no encontrada",
"500Message": "Error interno de servidor",
"403MessageDetail": "Tu no estás autorizado para realizar esta operación!",
"404MessageDetail": "Disculpa, no hay nada en esta dirección."
"404MessageDetail": "Disculpa, no hay nada en esta dirección.",
"Unauthorized": "No autorizado",
"invalid_token": "El token no es válido.",
"SessionExpired": "Tu sesión ha expirado. Por favor inicia sesión de nuevo para continuar en la aplicación."
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/fa.json

@ -21,6 +21,9 @@
"404Message": "صفحه موردنظر یافت نشد",
"500Message": "خطای داخلی رخ داد",
"403MessageDetail": "شما مجاز به انجام این عملیات نمی باشید!",
"404MessageDetail": "با عرض پورش، در این آدرس هیچ محتوایی وجود ندارد."
"404MessageDetail": "با عرض پورش، در این آدرس هیچ محتوایی وجود ندارد.",
"Unauthorized": "غیر مجاز",
"invalid_token": "توکن نامعتبر است",
"SessionExpired": "جلسه شما منقضی شده است. لطفاً مجددا وارد شوید تا در برنامه ادامه دهید."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/fi.json

@ -21,6 +21,9 @@
"404Message": "Sivua ei löydetty",
"500Message": "Sisäinen palvelinvirhe",
"403MessageDetail": "Sinulla ei ole oikeutta suorittaa tätä toimintoa!",
"404MessageDetail": "Valitettavasti tässä osoitteessa ei ole mitään."
"404MessageDetail": "Valitettavasti tässä osoitteessa ei ole mitään.",
"Unauthorized": "Sinulla ei ole oikeutta suorittaa tätä toimintoa!",
"invalid_token": "Virheellinen token",
"SessionExpired": "Istuntosi on vanhentunut. Kirjaudu sisään jatkaaksesi sovelluksessa."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/fr.json

@ -21,6 +21,9 @@
"404Message": "Page introuvable",
"500Message": "Erreur Interne du Serveur",
"403MessageDetail": "Vous n'êtes pas autorisé à effectuer cette opération!",
"404MessageDetail": "Désolé, il n'y a rien à cette adresse."
"404MessageDetail": "Désolé, il n'y a rien à cette adresse.",
"Unauthorized": "Non autorisé",
"invalid_token": "Le jeton n'est pas valide.",
"SessionExpired": "Votre session a expiré. Veuillez vous reconnecter pour continuer dans l'application."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/hi.json

@ -21,6 +21,9 @@
"404Message": "पृष्ठ नहीं मिला",
"500Message": "आंतरिक सर्वर त्रुटि",
"403MessageDetail": "आप इस ऑपरेशन को करने के लिए अधिकृत नहीं हैं!",
"404MessageDetail": "क्षमा करें, इस पते पर कुछ भी नहीं है।"
"404MessageDetail": "क्षमा करें, इस पते पर कुछ भी नहीं है।",
"Unauthorized": "अनधिकृत",
"invalid_token": "टोकन अमान्य है",
"SessionExpired": "आपकी सत्र समाप्त हो गई है। कृपया आवेदन में जारी रखने के लिए फिर से लॉगिन करें।"
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/hr.json

@ -21,6 +21,9 @@
"404Message": "Stranica nije pronađena",
"500Message": "Interna pogreška poslužitelja",
"403MessageDetail": "Niste ovlašteni za izvođenje ove operacije!",
"404MessageDetail": "Nažalost, na ovoj adresi nema ničega."
"404MessageDetail": "Nažalost, na ovoj adresi nema ničega.",
"Unauthorized": "Niste ovlašteni za izvođenje ove operacije.",
"invalid_token": "Neispravan token",
"SessionExpired": "Vaša sesija je istekla. Prijavite se ponovno kako biste nastavili s radom u aplikaciji."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/hu.json

@ -21,6 +21,9 @@
"404Message": "Az oldal nem található",
"500Message": "Belső Szerverhiba",
"403MessageDetail": "Ön nem jogosult ennek a műveletnek a végrehajtására!",
"404MessageDetail": "Sajnos ezen a címen nincs semmi."
"404MessageDetail": "Sajnos ezen a címen nincs semmi.",
"Unauthorized": "Nem jogosult",
"invalid_token": "A token érv",
"SessionExpired": "A munkamenete lejárt. Kérjük, jelentkezzen be újra az alkalmazásban való folytatáshoz."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/is.json

@ -21,6 +21,9 @@
"404Message": "Síðan fannst ekki",
"500Message": "Villa í netþjóni",
"403MessageDetail": "Þú hefur ekki heimild fyrir þessa aðgerð!",
"404MessageDetail": "Því miður, það er ekkert á þessu heimilisfangi."
"404MessageDetail": "Því miður, það er ekkert á þessu heimilisfangi.",
"Unauthorized": "Óheimilt",
"invalid_token": "Ógildur auðkenni",
"SessionExpired": "Sessían þín er útrunnin. Vinsamlegast skráðu þig inn aftur til að halda áfram í forritinu."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/it.json

@ -21,6 +21,9 @@
"404Message": "pagina non trovata",
"500Message": "Errore interno del server",
"403MessageDetail": "Non sei autorizzato a eseguire questa operazione!",
"404MessageDetail": "Spiacenti, non c'è niente a questo indirizzo."
"404MessageDetail": "Spiacenti, non c'è niente a questo indirizzo.",
"Unauthorized": "Non autorizzato",
"invalid_token": "Token non valido",
"SessionExpired": "La tua sessione è scaduta. Effettua nuovamente il login per continuare nell'applicazione."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/nl.json

@ -21,6 +21,9 @@
"404Message": "Pagina niet gevonden",
"500Message": "Interne Server Fout",
"403MessageDetail": "U bent niet bevoegd om deze handeling uit te voeren!",
"404MessageDetail": "Sorry, op dit adres is niks gevonden."
"404MessageDetail": "Sorry, op dit adres is niks gevonden.",
"Unauthorized": "Niet geautoriseerd",
"invalid_token": "Ongeldig token",
"SessionExpired": "Uw sessie is verlopen. Log opnieuw in om door te gaan in de applicatie."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/pl-PL.json

@ -21,6 +21,9 @@
"404Message": "Strona nie znaleziona",
"500Message": "Wewnętrzny błąd serwera",
"403MessageDetail": "Nie masz uprawnień do wykonania tej operacji!",
"404MessageDetail": "Przepraszamy, pod tym adresem nic nie ma."
"404MessageDetail": "Przepraszamy, pod tym adresem nic nie ma.",
"Unauthorized": "Nieautoryzowany",
"invalid_token": "Nieprawidłowy token",
"SessionExpired": "Twoja sesja wygasła. Zaloguj się ponownie, aby kontynuować w aplikacji."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/pt-BR.json

@ -21,6 +21,9 @@
"404Message": "Página não encontrada",
"500Message": "Erro interno do servidor",
"403MessageDetail": "Você não está autorizado a realizar esta operação!",
"404MessageDetail": "Desculpe, não há nada neste endereço."
"404MessageDetail": "Desculpe, não há nada neste endereço.",
"Unauthorized": "Não autorizado",
"invalid_token": "Token inválido",
"SessionExpired": "Sua sessão expirou. Por favor, faça login novamente para continuar na aplicação."
}
}
}

5
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ro-RO.json

@ -21,6 +21,9 @@
"404Message": "Pagina nu a fost găsită",
"500Message": "Eroare internă a serverului",
"403MessageDetail": "Nu sunteţi autorizat să efectuaţi această operaţie!",
"404MessageDetail": "Ne cerem scuze, nu este nimic la această adresă."
"404MessageDetail": "Ne cerem scuze, nu este nimic la această adresă.",
"Unauthorized": "Nu sunteţi autorizat să efectuaţi această operaţie.",
"invalid_token": "Tokenul nu este valid sau a expirat.",
"SessionExpired": "Sesiunea dumneavoastră a expirat. Vă rugăm să vă autentificaţi din nou pentru a continua în aplicaţie."
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ru.json

@ -21,6 +21,9 @@
"404Message": "Страница не найдена",
"500Message": "Внутренняя ошибка сервера",
"403MessageDetail": "У вас нет прав на выполнение этой операции!",
"404MessageDetail": "Извините, по этому адресу ничего нет."
"404MessageDetail": "Извините, по этому адресу ничего нет.",
"Unauthorized": "Неавторизованный",
"invalid_token": "Неверный токен",
"SessionExpired": "Ваша сессия истекла. Пожалуйста, войдите снова, чтобы продолжить работу в приложении."
}
}
}

7
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/sk.json

@ -21,6 +21,9 @@
"404Message": "Stránka nebola nájdená",
"500Message": "Interná chyba servera",
"403MessageDetail": "Nie ste oprávnený vykonávať túto operáciu!",
"404MessageDetail": "Je nám ľúto, ale na tejto adrese sa nič nenachádza."
"404MessageDetail": "Je nám ľúto, ale na tejto adrese sa nič nenachádza.",
"Unauthorized": "Neoprávnený",
"invalid_token": "Neplatný token",
"SessionExpired": "Vaša relácia vypršala. Prihláste sa znova, aby ste mohli pokračovať v aplikácii."
}
}
}

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

Loading…
Cancel
Save