(options =>
+ {
+ options.EntityTypes.Add(new CommentEntityTypeDefinition("Comment"));
+ options.IsRecaptchaEnabled = true;
+ });
+```
+
+Here, we simply defining what should be the entity-type name of our comment and also enable the reCaptcha for the comment system. After this configuration, now we can open the `Index.cshtml` file in the `*.Web` project and invoke the `CommentingViewComponent` as below:
+
+```html
+@page
+@using Microsoft.AspNetCore.Mvc.Localization
+@using SentimentAnalysisDemo.Localization
+@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting
+@model SentimentAnalysisDemo.Web.Pages.IndexModel
+
+
+
Comments:
+
+ @await Component.InvokeAsync(typeof(CommentingViewComponent), new
+ {
+ entityType = "Comment",
+ entityId = "SentimentAnalysisDemo",
+ isReadOnly = false
+ })
+
+
+```
+
+After adding the related component, now you can run the web project and see the comment component if you want.
+
+### Applying Sentiment Analysis (Creating the Spam Detection Service)
+
+By default, CMS Kit's Comment Feature does not provide a spam detection system. In this demo application, we will override the `CommentPublicAppService`'s `CreateAsync` and `UpdateAsync` methods and then will add the spam detection control whenever a new comment has been submitted or an existing one is being updated.
+
+To override the `CommentPublicAppService` and extend its use-case implementations, create a `MyCommentAppService` class and update its content as below:
+
+```csharp
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+using SentimentAnalysisDemo.ML;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.EventBus.Distributed;
+using Volo.CmsKit.Comments;
+using Volo.CmsKit.Public.Comments;
+using Volo.CmsKit.Users;
+
+namespace SentimentAnalysisDemo.Volo.CmsKit.Public.Comments;
+
+[Dependency(ReplaceServices = true)]
+[ExposeServices(typeof(ICommentPublicAppService), typeof(CommentPublicAppService), typeof(MyCommentAppService))]
+public class MyCommentAppService : CommentPublicAppService
+{
+ protected ISpamDetector SpamDetector { get; }
+
+ public MyCommentAppService(
+ ICommentRepository commentRepository,
+ ICmsUserLookupService cmsUserLookupService,
+ IDistributedEventBus distributedEventBus,
+ CommentManager commentManager,
+ IOptionsSnapshot cmsCommentOptions,
+ ISpamDetector spamDetector
+ )
+ : base(commentRepository, cmsUserLookupService, distributedEventBus, commentManager, cmsCommentOptions)
+ {
+ SpamDetector = spamDetector;
+ }
+
+ public override async Task CreateAsync(string entityType, string entityId, CreateCommentInput input)
+ {
+ //Check message: spam or ham.
+ await SpamDetector.CheckAsync(input.Text);
+
+ return await base.CreateAsync(entityType, entityId, input);
+ }
+
+ public override async Task UpdateAsync(Guid id, UpdateCommentInput input)
+ {
+ //Check message: spam or ham.
+ await SpamDetector.CheckAsync(input.Text);
+
+ return await base.UpdateAsync(id, input);
+ }
+}
+```
+
+Here, we simply just inject the `ISpamDetector` service, which we will create in a minute, and use its `CheckAsync` method to make a spam check before the comment is created or updated.
+
+Now, we can create the `ISpamDetector` service in the `*.Application.Contracts` project as follows:
+
+```csharp
+using System.Threading.Tasks;
+
+namespace SentimentAnalysisDemo.ML;
+
+public interface ISpamDetector
+{
+ Task CheckAsync(string text);
+}
+```
+
+Then, we can create the `SpamDetector` and implement the `ISpamDetector` interface (in the `*.Application` project):
+
+```csharp
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.ML;
+using SentimentAnalysisDemo.ML.Model;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+
+namespace SentimentAnalysisDemo.ML;
+
+public class SpamDetector : ISpamDetector, ITransientDependency
+{
+ public async Task CheckAsync(string text)
+ {
+ //check if the text contains a spam content or not...
+
+ }
+}
+```
+
+The `CheckAsync` method is where we need to make the sentiment analysis and detect if the comment contains spam content or not. If it's spam, then we should throw a [UserFriendlyException](https://docs.abp.io/en/abp/latest/Exception-Handling#user-friendly-exception) and notify the user that the comment should be updated and should not contain any spam content.
+
+#### Spam Detection
+
+Before, making the spam check, we should have a dataset to train a machine-learning model and add `Microsoft.ML` package into our project. For that purpose, I searched in [Kaggle](https://www.kaggle.com/) for spam datasets, found the **Spam-Mail-Detection-Dataset** from Kaggle, and downloaded the csv file to use in my application. Therefore, [you should also download the dataset from the link and put it under the **/ML/Data/spam_data.csv** directory of the `*.Web` project](https://github.com/EngincanV/SentimentAnalysisDemo/blob/master/src/SentimentAnalysisDemo.Web/ML/Data/spam_data.csv).
+
+Here is what our dataset looks like (**0 -> not spam / 1 -> spam**):
+
+| Category | Message |
+|----------|---------|
+| 0 | Is that seriously how you spell his name? |
+| 1 | Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's |
+| . | . |
+| . | . |
+| . | . |
+
+> **Note:** This dataset is not ready-to use in a real-world solution. It's for mail spam detection but for the simplicity of the sample, it's not important and can be used for development purposes.
+
+After, downloading the dataset and putting it in the directory of **/ML/Data**, now we can add the `Microsoft.ML` package into our `*.Application` project:
+
+```bash
+dotnet add package Microsoft.ML
+```
+
+Finally, we can implement the `CheckAsync` method and use sentiment analysis to make spam checks as follows:
+
+```csharp
+
+ public async Task CheckAsync(string text)
+ {
+ var dataPath = Path.Combine(Environment.CurrentDirectory, "ML", "Data", "spam_data.csv");
+
+ var mlContext = new MLContext();
+
+ //Step 1: Load Data 👇
+ IDataView dataView = mlContext.Data.LoadFromTextFile(dataPath, hasHeader: true, separatorChar: ',');
+
+ //Step 2: Split data to train-test data 👇
+ DataOperationsCatalog.TrainTestData trainTestSplit = mlContext.Data.TrainTestSplit(dataView, testFraction: 0.2);
+ IDataView trainingData = trainTestSplit.TrainSet; //80% of the data.
+ IDataView testData = trainTestSplit.TestSet; //20% of the data.
+
+ //Step 3: Common data process configuration with pipeline data transformations + choose and set the training algorithm 👇
+ var estimator = mlContext.Transforms.Text.FeaturizeText(outputColumnName: "Features", inputColumnName: nameof(SentimentAnalyzeInput.Message))
+ .Append(mlContext.BinaryClassification.Trainers.SdcaLogisticRegression(labelColumnName: "Label", featureColumnName: "Features"));
+
+ //Step 4: Train the model 👇
+ ITransformer model = estimator.Fit(trainingData);
+
+ //Step 5: Predict 👇
+ var sentimentAnalyzeInput = new SentimentAnalyzeInput
+ {
+ Message = text
+ };
+
+ var predictionEngine = mlContext.Model.CreatePredictionEngine(model);
+ var result = predictionEngine.Predict(sentimentAnalyzeInput);
+ if (IsSpam(result))
+ {
+ throw new UserFriendlyException("Spam detected! Please update the message!");
+ }
+ }
+
+ private static bool IsSpam(SentimentAnalyzeResult result)
+ {
+ //1 -> spam / 0 -> ham (for 'Prediction' column)
+ return result is { Prediction: true, Probability: >= 0.5f };
+ }
+
+```
+
+Here, we have done the following things:
+
+1. **First, we loaded the data**: For that reason, we created a `MLContext` object, which is a main class for all ML.NET operations. Then, we used its `LoadFromTextFile` method and specified the dataset path in our application. Also, we mapped the dataset columns to the `SentimentAnalyzeInput` class, which we will create later on.
+2. **For the second step, we split the data as training and testing data**: To be able to train a machine learning model and then evaluate its accuracy, we should not use all the data for training purposes, instead, we should split the data as training and testing data and after training the model, compare the training data accuracy with the testing data.
+3. **For the third step, we should make data transformation, convert the text-based data into numeric vectors and then choose a training algorithm**: After splitting the data for training and testing purposes, now we can apply some data transformations for the *Message* column in our dataset. Because, as you would see, messages are text-based inputs and machine-learning algorithms work best with the numeric vectors. So, we are making data transformations and representing the data as numeric values. Then, we can apply `BinaryClassification` with the **SdcaLogicticRegression** algorithm to our training data.
+4. **Train the model**: Since we make the data transformations and chose the correct algorithm for our model, now we can train the model.
+5. **Predict the sample data**: Finally, we can pass a comment to this method and make spam check and either approve our reject the comment according to the predicted result. (To make predictions, we need to create a **PredictionEngine** and get the final results in the output class that we specified, `SentimentAnalyzeResult` in our example)
+
+Let's create the `SentimentAnalyzeInput` and `SentimentAnalyzeResult` classes as follows.
+
+**SentimentAnalyzeInput.cs:**
+
+```csharp
+using Microsoft.ML.Data;
+
+namespace SentimentAnalysisDemo.ML.Model;
+
+public class SentimentAnalyzeInput
+{
+ [LoadColumn(0), ColumnName("Label")]
+ public bool Category { get; set; }
+
+ [LoadColumn(1), ColumnName("Message")]
+ public string Message { get; set; }
+}
+```
+
+**SentimentAnalyzeResult.cs:**
+
+```csharp
+using Microsoft.ML.Data;
+
+namespace SentimentAnalysisDemo.ML.Model;
+
+public class SentimentAnalyzeResult
+{
+ [ColumnName("PredictedLabel")]
+ public bool Prediction { get; set; }
+
+ public float Probability { get; set; }
+
+ public float Score { get; set; }
+}
+```
+
+Then, finally, we can run the application to see the final results:
+
+
+
+## Advanced: Reusing And Optimizing Machine Learning Models
+
+Once the model is trained and evaluated, we can save the trained model and use it directly for further use. In this way, you don’t have to retrain the model every time when you want to make predictions. It’s essential to save the trained model for future use and a must for the production-ready code. I created a separate article dedicated to that topic, and if you are interested, you can read it from [here](https://engincanv.github.io/machine-learning/sentiment-analysis/best-practises/2024/05/16/reusing-and-optimizing-machine-learning-models-in-dotnet.html).
+
+## Conclusion
+
+In this article, I briefly explain what sentiment analysis is, created a sample ABP-based application, integrated the CMS Kit Module and finally, applied sentiment analysis to make spam checks whenever a new comment has been submitted or updated. You can get the source code of the demo from [https://github.com/EngincanV/SentimentAnalysisDemo](https://github.com/EngincanV/SentimentAnalysisDemo)
+
+Thanks for reading :)
diff --git a/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/cover-image.png b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/cover-image.png
new file mode 100644
index 0000000000..7091f1a6b5
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/cover-image.png differ
diff --git a/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/demo.gif b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/demo.gif
new file mode 100644
index 0000000000..5a373ba110
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/demo.gif differ
diff --git a/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/sentiment-analysis-steps.png b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/sentiment-analysis-steps.png
new file mode 100644
index 0000000000..4222b4c97c
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/sentiment-analysis-steps.png differ
diff --git a/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/sentiment-analysis.png b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/sentiment-analysis.png
new file mode 100644
index 0000000000..9597476dca
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/sentiment-analysis.png differ
diff --git a/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/POST.md b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/POST.md
new file mode 100644
index 0000000000..f94e92a548
--- /dev/null
+++ b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/POST.md
@@ -0,0 +1,228 @@
+# Deploy Your ABP Framework Angular Project to Azure Kubernetes Service (AKS)
+
+
+
+In my previous article on [Deploy Your ABP Framework MVC Project to Azure Container Apps](https://community.abp.io/posts/deploy-your-abp-framework-mvc-project-to-azure-container-apps-r93u9c6d), I talked about how ABP Mvc project can be easily deployed to Azure Container Apps. Now I will show how we can deploy to kubernetes environment, which looks a bit more complex but is more preferred for production, using a Helm chart.
+
+### Getting Started with ABP Framework Angular and Azure Kubernetes Service
+
+To get started, you will need an ABP Framework Angular project that you want to deploy. If you don't have one, you can [create a new project using the ABP CLI](https://docs.abp.io/en/abp/latest/Startup-Templates/Application). You will also need [an Azure subscription](https://azure.microsoft.com) and [an Azure Kubernetes Service](https://azure.microsoft.com/en-gb/services/kubernetes-service/).
+
+### Configuring Your ABP Framework Angular Project
+
+We have a sample ABP Framework Angular project that we will use for this deployment. Before creating the Docker image and Helm chart, you just need to configure `aspnet-core/src/***.HttpApi.Host/***.HttpApiHostModule.cs` file to allow CORS requests from your frontend application. You can do this by updating the following code to the `ConfigureServices` method:
+
+```csharp
+public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ var configuration = context.Services.GetConfiguration();
+ var hostingEnvironment = context.Services.GetHostingEnvironment();
+
+ if (!configuration.GetValue("App:DisablePII"))
+ {
+ Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
+ }
+
+ if (!configuration.GetValue("AuthServer:RequireHttpsMetadata"))
+ {
+ Configure(options =>
+ {
+ options.DisableTransportSecurityRequirement = true;
+ });
+ }
+ context.Services.Configure(options =>
+ {
+ options.ForwardedHeaders = ForwardedHeaders.XForw
+```
+
+
+
+If your ABP Framework Angular project and Azure kubernetes cluster ready, we can start to build the docker images and pushing them to any container registry. In this article, I will use DockerHub as the container registry.
+
+I will also show you how I automated the steps that I originally did manually to make it simpler in the beginning and then automated them in Azure Devops.
+
+### Creating a Docker Image for ABP Framework Angular
+
+To create a Docker image for your ABP Framework Angular project, navigate to `etc/build/build-images-locally.ps1` and fix the script to match your Docker Hub username and image name. Then, run the script to build the Docker image locally.
+
+
+
+At the end of this process, check your Docker Hub repository to confirm that the image has been pushed successfully. My Docker Hub repository looks like this. Also you can use these my public images to test the deployment.
+
+
+
+### Creating Helm Chart for ABP Framework Angular
+
+To deploy your ABP Framework Angular project to Azure Kubernetes Service, you need to create a Helm chart. Helm is a package manager for Kubernetes that allows you to define, install, and upgrade even the most complex Kubernetes applications. Helm uses a packaging format called charts, which are a collection of files that describe a related set of Kubernetes resources.
+
+These helm charts are prepared to create a deployment, configmap, service and ingress for your ABP Framework Angular project. It is prepared not only for migration, frontend and backend, but also to create the sqlserver and redis that the application needs as a kubernetes service. If you already have redis and sqlserver, you don't need to stand them up in kubernetes, of course.
+
+
+
+You can find the helm chart in the https://github.com/skoc10/k8s_works/tree/main/demo/helm/k8s/angular repository. You can configure the `values-azure.yaml` file according to your needs.
+
+### Deploying to Azure Kubernetes Service
+
+`Note:` You need to have nginx-ingress-controller and cert-manager installed for letsencrypt certificate in your kubernetes cluster.
+
+Now that you have Docker images for your ABP Framework Angular project and a Helm chart, you can proceed to deploy it to Azure Kubernetes Service. To do this, you need to create a new Azure Kubernetes Service resource and configure the `values-azure.yaml` file according to your needs. If you want, you can deploy a single Helm with `demo/helm/k8s/deploy-staging.ps1` script you can deploy each chart separately.
+
+
+
+After deploying the Helm chart, you can check the deployment status to confirm that the deployment was successful. You can also check the logs of the pods to see if there are any errors.
+
+
+
+Finally, you can navigate to the web url to see your ABP Framework Angular project running in Azure Kubernetes Service.
+
+
+
+### CI/CD with Azure DevOps
+
+I have automated the steps that I originally did manually to make it simpler in the beginning and then automated them in Azure Devops. You can find the `azure-pipelines.yml` file in the https://github.com/skoc10/k8s_works/blob/main/demo/azure/azure-pipelines.yml repository. You can configure the `azure-pipelines.yml` file according to your needs.
+
+```yaml
+trigger:
+ tags:
+ include:
+ - "*.*.*"
+
+variables:
+ dockerRegistryServiceConnection: 'demo-reg'
+ buildContextBasePath: '$(Build.SourcesDirectory)'
+ tag: $(Build.BuildNumber)
+ DOCKER_BUILDKIT: 1
+
+pool:
+ name: ubuntu
+
+stages:
+- stage: Build
+ displayName: Build
+ jobs:
+ - job: CheckChanges
+ displayName: Check if Angular or ASP.NET Core has changed
+ pool:
+ name: ubuntu
+ steps:
+ - checkout: self
+
+# Migration
+ - task: Docker@2
+ displayName: 'Build Migration Docker image'
+ inputs:
+ command: build
+ repository: demo-angular-apppro/migration
+ dockerfile: $(buildContextBasePath)/aspnet-core/src/Demo.AzureAppsAngular.DbMigrator/Dockerfile.azure
+ buildContext: $(buildContextBasePath)/aspnet-core
+ containerRegistry: $(dockerRegistryServiceConnection)
+ tags: |
+ $(tag)
+
+ - task: Docker@2
+ displayName: 'Push Migration Docker image'
+ inputs:
+ command: push
+ repository: demo-angular-apppro/migration
+ containerRegistry: $(dockerRegistryServiceConnection)
+ tags: |
+ $(tag)
+
+ - task: HelmDeploy@0
+ displayName: 'Delete Migrator'
+ inputs:
+ connectionType: Kubernetes Service Connection
+ kubernetesServiceConnection: 'aks-demoms'
+ namespace: 'angular'
+ command: delete
+ arguments: dbmigrator
+ continueOnError: true
+
+ - task: HelmDeploy@0
+ displayName: 'Deploy Migration to AKS'
+ inputs:
+ connectionType: Kubernetes Service Connection
+ kubernetesServiceConnection: 'aks-demoms'
+ namespace: 'angular'
+ command: 'upgrade'
+ chartType: 'FilePath'
+ chartPath: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/dbmigrator'
+ releaseName: 'dbmigrator'
+ overrideValues: 'image.tag=$(tag)'
+ valueFile: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/dbmigrator/values.yaml'
+ waitForExecution: false
+
+# Backend
+ - task: Docker@2
+ displayName: 'Build Backend Docker image'
+ inputs:
+ command: build
+ repository: demo-angular-apppro/backend
+ dockerfile: $(buildContextBasePath)/aspnet-core/src/Demo.AzureAppsAngular.HttpApi.Host/Dockerfile.azure
+ buildContext: $(buildContextBasePath)/aspnet-core
+ containerRegistry: $(dockerRegistryServiceConnection)
+ tags: |
+ $(tag)
+
+ - task: Docker@2
+ displayName: 'Push Backend Docker image'
+ inputs:
+ command: push
+ repository: demo-angular-apppro/backend
+ containerRegistry: $(dockerRegistryServiceConnection)
+ tags: |
+ $(tag)
+
+ - task: HelmDeploy@0
+ displayName: 'Deploy Backend to AKS'
+ inputs:
+ connectionType: Kubernetes Service Connection
+ kubernetesServiceConnection: 'aks-demoms'
+ namespace: 'angular'
+ command: 'upgrade'
+ chartType: 'FilePath'
+ chartPath: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/backend'
+ releaseName: 'backend'
+ overrideValues: 'image.tag=$(tag)'
+ valueFile: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/backend/values.yaml'
+ waitForExecution: false
+
+# Frontend
+ - task: Docker@2
+ displayName: 'Build Frontend Docker image'
+ inputs:
+ command: build
+ repository: demo-angular-apppro/frontend
+ dockerfile: $(buildContextBasePath)/angular/Dockerfile.azure
+ buildContext: $(buildContextBasePath)/angular
+ containerRegistry: $(dockerRegistryServiceConnection)
+ tags: |
+ $(tag)
+
+ - task: Docker@2
+ displayName: 'Push Frontend Docker image'
+ inputs:
+ command: push
+ repository: demo-angular-apppro/frontend
+ containerRegistry: $(dockerRegistryServiceConnection)
+ tags: |
+ $(tag)
+
+ - task: HelmDeploy@0
+ displayName: 'Deploy Frontend to AKS'
+ inputs:
+ connectionType: Kubernetes Service Connection
+ kubernetesServiceConnection: 'aks-demoms'
+ namespace: 'angular'
+ command: 'upgrade'
+ chartType: 'FilePath'
+ chartPath: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/angular'
+ releaseName: 'frontend'
+ overrideValues: 'image.tag=$(tag)'
+ valueFile: '$(buildContextBasePath)/aspnet-core/etc/k8s/angular/charts/angular/values.yaml'
+ waitForExecution: false
+```
+
+### Conclusion
+
+In this article, I showed you how you can deploy your ABP Framework Angular project to Azure Kubernetes Service using Helm chart. I also showed you how you can automate the deployment process using Azure DevOps. I hope you found this article helpful. If you have any questions or feedback, please feel free to leave a comment below.
\ No newline at end of file
diff --git a/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks-helm.png b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks-helm.png
new file mode 100644
index 0000000000..0e861b2a68
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks-helm.png differ
diff --git a/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks.png b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks.png
new file mode 100644
index 0000000000..1790b100e2
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/abp-angular-aks.png differ
diff --git a/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/build-docker-image.png b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/build-docker-image.png
new file mode 100644
index 0000000000..12baa30af4
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/build-docker-image.png differ
diff --git a/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/configure-cors.png b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/configure-cors.png
new file mode 100644
index 0000000000..def6e9b97e
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/configure-cors.png differ
diff --git a/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/create-azure-kubernetes-service.png b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/create-azure-kubernetes-service.png
new file mode 100644
index 0000000000..88c5c3b97e
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/create-azure-kubernetes-service.png differ
diff --git a/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/deploy-helm-chart.png b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/deploy-helm-chart.png
new file mode 100644
index 0000000000..b58be08115
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/deploy-helm-chart.png differ
diff --git a/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/docker-hub-repository.png b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/docker-hub-repository.png
new file mode 100644
index 0000000000..1a58455658
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/docker-hub-repository.png differ
diff --git a/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/helm-chart.png b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/helm-chart.png
new file mode 100644
index 0000000000..398a724baf
Binary files /dev/null and b/docs/en/Community-Articles/2024-05-28-AKS-Helm-deployment-ABP-Angular/helm-chart.png differ
diff --git a/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/How to use Aspire with ABP framework.md b/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/How to use Aspire with ABP framework.md
new file mode 100644
index 0000000000..7d78d476c5
--- /dev/null
+++ b/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
+
+
+
+
+
+
+
+
+
+
+
+```
+
+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("dbMigrator")
+ .WithReference(postgres, "Default")
+ .WithReference(redis, "Redis")
+ .WithReplicas(1);
+}
+
+// AuthServer
+var authServerLaunchProfile = "AspirationalAbp.AuthServer";
+builder
+ .AddProject("authserver", launchProfileName: authServerLaunchProfile)
+ .WithExternalHttpEndpoints()
+ .WithReference(postgres, "Default")
+ .WithReference(redis);
+
+// HttpApi.Host
+var httpApiHostLaunchProfile = "AspirationalAbp.HttpApi.Host";
+builder
+ .AddProject("httpapihost", launchProfileName: httpApiHostLaunchProfile)
+ .WithExternalHttpEndpoints()
+ .WithReference(postgres, "Default")
+ .WithReference(redis);
+
+// Web
+builder
+ .AddProject("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("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("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.
+
+
+
+
+
+
+
+
+
+## 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.
diff --git a/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-dashboard.png b/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-dashboard.png
new file mode 100644
index 0000000000..b1141c4644
Binary files /dev/null and b/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-dashboard.png differ
diff --git a/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-metrics.png b/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-metrics.png
new file mode 100644
index 0000000000..ec2c9abda7
Binary files /dev/null and b/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-metrics.png differ
diff --git a/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-structured-logs.png b/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-structured-logs.png
new file mode 100644
index 0000000000..af0b685000
Binary files /dev/null and b/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-structured-logs.png differ
diff --git a/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-traces.png b/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-traces.png
new file mode 100644
index 0000000000..20a5d65d3c
Binary files /dev/null and b/docs/en/Community-Articles/2024-06-27-how-to-use-Aspire-with-ABP-framework/aspire-traces.png differ
diff --git a/docs/en/Community-Articles/2024-07-01-Use_User-Defined_Function_Mapping_For_Global_Filter/POST.md b/docs/en/Community-Articles/2024-07-01-Use_User-Defined_Function_Mapping_For_Global_Filter/POST.md
new file mode 100644
index 0000000000..37171d1219
--- /dev/null
+++ b/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() ?? false;
+
+protected override bool ShouldFilterEntity(IMutableEntityType entityType)
+{
+ if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
+ {
+ return true;
+ }
+
+ return base.ShouldFilterEntity(entityType);
+}
+
+protected override Expression> CreateFilterExpression(ModelBuilder modelBuilder)
+{
+ var expression = base.CreateFilterExpression(modelBuilder);
+
+ if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
+ {
+ Expression> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property(e, "IsActive");
+
+ if (UseDbFunction())
+ {
+ isActiveFilter = e => IsActiveFilter(((IIsActive)e).IsActive, true);
+
+ var abpEfCoreCurrentDbContext = this.GetService();
+ 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() == 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)
diff --git a/docs/en/Contribution/Index.md b/docs/en/Contribution/Index.md
index 8ebac70c20..77dd3cdb8b 100644
--- a/docs/en/Contribution/Index.md
+++ b/docs/en/Contribution/Index.md
@@ -29,23 +29,6 @@ You may want to fix a known bug or work on a planned enhancement. See [the issue
If you have a feature idea for the framework or modules, [create an issue](https://github.com/abpframework/abp/issues/new) on Github or attend to an existing discussion. Then you can implement it if it's embraced by the community.
-## Document Translation
-
-You may want to translate the complete [documentation](https://docs.abp.io) (including this one) to your mother language. If so, follow these steps:
-
-* Clone the [ABP repository](https://github.com/abpframework/abp/) from Github.
-* To add a new language, create a new folder inside the [docs](https://github.com/abpframework/abp/tree/master/docs) folder. Folder names can be "en", "es", "fr", "tr" and so on based on the language (see [all culture codes](https://msdn.microsoft.com/en-us/library/hh441729.aspx)).
-* Get the ["en" folder](https://github.com/abpframework/abp/tree/master/docs/en) as a reference for the file names and folder structure. Keep the same naming if you are translating the same documentation.
-* Send a pull request (PR) once you translate any document. Please translate documents & send PRs one by one. Don't wait to finish translations for all documents.
-
-There are some fundamental documents need to be translated before publishing a language on the [ABP documentation web site](https://docs.abp.io):
-
-* Index (Home)
-* Getting Started
-* Web Application Development Tutorial
-
-A new language is published after these minimum translations have been completed.
-
## Resource Localization
ABP framework has a flexible [localization system](../Localization.md). You can create localized user interfaces for your own application.
diff --git a/docs/en/Data-Filtering.md b/docs/en/Data-Filtering.md
index 830979e710..4706912fe7 100644
--- a/docs/en/Data-Filtering.md
+++ b/docs/en/Data-Filtering.md
@@ -217,17 +217,14 @@ protected override bool ShouldFilterEntity(IMutableEntityType entityTyp
return base.ShouldFilterEntity(entityType);
}
-protected override Expression> CreateFilterExpression()
+protected override Expression> CreateFilterExpression(ModelBuilder modelBuilder)
{
- var expression = base.CreateFilterExpression();
+ var expression = base.CreateFilterExpression(modelBuilder);
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
- Expression> isActiveFilter =
- e => !IsActiveFilterEnabled || EF.Property(e, "IsActive");
- expression = expression == null
- ? isActiveFilter
- : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
+ Expression> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property(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() ?? false;
+
+protected override bool ShouldFilterEntity(IMutableEntityType entityType)
+{
+ if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
+ {
+ return true;
+ }
+
+ return base.ShouldFilterEntity(entityType);
+}
+
+protected override Expression> CreateFilterExpression(ModelBuilder modelBuilder)
+{
+ var expression = base.CreateFilterExpression(modelBuilder);
+
+ if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
+ {
+ Expression> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property(e, "IsActive");
+
+ if (UseDbFunction())
+ {
+ isActiveFilter = e => IsActiveFilter(((IIsActive)e).IsActive, true);
+
+ var abpEfCoreCurrentDbContext = this.GetService();
+ 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() == 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.
diff --git a/docs/en/Modules/Cms-Kit/Comments.md b/docs/en/Modules/Cms-Kit/Comments.md
index ddd39fc675..4eca4cb93d 100644
--- a/docs/en/Modules/Cms-Kit/Comments.md
+++ b/docs/en/Modules/Cms-Kit/Comments.md
@@ -80,6 +80,12 @@ You can also view and manage replies on this page.

+## 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."
+
+
+
## Internals
### Domain Layer
diff --git a/docs/en/Modules/Cms-Kit/Index.md b/docs/en/Modules/Cms-Kit/Index.md
index 6c3201d0f9..f546552f16 100644
--- a/docs/en/Modules/Cms-Kit/Index.md
+++ b/docs/en/Modules/Cms-Kit/Index.md
@@ -17,6 +17,7 @@ The following features are currently available:
* Provides a [**menu**](Menus.md) system to manage public menus dynamically.
* Provides a [**global resources**](Global-Resources.md) system to add global styles and scripts dynamically.
* Provides a [**Dynamic Widget**](Dynamic-Widget.md) system to create dynamic widgets for page and blog posts.
+* Provides a [**Marked Item**](MarkedItems.md) system to mark any kind of resource, like a blog post or a product, as a favorite, starred, flagged, or bookmarked.
> You can click on the any feature links above to understand and learn how to use it.
diff --git a/docs/en/Modules/Cms-Kit/MarkedItems.md b/docs/en/Modules/Cms-Kit/MarkedItems.md
new file mode 100644
index 0000000000..b7bbb77519
--- /dev/null
+++ b/docs/en/Modules/Cms-Kit/MarkedItems.md
@@ -0,0 +1,123 @@
+# Marked Item System
+
+CMS kit provides a marking system to mark any kind of resource, like a blog post or a product, as a favorite, starred, flagged, or bookmarked.
+
+Marked toggling component allows users to mark your items via pre-defined icons/emojis. Here how the marked toggling components may look like:
+
+
+
+
+you can also customize the marking icons shown in the toggling components.
+
+## Enabling the Marked Item Feature
+
+By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
+
+> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
+
+## Options
+
+Marking system provides a simple approach to define your entity type with mark types like favorite or starred. For example, if you want to use the marking system for products, you need to define an entity type named `product` with the icon name.
+
+`CmsKitMarkedItemOptions` can be configured in YourModule.cs, in the `ConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example:
+
+```csharp
+Configure(options =>
+{
+ options.EntityTypes.Add(
+ new MarkedItemEntityTypeDefinition(
+ "product",
+ StandardMarkedItems.Favorite
+ )
+ );
+});
+```
+
+`CmsKitMarkedItemOptions` properties:
+
+- `EntityTypes`: List of defined entity types (`CmsKitMarkedItemOptions`) in the marking system.
+
+`MarkedItemEntityTypeDefinition` properties:
+
+- `EntityType`: Name of the entity type.
+- `IconName`: The name of the icon.
+
+## The Marked Item widget
+
+The marking system provides a toggle widget to allow users to add/remove the marks from an item. You can place the widget with the item as shown below:
+
+``` csharp
+@await Component.InvokeAsync(typeof (MarkedItemToggleViewComponent), new
+{
+ entityId = "...",
+ entityType = "product",
+ needsConfirmation = true // (optional)
+})
+```
+* `entityType` was explained in the previous section.
+* `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here.
+* `needsConfirmation` An optional parameter to let the user confirm when removing the mark.
+
+# Internals
+
+## Domain Layer
+
+#### Aggregates
+
+This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide.
+
+##### UserMarkedItem
+
+A user markedItem represents a user has marking on the item.
+
+- `UserMarkedItem` (aggregate root): Represents a marked item in the system.
+
+#### Repositories
+
+This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide.
+
+Following custom repositories are defined for this feature:
+
+- `IUserMarkedItemRepository`
+
+
+#### Domain services
+
+This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide.
+
+##### Marked Item Manager
+
+`MarkedItemManager` is used to perform some operations for the `UserMarkedItem` aggregate root.
+
+### Application layer
+
+#### Application services
+
+- `MarkedItemPublicAppService` (implements `IMarkedItemPublicAppService`): Implements the use cases of marking system.
+
+### Database providers
+
+#### Common
+
+##### Table / collection prefix & schema
+
+All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
+
+##### Connection string
+
+This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string.
+
+See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details.
+
+#### Entity Framework Core
+
+##### Tables
+
+- CmsUserMarkedItems
+
+#### MongoDB
+
+##### Collections
+
+- **CmsUserMarkedItems**
+
diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md
index 922976767d..123eb45165 100644
--- a/docs/en/Multi-Tenancy.md
+++ b/docs/en/Multi-Tenancy.md
@@ -1,6 +1,6 @@
# Multi-Tenancy
-Multi-Tenancy is a widely used architecture to create **SaaS applications** where the hardware and software **resources are shared by the customers** (tenants). ABP Framework provides all the base functionalities to create **multi tenant applications**.
+Multi-Tenancy is a widely used architecture to create **SaaS applications** where the hardware and software **resources are shared by the customers** (tenants). ABP Framework provides all the base functionalities to create **multi tenant applications**.
Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) the multi-tenancy as like that:
@@ -10,8 +10,8 @@ Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) the multi-tenanc
There are two main side of a typical SaaS / Multi-tenant application:
-* A **Tenant** is a customer of the SaaS application that pays money to use the service.
-* **Host** is the company that owns the SaaS application and manages the system.
+- A **Tenant** is a customer of the SaaS application that pays money to use the service.
+- **Host** is the company that owns the SaaS application and manages the system.
The Host and the Tenant terms will be used for that purpose in the rest of the document.
@@ -36,9 +36,9 @@ Configure(options =>
ABP Framework supports all the following approaches to store the tenant data in the database;
-* **Single Database**: All tenants are stored in a single database.
-* **Database per Tenant**: Every tenant has a separate, dedicated database to store the data related to that tenant.
-* **Hybrid**: Some tenants share a single databases while some tenants may have their own databases.
+- **Single Database**: All tenants are stored in a single database.
+- **Database per Tenant**: Every tenant has a separate, dedicated database to store the data related to that tenant.
+- **Hybrid**: Some tenants share a single database while some tenants may have their own databases.
[Tenant management module](Modules/Tenant-Management.md) (which comes pre-installed with the startup projects) allows you to set a connection string for any tenant (as optional), so you can achieve any of the approaches.
@@ -48,11 +48,11 @@ Multi-tenancy system is designed to **work seamlessly** and make your applicatio
### IMultiTenant
-You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**.
+You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**.
-**Example: A multi-tenant *Product* entity**
+**Example: A multi-tenant _Product_ entity**
-````csharp
+```csharp
using System;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
@@ -68,7 +68,7 @@ namespace MultiTenancyDemo.Products
public float Price { get; set; }
}
}
-````
+```
`IMultiTenant` interface just defines a `TenantId` property. When you implement this interface, ABP Framework **automatically** [filters](Data-Filtering.md) entities for the current tenant when you query from database. So, you don't need to manually add `TenantId` condition while performing queries. A tenant can not access to data of another tenant by default.
@@ -96,9 +96,9 @@ If you set the `TenantId` value for a specific entity object, it will override t
`ICurrentTenant` defines the following properties;
-* `Id` (`Guid`): Id of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
-* `Name` (`string`): Name of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
-* `IsAvailable` (`bool`): Returns `true` if the `Id` is not `null`.
+- `Id` (`Guid`): Id of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
+- `Name` (`string`): Name of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
+- `IsAvailable` (`bool`): Returns `true` if the `Id` is not `null`.
#### Change the Current Tenant
@@ -108,7 +108,7 @@ ABP Framework automatically filters the resources (database, cache...) based on
**Example: Get product count of a specific tenant**
-````csharp
+```csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
@@ -134,11 +134,11 @@ namespace MultiTenancyDemo.Products
}
}
}
-````
+```
-* `Change` method can be used in a **nested way**. It restores the `CurrentTenant.Id` to the previous value after the `using` statement.
-* When you use `CurrentTenant.Id` inside the `Change` scope, you get the `tenantId` provided to the `Change` method. So, the repository also get this `tenantId` and can filter the database query accordingly.
-* Use `CurrentTenant.Change(null)` to change scope to the host context.
+- `Change` method can be used in a **nested way**. It restores the `CurrentTenant.Id` to the previous value after the `using` statement.
+- When you use `CurrentTenant.Id` inside the `Change` scope, you get the `tenantId` provided to the `Change` method. So, the repository also get this `tenantId` and can filter the database query accordingly.
+- Use `CurrentTenant.Change(null)` to change scope to the host context.
> Always use the `Change` method with a `using` statement like done in this example.
@@ -148,7 +148,7 @@ As mentioned before, ABP Framework handles data isolation between tenants using
**Example: Get count of products in the database, including all the products of all the tenants.**
-````csharp
+```csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
@@ -181,7 +181,7 @@ namespace MultiTenancyDemo.Products
}
}
-````
+```
See the [Data Filtering document](Data-Filtering.md) for more.
@@ -201,15 +201,15 @@ ABP Framework provides an extensible **Tenant Resolving** system for that purpos
The following resolvers are provided and configured by default;
-* `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**.
-* `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default.
-* `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route.
-* `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default.
-* `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default.
+- `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**.
+- `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default.
+- `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route.
+- `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default.
+- `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default.
###### Problems with the NGINX
-You may have problems with the `__tenant` in the HTTP Headers if you're using the [nginx](https://www.nginx.com/) as the reverse proxy server. Because it doesn't allow to use underscore and some other special characters in the HTTP headers and you may need to manually configure it. See the following documents please:
+You may have problems with the `__tenant` in the HTTP Headers if you're using the [nginx](https://www.nginx.com/) as the reverse proxy server. Because it doesn't allow to use underscore and some other special characters in the HTTP headers and you may need to manually configure it. See the following documents please:
http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers
http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
@@ -219,22 +219,24 @@ http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
**Example:**
-````csharp
+```csharp
services.Configure(options =>
{
options.TenantKey = "MyTenantKey";
});
-````
+```
-If you change the `TenantKey`, make sure to pass it to `CoreModule` in the Angular client as follows:
+If you change the `TenantKey`, make sure to pass it to `provideAbpCore` via `withOptions` method in the Angular client as follows:
```js
@NgModule({
- imports: [
- CoreModule.forRoot({
- // ...
- tenantKey: 'MyTenantKey'
- }),
+ providers: [
+ provideAbpCore(
+ withOptions({
+ // ...
+ tenantKey: "MyTenantKey",
+ })
+ ),
],
// ...
})
@@ -249,7 +251,7 @@ import { TENANT_KEY } from '@abp/ng.core';
class SomeComponent {
constructor(@Inject(TENANT_KEY) private tenantKey: string) {}
-}
+}
```
> However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then.
@@ -277,30 +279,30 @@ In a real application, most of times you will want to determine the current tena
**Example: Add a subdomain resolver**
-````csharp
+```csharp
Configure(options =>
{
options.AddDomainTenantResolver("{0}.mydomain.com");
});
-````
+```
-* `{0}` is the placeholder to determine the current tenant's unique name.
-* Add this code to the `ConfigureServices` method of your [module](Module-Development-Basics.md).
-* This should be done in the *Web/API Layer* since the URL is a web related stuff.
+- `{0}` is the placeholder to determine the current tenant's unique name.
+- Add this code to the `ConfigureServices` method of your [module](Module-Development-Basics.md).
+- This should be done in the _Web/API Layer_ since the URL is a web related stuff.
Openiddict is the default Auth Server in ABP (since v6.0). When you use OpenIddict, you must add this code to the `PreConfigure` method as well.
```csharp
// using Volo.Abp.OpenIddict.WildcardDomains
-PreConfigure(options =>
+PreConfigure(options =>
{
options.EnableWildcardDomainSupport = true;
options.WildcardDomainsFormat.Add("https://{0}.mydomain.com");
});
```
-You must add this code to the `Configure` method as well.
+You must add this code to the `Configure` method as well.
```csharp
// using Volo.Abp.MultiTenancy;
@@ -312,7 +314,7 @@ Configure(options =>
```
-> There is an [example](https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver) that uses the subdomain to determine the current tenant.
+> There is an [example](https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver) that uses the subdomain to determine the current tenant.
If you use a sepereted Auth server, you must install `[Owl.TokenWildcardIssuerValidator](https://www.nuget.org/packages/Owl.TokenWildcardIssuerValidator)` on the `HTTPApi.Host` project
@@ -332,7 +334,7 @@ context.Services
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
options.Audience = "ExampleProjectName";
-
+
// start of added block
options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator;
options.TokenValidationParameters.ValidIssuers = new[]
@@ -348,16 +350,16 @@ context.Services
You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below:
-````csharp
+```csharp
Configure(options =>
{
options.TenantResolvers.Add(new MyCustomTenantResolveContributor());
});
-````
+```
`MyCustomTenantResolveContributor` must inherit from the `TenantResolveContributorBase` (or implement the `ITenantResolveContributor`) as shown below:
-````csharp
+```csharp
using System.Threading.Tasks;
using Volo.Abp.MultiTenancy;
@@ -373,10 +375,10 @@ namespace MultiTenancyDemo.Web
}
}
}
-````
+```
-* A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it.
-* `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](Dependency-Injection.md) system.
+- A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it.
+- `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](Dependency-Injection.md) system.
#### Multi-Tenancy Middleware
@@ -384,9 +386,9 @@ Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https:
Multi-Tenancy middleware is typically placed just under the [authentication](https://docs.microsoft.com/en-us/aspnet/core/security/authentication) middleware (`app.UseAuthentication()`):
-````csharp
+```csharp
app.UseMultiTenancy();
-````
+```
> This middleware is already configured in the startup templates, so you normally don't need to manually add it.
@@ -406,7 +408,7 @@ The [tenant management module](Modules/Tenant-Management) is **included in the s
**Example: Define tenants in appsettings.json**
-````json
+```json
"Tenants": [
{
"Id": "446a5211-3d72-4339-9adc-845151f8ada0",
@@ -422,7 +424,7 @@ The [tenant management module](Modules/Tenant-Management) is **included in the s
}
}
]
-````
+```
> It is recommended to **use the Tenant Management module**, which is already pre-configured when you create a new application with the ABP startup templates.
@@ -440,4 +442,4 @@ The [Tenant Management module](Modules/Tenant-Management.md) provides a basic UI
## See Also
-* [Features](Features.md)
+- [Features](Features.md)
diff --git a/docs/en/Themes/LeptonXLite/Angular.md b/docs/en/Themes/LeptonXLite/Angular.md
index e7c8142bda..8e46468bd2 100644
--- a/docs/en/Themes/LeptonXLite/Angular.md
+++ b/docs/en/Themes/LeptonXLite/Angular.md
@@ -31,20 +31,21 @@ yarn add bootstrap-icons
Note: You should remove the old theme styles from "angular.json" if you are switching from "ThemeBasic" or "Lepton."
Look at the [Theme Configurations](../../UI/Angular/Theme-Configurations) list of styles. Depending on your theme, you can alter your styles in angular.json.
-- Finally, remove `ThemeBasicModule` from `app.module.ts`, and import the related modules in `app.module.ts`
+- Finally, remove `ThemeBasicModule`, `provideThemeBasicConfig` from `app.module.ts`, and import the related modules in `app.module.ts`
```js
import { ThemeLeptonXModule } from "@abp/ng.theme.lepton-x";
-import { SideMenuLayoutModule } from "@abp/ng.theme.lepton-x/layouts";
@NgModule({
imports: [
// ...
-
// do not forget to remove ThemeBasicModule or other old theme module
- // ThemeBasicModule.forRoot(),
- ThemeLeptonXModule.forRoot(),
- SideMenuLayoutModule.forRoot(),
+ // ThemeBasicModule
+ ThemeLeptonXModule.forRoot()
+ ],
+ providers: [
+ // do not forget to remove provideThemeBasicConfig or other old theme providers
+ // provideThemeBasicConfig
],
// ...
})
diff --git a/docs/en/UI/Angular/Account-Module.md b/docs/en/UI/Angular/Account-Module.md
index 28852f6b4d..b5fedb8afe 100644
--- a/docs/en/UI/Angular/Account-Module.md
+++ b/docs/en/UI/Angular/Account-Module.md
@@ -7,7 +7,6 @@ If you add the account module to your project;
- "My account" link in the current user dropdown on the top bar will redirect the user to a page in the account module.
- You can switch the authentication flow to the resource owner password flow.
-
### Account Module Implementation
Install the `@abp/ng.account` NPM package by running the below command:
@@ -18,18 +17,18 @@ npm install @abp/ng.account
> Make sure v4.3 or higher version is installed.
-Open the `app.module.ts` and add `AccountConfigModule.forRoot()` to the imports array as shown below:
+Open the `app.module.ts` and add `provideAccountConfig()` to the providers array as shown below:
```js
// app.module.ts
-import { AccountConfigModule } from '@abp/ng.account/config';
+import { provideAccountConfig } from "@abp/ng.account/config";
//...
@NgModule({
- imports: [
+ providers: [
//...
- AccountConfigModule.forRoot()
+ provideAccountConfig(),
],
//...
})
@@ -57,19 +56,19 @@ The pro startup template comes with `@volo/abp.ng.account` package. You should u
```bash
npm install @volo/abp.ng.account
```
+
> Make sure v4.3 or higher version is installed.
Open the `app.module.ts` and add `AccountPublicConfigModule.forRoot()` to the imports array as shown below:
> Ensure that the `Account Layout Module` has been added if you are using the Lepton X theme. If you miss the step, you will get an error message that says `Account layout not found. Please check your configuration. If you are using LeptonX, please make sure you have added "AccountLayoutModule.forRoot()" to your app.module configuration.` when you try to access the account pages. Otherwise, you can skip adding the `AccountLayoutModule` step.
-
```js
// app.module.ts
-import { AccountPublicConfigModule } from '@volo/abp.ng.account/public/config';
+import { AccountPublicConfigModule } from "@volo/abp.ng.account/public/config";
// if you are using or want to use Lepton X, you should add AccountLayoutModule
-// import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account'
+// import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account'
//...
@@ -104,9 +103,9 @@ Before v4.3, the "My account" link in the current user dropdown on the top bar r
### Personal Info Page Confirm Message
-When the user changes their own data on the personal settings tab in My Account, The data can not update the CurrentUser key of Application-Configuration. The information of the user is stored in claims. The only way to apply this information to the CurrentUser of Application-Configuration is user should log out and log in. When the Refresh-Token feature is implemented, it will be fixed. So We've added a confirmation alert.
+When the user changes their own data on the personal settings tab in My Account, The data can not update the CurrentUser key of Application-Configuration. The information of the user is stored in claims. The only way to apply this information to the CurrentUser of Application-Configuration is user should log out and log in. When the Refresh-Token feature is implemented, it will be fixed. So We've added a confirmation alert.
-If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false
+If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false
```js
// app-routing.module.ts
diff --git a/docs/en/UI/Angular/Basic-Theme.md b/docs/en/UI/Angular/Basic-Theme.md
index 651be9b637..3b11e81f14 100644
--- a/docs/en/UI/Angular/Basic-Theme.md
+++ b/docs/en/UI/Angular/Basic-Theme.md
@@ -11,7 +11,7 @@ The Basic Theme is a theme implementation for the Angular UI. It is a minimalist
If you need to manually this theme, follow the steps below:
* Install the [@abp/ng.theme.basic](https://www.npmjs.com/package/@abp/ng.theme.basic) NPM package to your Angular project.
-* Open the `src/app/app.module.ts` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule.forRoot()` to the `imports` array.
+* Open the `src/app/app.module.ts` file, import `ThemeBasicModule`,`provideThemeBasicConfig` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` array and provide `provideThemeBasicConfig()` to the providers array.
* Open the `src/app/shared/shared.module` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` and `exports` array.
The `ThemeBasicModule` is registered own layouts (`ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`) to a service which is exposed by `@abp/ng.core` package on application initialization.
diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md
index d21489d232..73c7a2ed15 100644
--- a/docs/en/UI/Angular/Component-Replacement.md
+++ b/docs/en/UI/Angular/Component-Replacement.md
@@ -30,7 +30,6 @@ export class AppComponent {

-
## How to Replace a Layout
Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced the same way.
@@ -88,11 +87,13 @@ This component should have a 'router-outlet' for dynamic content loading. You ca
```bash
ng generate component new-layout
```
+
This command will create a new component named `new-layout`. Now, open the new-layout.component.html file and add a `router-outlet` to it:
```html
-
+
```
+
This 'router-outlet' will act as a placeholder that Angular dynamically fills based on the current router state.
note: (don't forget: you should add the app in the app.module.ts file)
@@ -103,10 +104,11 @@ Although this step is optional, it can be useful if you're going to use the layo
```javascript
export const eCustomLayout = {
- key: 'CustomLayout',
- component: 'CustomLayoutComponent',
+ key: "CustomLayout",
+ component: "CustomLayoutComponent",
};
```
+
In this variable, `key` is a unique identifier for the layout component, and `component` is the name of the layout component.
You can use this variable when you need to refer to the layout component.
@@ -120,19 +122,24 @@ Here's how you can do it:
```javascript
export const CUSTOM_LAYOUT_PROVIDERS = [
- { provide: APP_INITIALIZER, useFactory: configureLayoutFn, deps: [ReplaceableComponentsService], multi: true },
-
+ {
+ provide: APP_INITIALIZER,
+ useFactory: configureLayoutFn,
+ deps: [ReplaceableComponentsService],
+ multi: true,
+ },
];
function configureLayoutFn() {
- const service= inject( ReplaceableComponentsService)
- return () =>{
- service.add({
- key: eCustomLayout.component,
- component: CustomLayoutComponent,
- })
- }
+ const service = inject(ReplaceableComponentsService);
+ return () => {
+ service.add({
+ key: eCustomLayout.component,
+ component: CustomLayoutComponent,
+ });
+ };
}
```
+
In this code, `configureLayoutFn` is a factory function that adds the new layout component to the `ReplaceableComponentsService`. The `APP_INITIALIZER` provider runs this function when the application starts.
note: (don't forget: you should add the CUSTOM_LAYOUT_PROVIDERS in the app.module.ts file)
@@ -149,34 +156,31 @@ export const myDynamicLayouts = new Map([...DEFAULT_DYNAMIC_LAYO
#### Step 5: Pass the Dynamic Layouts to the CoreModule
-The final step is to pass the dynamic layouts to the `CoreModule` using the `forRoot` method. This method allows you to configure the module with a static method.
+The final step is to pass the dynamic layouts to the `provideAbpCore` using the `withOptions` method. This method allows you to configure the module with a static method.
Here's how you can do it:
-```javascript
+```ts
@NgModule({
- declarations: [AppComponent],
- imports: [
- // other imports...
- CoreModule.forRoot({
- dynamicLayouts: myDynamicLayouts,
- environment,
- registerLocaleFn: registerLocale(),
- }),
- // other imports...
- NewLayoutComponent
+ providers: [
+ // ...
+ provideAbpCore(
+ withOptions({
+ dynamicLayouts: myDynamicLayouts,
+ environment,
+ registerLocaleFn: registerLocale(),
+ }),
+ ),
],
- providers: [APP_ROUTE_PROVIDER, CUSTOM_LAYOUT_PROVIDERS],
- bootstrap: [AppComponent],
})
export class AppModule {}
```
-In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `CoreModule` using the `forRoot` method.
+In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `provideAbpCore` using the `withOptions` method.
Now that you have defined the new layout, you can use it in the router definition. You do this by adding a new route that uses the new layout.
-Here's how you can do it:
+Here's how you can do it:
```javascript
// route.provider.ts
@@ -221,14 +225,13 @@ Run the following command in `angular` folder to create a new component called `
yarn ng generate component logo --inlineTemplate --inlineStyle
```
-
Open the generated `logo.component.ts` in `src/app/logo` folder and replace its content with the following:
```js
-import { Component } from '@angular/core';
+import { Component } from "@angular/core";
@Component({
- selector: 'app-logo',
+ selector: "app-logo",
template: `
@@ -284,14 +287,14 @@ yarn ng generate component routes
Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following:
```js
-import { Component, HostBinding } from '@angular/core';
+import { Component, HostBinding } from "@angular/core";
@Component({
- selector: 'app-routes',
- templateUrl: 'routes.component.html',
+ selector: "app-routes",
+ templateUrl: "routes.component.html",
})
export class RoutesComponent {
- @HostBinding('class.mx-auto')
+ @HostBinding("class.mx-auto")
marginAuto = true;
get smallScreen() {
@@ -321,11 +324,14 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac
- My Page
+ My Page
-
+
{%{{{ 'AbpUiNavigation::Menu:Administration' | abpLocalization }}}%}
@@ -401,7 +411,8 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac
class="btn d-block text-start dropdown-toggle"
>
- {%{{{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization }}}%}
+ {%{{{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization
+ }}}%}
-