diff --git a/.github/scripts/update_versions.py b/.github/scripts/update_versions.py index 843e80a3a6..83b3b9ef74 100644 --- a/.github/scripts/update_versions.py +++ b/.github/scripts/update_versions.py @@ -39,14 +39,16 @@ def create_pr(): branch=branch_name, ) - pr = repo.create_pull(title="Update latest-versions.json", - body="Automated PR to update the latest-versions.json file.", - head=branch_name, base="dev") + try: + pr = repo.create_pull(title="Update latest-versions.json", + body="Automated PR to update the latest-versions.json file.", + head=branch_name, base="dev") + except Exception as e: + print(f"Error while creating PR: {e}") - pr.create_review_request(reviewers=["ebicoglu", "gizemmutukurt", "skoc10"]) + pr.create_review_request(reviewers=["gizemmutukurt"]) -if __name__ == "__dev__": +if __name__ == "__main__": should_create_pr = update_latest_versions() if should_create_pr: create_pr() - diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000000..b929fa6d3c --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,44 @@ +name: Create Release + +on: + workflow_dispatch: + inputs: + tag_name: + description: 'Tag Name' + required: true + prerelease: + description: 'Pre-release?' + required: true + branchName: + description: 'Branch Name' + required: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.branchName }} + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + with: + tag_name: ${{ github.event.inputs.tag_name }} + release_name: ${{ github.event.inputs.tag_name }} + draft: false + prerelease: ${{ github.event.inputs.prerelease }} + + - name: Checkout code at tag + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.tag_name }} + + - name: Build Project + run: | + # add your build commands here, depending on your project's requirements. + echo "Build project here" diff --git a/.github/workflows/update-versions.yml b/.github/workflows/update-versions.yml index 386b6858b9..d0a295884f 100644 --- a/.github/workflows/update-versions.yml +++ b/.github/workflows/update-versions.yml @@ -28,6 +28,6 @@ jobs: - name: Update latest-versions.json and create PR env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | python .github/scripts/update_versions.py diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index 87347f21ce..f0fe2ff809 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -212,6 +212,9 @@ "SpringCampaign": "Welcome
Spring Sale!", "SpringCampaign2": "Limited
Time Offer!
", "AboutUs": "About Us", - "HowItWorks": "How it works?" + "HowItWorks": "How it works?", + "ReleaseNotes": "Release Notes", + "DetailedChangeNotes" : "Detailed Change Notes", + "SeeTrainings": "See Trainings" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index 1ba3124e02..cd76e9e134 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -844,7 +844,7 @@ "BlazoriseSupportExplanation5": "You can post your questions on the support website and generate a product token for your application.", "AbpLiveTrainingPackages": "ABP Live Training Packages", "Releases": "Releases", - "ReleasesDescription": "ABP Commercial releases and their changelogs.", + "ReleasesDescription": "Release logs of ABP Commercial.", "ReleaseDate": "Release Date", "Labels": "Labels", "PreRelease": "Pre-release", diff --git a/docs/en/API/Dynamic-CSharp-API-Clients.md b/docs/en/API/Dynamic-CSharp-API-Clients.md index 3ece459842..de9b87e206 100644 --- a/docs/en/API/Dynamic-CSharp-API-Clients.md +++ b/docs/en/API/Dynamic-CSharp-API-Clients.md @@ -38,20 +38,15 @@ Implement this class in your service application. You can use [auto API controll ## Client Proxy Generation -First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: +> The startup templates already comes pre-configured for the client proxy generation, in the `HttpApi.Client` project. + +If you're not using a startup template, then execute the following command in the folder that contains the .csproj file of your client project: ```` -Install-Package Volo.Abp.Http.Client +abp add-package Volo.Abp.Http.Client ```` -Then add `AbpHttpClientModule` dependency to your module: - -````csharp -[DependsOn(typeof(AbpHttpClientModule))] //add the dependency -public class MyClientAppModule : AbpModule -{ -} -```` +> If you haven't done it yet, you first need to install the [ABP CLI](../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Http.Client). Now, it's ready to create the client proxies. Example: @@ -74,8 +69,6 @@ public class MyClientAppModule : AbpModule `AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. -> The startup templates already comes pre-configured for the client proxy generation, in the `HttpApi.Client` project. - ### Endpoint Configuration `RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. The simplest configuration is shown below: diff --git a/docs/en/API/Swagger-Integration.md b/docs/en/API/Swagger-Integration.md index 40660eaab9..d9afd0e2e2 100644 --- a/docs/en/API/Swagger-Integration.md +++ b/docs/en/API/Swagger-Integration.md @@ -21,6 +21,8 @@ Open a command line window in the folder of the `Web` or `HttpApi.Host` project abp add-package Volo.Abp.Swashbuckle ``` +> If you haven't done it yet, you first need to install the [ABP CLI](../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Swashbuckle). + ### Manual Installation If you want to manually install; diff --git a/docs/en/Application-Startup.md b/docs/en/Application-Startup.md index 2839fcb19c..5711d61dc4 100644 --- a/docs/en/Application-Startup.md +++ b/docs/en/Application-Startup.md @@ -18,12 +18,14 @@ If you [create a new console application with Visual Studio](https://learn.micro This example uses the [top level statements](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements), so it consists of only a single line of code. -The first step is to install the [Volo.Abp.Core](https://www.nuget.org/packages/Volo.Abp.Core) NuGet package, which is the most core NuGet package of the ABP framework. You can install it using the [Package Manager Console](https://learn.microsoft.com/en-us/nuget/consume-packages/install-use-packages-powershell) in Visual Studio: +The first step is to install the [Volo.Abp.Core](https://www.nuget.org/packages/Volo.Abp.Core) NuGet package, which is the most core NuGet package of the ABP framework. You can install it using the ABP CLI. Execute the following command in the folder of the .csproj file that you want to install the package on: ````bash -Install-Package Volo.Abp.Core +abp add-package Volo.Abp.Core ```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Core). + Alternatively, you can use a command-line terminal in the root folder of the project (the folder containing the `MyConsoleDemo.csproj` file, for this example): ````bash diff --git a/docs/en/Autofac-Integration.md b/docs/en/Autofac-Integration.md index d05412c94b..43da50299b 100644 --- a/docs/en/Autofac-Integration.md +++ b/docs/en/Autofac-Integration.md @@ -6,27 +6,14 @@ > All the [startup templates](Startup-Templates/Index.md) and samples are Autofac integrated. So, most of the time you don't need to manually install this package. -Install [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) nuget package to your project (for a multi-projects application, it's suggested to add to the executable/web project). +If you're not using a startup template, you can use the [ABP CLI](CLI.md) to install it to your project. Execute the following command in the folder that contains the .csproj file of your project (suggested to add it to the executable/web project): +````bash +abp add-package Volo.Abp.Autofac ```` -Install-Package Volo.Abp.Autofac -```` - -Then add `AbpAutofacModule` dependency to your module: -```csharp -using Volo.Abp.Modularity; -using Volo.Abp.Autofac; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpAutofacModule))] - public class MyModule : AbpModule - { - //... - } -} -``` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Autofac). +> Finally, configure `AbpApplicationCreationOptions` to replace default dependency injection services by Autofac. It depends on the application type. diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index 1b04be560f..77f2da8bd0 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -16,6 +16,8 @@ Open a command line window in the folder of the project (.csproj file) and type abp add-package Volo.Abp.BackgroundJobs.HangFire ```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundJobs.HangFire). + ### Manual Installation If you want to manually install; diff --git a/docs/en/Background-Jobs-Quartz.md b/docs/en/Background-Jobs-Quartz.md index 97327b5e8a..4c1441da7f 100644 --- a/docs/en/Background-Jobs-Quartz.md +++ b/docs/en/Background-Jobs-Quartz.md @@ -16,6 +16,8 @@ Open a command line window in the folder of the project (.csproj file) and type abp add-package Volo.Abp.BackgroundJobs.Quartz ```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundJobs.Quartz). + ### Manual Installation If you want to manually install; diff --git a/docs/en/CLI-New-Command-Samples.md b/docs/en/CLI-New-Command-Samples.md index d2c63cfec2..635390c29e 100644 --- a/docs/en/CLI-New-Command-Samples.md +++ b/docs/en/CLI-New-Command-Samples.md @@ -73,9 +73,9 @@ The following commands are for creating MVC UI projects: _Note that Public Website is only included in PRO templates._ -## Blazor +## Blazor WebAssembly -The following commands are for creating Blazor projects: +The following commands are for creating Blazor WASM projects: * **Entity Framework Core**, no mobile app: diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md index fc17ddcd49..e2cfd21a05 100644 --- a/docs/en/Entity-Framework-Core.md +++ b/docs/en/Entity-Framework-Core.md @@ -7,24 +7,11 @@ This document explains how to integrate EF Core as an ORM provider to ABP based `Volo.Abp.EntityFrameworkCore` is the main NuGet package for the EF Core integration. Install it to your project (for a layered application, to your data/infrastructure layer): ```` shell -Install-Package Volo.Abp.EntityFrameworkCore +abp add-package Volo.Abp.EntityFrameworkCore ```` -Then add `AbpEntityFrameworkCoreModule` module dependency (`DependsOn` attribute) to your [module](Module-Development-Basics.md): - -````c# -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.Modularity; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpEntityFrameworkCoreModule))] - public class MyModule : AbpModule - { - //... - } -} -```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.EntityFrameworkCore). +> > Note: Instead, you can directly download a [startup template](https://abp.io/Templates) with EF Core pre-installed. diff --git a/docs/en/Getting-Started-AspNetCore-Application.md b/docs/en/Getting-Started-AspNetCore-Application.md index 117b0b499e..0a6424ac3b 100644 --- a/docs/en/Getting-Started-AspNetCore-Application.md +++ b/docs/en/Getting-Started-AspNetCore-Application.md @@ -18,11 +18,13 @@ This tutorial explains how to start ABP from scratch with minimal dependencies. ## Install Volo.Abp.AspNetCore.Mvc Package -Volo.Abp.AspNetCore.Mvc is the AspNet Core MVC integration package for ABP. So, install it on your project: +You can use the [ABP CLI](CLI.md) to install the Volo.Abp.AspNetCore.Mvc package to your project. Execute the following command in the folder of the .csproj file that you want to install the package on: +````bash +abp add-package Volo.Abp.AspNetCore.Mvc ```` -Install-Package Volo.Abp.AspNetCore.Mvc -```` + +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.Mvc). ## Create the First ABP Module diff --git a/docs/en/Getting-Started-React-Native.md b/docs/en/Getting-Started-React-Native.md index bc3c7ef419..ba55af5acb 100644 --- a/docs/en/Getting-Started-React-Native.md +++ b/docs/en/Getting-Started-React-Native.md @@ -36,17 +36,9 @@ abp new MyCompanyName.MyProjectName -csf -u -m react-native This command will prepare a solution with an **Angular** or an **MVC** (depends on your choice), a **.NET Core**, and a **React Native** project in it. -### 2. Direct Download +### 2. Generating a CLI Command from Get Started Page -You may [download a solution scaffold directly on ABP.io](https://abp.io/get-started) if you are more comfortable with GUI or simply want to try ABP without installing the CLI. - -Please do the following: - -1. Click on the "DIRECT DOWNLOAD" tab. -2. Fill out the short form about your project. -3. Click on the "Create now" button. - -...and a customized download will start in a few seconds. +You can generate a CLI command on the [get started page of the abp.io website](https://abp.io/get-started). Then, use the command on your terminal to create a new [Startup Template](./Startup-Templates/Index.md). ## How to Configure & Run the Backend @@ -54,26 +46,6 @@ Please do the following: > When you are using OpenIddict, You should remove 'clientSecret' on Environment.js (if exists) and disable "HTTPS-only" settings. (Openiddict has default since Version 6.0) -### How to disable Https-only in Openiddict. - -You should add this code on {{ if Tiered == "No" }}`MyProjectNameHttpApiHostModule`{{ else if Tiered == "Yes" }}`MyProjectNameAuthServerModule`{{ end }}. - -```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - #if DEBUG - PreConfigure(options => - { - options - .UseAspNetCore() - .DisableTransportSecurityRequirement(); - }); - #endif - //.... - } - -``` - A React Native application running on an Android emulator or a physical phone **can not connect to the backend** on `localhost`. To fix this problem, it is necessary to run the backend application on your **local IP address**. {{ if Tiered == "No"}} @@ -99,13 +71,14 @@ Run the backend application as described in the [getting started document](Getti ## How to disable the Https-only settings of OpenIddict -Go to MyProjectNameHttpApiHostModule.cs under the host project. And put these codes under the `PreConfigureServices` function. +Open the {{ if Tiered == "No" }}`MyProjectNameHttpApiHostModule`{{ else if Tiered == "Yes" }}`MyProjectNameAuthServerModule`{{ end }} project and copy-paste the below code-block to the `PreConfigureServices` method: ```csharp #if DEBUG - PreConfigure(options => { - options.UseAspNetCore() - .DisableTransportSecurityRequirement(); + PreConfigure(options => + { + options.UseAspNetCore() + .DisableTransportSecurityRequirement(); }); #endif ``` diff --git a/docs/en/Localization.md b/docs/en/Localization.md index a432d6afb2..4fec386c69 100644 --- a/docs/en/Localization.md +++ b/docs/en/Localization.md @@ -6,11 +6,13 @@ ABP's localization system is seamlessly integrated to the `Microsoft.Extensions. > This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. -Volo.Abp.Localization is the core package of the localization system. Install it to your project using the package manager console (PMC): +You can use the [ABP CLI](CLI.md) to install the Volo.Abp.Localization package to your project. Execute the following command in the folder of the .csproj file that you want to install the package on: +```bash +abp add-package Volo.Abp.Localization ``` -Install-Package Volo.Abp.Localization -``` + +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Localization). Then you can add **AbpLocalizationModule** dependency to your module: diff --git a/docs/en/Migration-Guides/docs/en/Migration-Guides/Abp-7_4.md b/docs/en/Migration-Guides/docs/en/Migration-Guides/Abp-7_4.md new file mode 100644 index 0000000000..5ec8eb6c65 --- /dev/null +++ b/docs/en/Migration-Guides/docs/en/Migration-Guides/Abp-7_4.md @@ -0,0 +1,8 @@ +# ABP Version 7.4 Migration Guide + +This document is a guide for upgrading ABP v7.3 solutions to ABP v7.4. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. + +## TemplateDefinition + +The `LocalizationResource(Type)` of `TemplateDefinition` class is changed to `LocalizationResourceName(string)`. + diff --git a/docs/en/Modules/Virtual-File-Explorer.md b/docs/en/Modules/Virtual-File-Explorer.md index 1a22418937..a952b2c1db 100644 --- a/docs/en/Modules/Virtual-File-Explorer.md +++ b/docs/en/Modules/Virtual-File-Explorer.md @@ -10,9 +10,13 @@ Virtual File Explorer Module provided a simple UI to view all files in [virtual #### 1- Use ABP CLI -It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command: +It is recommended to use the [ABP CLI](../CLI.md) to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command: -`abp add-module Volo.VirtualFileExplorer` +``` +abp add-module Volo.VirtualFileExplorer +``` + +> If you haven't done it yet, you first need to install the [ABP CLI](../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.VirtualFileExplorer.Web). #### 2- Manually install diff --git a/docs/en/MongoDB.md b/docs/en/MongoDB.md index bfdac23104..372ad74ae6 100644 --- a/docs/en/MongoDB.md +++ b/docs/en/MongoDB.md @@ -4,12 +4,14 @@ This document explains how to integrate MongoDB as a database provider to ABP ba ## Installation -`Volo.Abp.MongoDB` is the main nuget package for the MongoDB integration. Install it to your project (for a layered application, to your data/infrastructure layer): +`Volo.Abp.MongoDB` is the main NuGet package for the MongoDB integration. Install it to your project (for a layered application, to your data/infrastructure layer), You can use the [ABP CLI](CLI.md) to install it to your project. Execute the following command in the folder of the .csproj file of the layer: ``` -Install-Package Volo.Abp.MongoDB +abp add-package Volo.Abp.MongoDB ``` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.MongoDB). + Then add `AbpMongoDbModule` module dependency to your [module](Module-Development-Basics.md): ```c# diff --git a/docs/en/PlugIn-Modules.md b/docs/en/PlugIn-Modules.md index cbcc380de2..70f7af67fb 100644 --- a/docs/en/PlugIn-Modules.md +++ b/docs/en/PlugIn-Modules.md @@ -61,11 +61,13 @@ Create a simple **Class Library Project** in a solution: ![simple-plugin-library](images/simple-plugin-library.png) -You can add ABP Framework packages you need to use in the module. At least, you should add the `Volo.Abp.Core` package to the project: +You can add the ABP Framework packages that you need to use in the module. At least, you should add the `Volo.Abp.Core` package to the project, Execute the following command in the folder of the .csproj file that you want to install the package on: +````bash +abp add-package Volo.Abp.Core ```` -Install-Package Volo.Abp.Core -```` + + If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Core). Every [module](Module-Development-Basics.md) must declare a class derived from the `AbpModule`. Here, a simple module class that resolves a service and initializes it on the application startup: diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md index 23ed2371dd..61383898e6 100644 --- a/docs/en/Repositories.md +++ b/docs/en/Repositories.md @@ -140,9 +140,9 @@ public async Task> GetListAsync(string filter) Any standard LINQ method can be used over the `IQueryable` returned from the repository. -> This sample uses `ToList()` method, but it is **strongly suggested to use the asynchronous methods** to perform database queries, like `ToListAsync()` for this example. -> -> See the **IQueryable & Async Operations** section to learn how you can do it. +> This sample uses `ToList()` method, but it is **strongly suggested to use the asynchronous methods** to perform database queries, like `ToListAsync()` for this example. See the **`IQueryable` & Async Operations** section to learn how you can do it. + +> **Exposing `IQueryable` to outside of a repository** class may leak your data access logic to the application layer. If you want to strictly follow the **layered architecture** principles, you can consider to implement a custom repository class and wrap your data access logic inside your repository class. You can see the ***Custom Repositories*** section to learn how to create custom repository classes for your application. ### Bulk Operations diff --git a/docs/en/SMS-Sending.md b/docs/en/SMS-Sending.md index efe63c3c91..3043ce643a 100644 --- a/docs/en/SMS-Sending.md +++ b/docs/en/SMS-Sending.md @@ -17,6 +17,8 @@ Open a command line window in the folder of the project (.csproj file) and type abp add-package Volo.Abp.Sms ``` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Sms). + ### Manual Installation If you want to manually install; diff --git a/docs/en/SignalR-Integration.md b/docs/en/SignalR-Integration.md index d532067911..f441c64ae1 100644 --- a/docs/en/SignalR-Integration.md +++ b/docs/en/SignalR-Integration.md @@ -18,6 +18,8 @@ abp add-package Volo.Abp.AspNetCore.SignalR > You typically want to add this package to the web or API layer of your application, depending on your architecture. +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.SignalR). + #### Manual Installation If you want to manually install; diff --git a/docs/en/Text-Templating-Razor.md b/docs/en/Text-Templating-Razor.md index 44a207a991..bf911abd4a 100644 --- a/docs/en/Text-Templating-Razor.md +++ b/docs/en/Text-Templating-Razor.md @@ -16,6 +16,8 @@ Open a command line window in the folder of the project (.csproj file) and type abp add-package Volo.Abp.TextTemplating.Razor ```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.TextTemplating.Razor). + ### Manual Installation If you want to manually install; diff --git a/docs/en/UI/AspNetCore/Bundling-Minification.md b/docs/en/UI/AspNetCore/Bundling-Minification.md index d262a9dd7f..dd2282c883 100644 --- a/docs/en/UI/AspNetCore/Bundling-Minification.md +++ b/docs/en/UI/AspNetCore/Bundling-Minification.md @@ -12,27 +12,13 @@ ABP offers a simple, dynamic, powerful, modular and built-in way. > This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually. -Install the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget package to your project: +If you're not using a startup template, you can use the [ABP CLI](../../CLI.md) to install it to your project. Execute the following command in the folder that contains the .csproj file of your project: ```` -install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling +abp add-package Volo.Abp.AspNetCore.Mvc.UI.Bundling ```` -Then you can add the `AbpAspNetCoreMvcUiBundlingModule` dependency to your module: - -````C# -using Volo.Abp.Modularity; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] - public class MyWebModule : AbpModule - { - //... - } -} -```` +> If you haven't done it yet, you first need to install the [ABP CLI](../../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.Mvc.UI.Bundling). ## Razor Bundling Tag Helpers @@ -308,28 +294,13 @@ Using the built-in contributors for standard packages; > This package is already installed by default in the startup templates. So, most of the time, you don't need to install it manually. -Standard package contributors are defined in the `Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet package. -To install it to your project: +If you're not using a startup template, you can use the [ABP CLI](../../CLI.md) to install it to your project. Execute the following command in the folder that contains the .csproj file of your project: ```` -install-package Volo.Abp.AspNetCore.Mvc.UI.Packages +abp add-package Volo.Abp.AspNetCore.Mvc.UI.Packages ```` -Then add the `AbpAspNetCoreMvcUiPackagesModule` module dependency to your own module; - -````C# -using Volo.Abp.Modularity; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))] - public class MyWebModule : AbpModule - { - //... - } -} -```` +> If you haven't done it yet, you first need to install the [ABP CLI](../../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.Mvc.UI.Packages). ### Bundle Inheritance diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs index ea73a5673f..5811cc22c1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs @@ -1,6 +1,11 @@ -namespace Volo.Abp.AspNetCore.Components.Web.Theming.Layout; +using System; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Layout; public static class StandardLayouts { public const string Application = "Application"; + public const string Account = "Account"; + public const string Public = "Public"; + public const string Empty = "Empty"; } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/AbpThemingOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/AbpThemingOptions.cs new file mode 100644 index 0000000000..6bba37e0f8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/AbpThemingOptions.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class AbpThemingOptions +{ + public ThemeDictionary Themes { get; } + + public string DefaultThemeName { get; set; } + + public AbpThemingOptions() + { + Themes = new ThemeDictionary(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeManager.cs new file mode 100644 index 0000000000..c85a25bf19 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeManager.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class DefaultThemeManager : IThemeManager, IScopedDependency, IServiceProviderAccessor +{ + public IServiceProvider ServiceProvider { get; } + public ITheme CurrentTheme => GetCurrentTheme(); + + private ITheme _currentTheme; + + protected IThemeSelector ThemeSelector { get; } + + public DefaultThemeManager( + IServiceProvider serviceProvider, + IThemeSelector themeSelector) + { + ServiceProvider = serviceProvider; + ThemeSelector = themeSelector; + } + + protected virtual ITheme GetCurrentTheme() + { + if (_currentTheme != null) + { + return _currentTheme; + } + + _currentTheme = (ITheme)ServiceProvider.GetRequiredService(ThemeSelector.GetCurrentThemeInfo().ThemeType); + return CurrentTheme; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeSelector.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeSelector.cs new file mode 100644 index 0000000000..7e08ed1d97 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeSelector.cs @@ -0,0 +1,36 @@ +using System.Linq; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class DefaultThemeSelector : IThemeSelector, ITransientDependency +{ + protected AbpThemingOptions Options { get; } + + public DefaultThemeSelector(IOptions options) + { + Options = options.Value; + } + + public virtual ThemeInfo GetCurrentThemeInfo() + { + if (!Options.Themes.Any()) + { + throw new AbpException($"No theme registered! Use {nameof(AbpThemingOptions)} to register themes."); + } + + if (Options.DefaultThemeName == null) + { + return Options.Themes.Values.First(); + } + + var themeInfo = Options.Themes.Values.FirstOrDefault(t => t.Name == Options.DefaultThemeName); + if (themeInfo == null) + { + throw new AbpException("Default theme is configured but it's not found in the registered themes: " + Options.DefaultThemeName); + } + + return themeInfo; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ITheme.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ITheme.cs new file mode 100644 index 0000000000..f379252f52 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ITheme.cs @@ -0,0 +1,8 @@ +using System; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public interface ITheme +{ + Type GetLayout(string name, bool fallbackToDefault = true); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeManager.cs new file mode 100644 index 0000000000..75bb35e8d8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeManager.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public interface IThemeManager +{ + ITheme CurrentTheme { get; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeSelector.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeSelector.cs new file mode 100644 index 0000000000..6cf92c4cfa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeSelector.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public interface IThemeSelector +{ + ThemeInfo GetCurrentThemeInfo(); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeDictionary.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeDictionary.cs new file mode 100644 index 0000000000..bf33a1f083 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeDictionary.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class ThemeDictionary : Dictionary +{ + public ThemeInfo Add() + where TTheme : ITheme + { + return Add(typeof(TTheme)); + } + + public ThemeInfo Add(Type themeType) + { + if (ContainsKey(themeType)) + { + throw new AbpException("This theme is already added before: " + themeType.AssemblyQualifiedName); + } + + return this[themeType] = new ThemeInfo(themeType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeExtensions.cs new file mode 100644 index 0000000000..3236f491fe --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeExtensions.cs @@ -0,0 +1,27 @@ +using System; +using Volo.Abp.AspNetCore.Components.Web.Theming.Layout; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public static class ThemeExtensions +{ + public static Type GetApplicationLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Application, fallbackToDefault); + } + + public static Type GetAccountLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Account, fallbackToDefault); + } + + public static Type GetPublicLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Public, fallbackToDefault); + } + + public static Type GetEmptyLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Empty, fallbackToDefault); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeInfo.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeInfo.cs new file mode 100644 index 0000000000..a17f6c08ba --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeInfo.cs @@ -0,0 +1,24 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class ThemeInfo +{ + public Type ThemeType { get; } + + public string Name { get; } + + public ThemeInfo([NotNull] Type themeType) + { + Check.NotNull(themeType, nameof(themeType)); + + if (!typeof(ITheme).IsAssignableFrom(themeType)) + { + throw new AbpException($"Given {nameof(themeType)} ({themeType.AssemblyQualifiedName}) should be type of {typeof(ITheme).AssemblyQualifiedName}"); + } + + ThemeType = themeType; + Name = ThemeNameAttribute.GetName(themeType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeNameAttribute.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeNameAttribute.cs new file mode 100644 index 0000000000..fe4e6de371 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeNameAttribute.cs @@ -0,0 +1,23 @@ +using System; +using System.Linq; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +[AttributeUsage(AttributeTargets.Class)] +public class ThemeNameAttribute : Attribute +{ + public string Name { get; set; } + + public ThemeNameAttribute(string name) + { + Name = name; + } + + public static string GetName(Type themeType) + { + return themeType + .GetCustomAttributes(true) + .OfType() + .FirstOrDefault()?.Name ?? themeType.Name; + } +} diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs index c4f8d9674b..069b7bb85b 100644 --- a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs @@ -24,4 +24,16 @@ public class AbpDatabaseInfo DatabaseName = databaseName; MappedConnections = new HashSet(); } + + /// + /// Shortcut method to add one or multiple connections to the collection. + /// + /// + public void MapConnection(params string[] connectionNames) + { + foreach (var connectionName in connectionNames) + { + MappedConnections.AddIfNotContains(connectionName); + } + } } diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AppliedDatabaseMigrationsEto.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AppliedDatabaseMigrationsEto.cs new file mode 100644 index 0000000000..739571e241 --- /dev/null +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AppliedDatabaseMigrationsEto.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.EventBus; + +namespace Volo.Abp.Data; + +[Serializable] +[EventName("abp.data.applied_database_migrations")] +public class AppliedDatabaseMigrationsEto +{ + public string DatabaseName { get; set; } + public Guid? TenantId { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/DatabaseMigrationEventHandlerBase.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/DatabaseMigrationEventHandlerBase.cs new file mode 100644 index 0000000000..ce7e21dc0a --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/DatabaseMigrationEventHandlerBase.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.Migrations; + +public abstract class DatabaseMigrationEventHandlerBase : + IDistributedEventHandler, + IDistributedEventHandler, + IDistributedEventHandler, + ITransientDependency + where TDbContext : DbContext, IEfCoreDbContext +{ + protected string DatabaseName { get; } + + protected const string TryCountPropertyName = "__TryCount"; + + protected int MaxEventTryCount { get; set; } = 3; + + /// + /// As milliseconds. + /// + protected int MinValueToWaitOnFailure { get; set; } = 5000; + + /// + /// As milliseconds. + /// + protected int MaxValueToWaitOnFailure { get; set; } = 15000; + + protected ICurrentTenant CurrentTenant { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected ITenantStore TenantStore { get; } + protected IDistributedEventBus DistributedEventBus { get; } + protected ILogger> Logger { get; } + + protected DatabaseMigrationEventHandlerBase( + string databaseName, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + ITenantStore tenantStore, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory) + { + CurrentTenant = currentTenant; + UnitOfWorkManager = unitOfWorkManager; + TenantStore = tenantStore; + DatabaseName = databaseName; + DistributedEventBus = distributedEventBus; + + Logger = loggerFactory.CreateLogger>(); + } + + public virtual async Task HandleEventAsync(ApplyDatabaseMigrationsEto eventData) + { + if (eventData.DatabaseName != DatabaseName) + { + return; + } + + var schemaMigrated = false; + try + { + schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.TenantId); + await SeedAsync(eventData.TenantId); + + if (schemaMigrated) + { + await DistributedEventBus.PublishAsync( + new AppliedDatabaseMigrationsEto + { + DatabaseName = DatabaseName, + TenantId = eventData.TenantId + } + ); + } + } + catch (Exception ex) + { + await HandleErrorOnApplyDatabaseMigrationAsync(eventData, ex); + } + + await AfterApplyDatabaseMigrations(eventData, schemaMigrated); + } + + protected virtual Task AfterApplyDatabaseMigrations(ApplyDatabaseMigrationsEto eventData, bool schemaMigrated) + { + return Task.CompletedTask; + } + + public virtual async Task HandleEventAsync(TenantCreatedEto eventData) + { + var schemaMigrated = false; + try + { + schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.Id); + await SeedAsync(eventData.Id); + + if (schemaMigrated) + { + await DistributedEventBus.PublishAsync( + new AppliedDatabaseMigrationsEto + { + DatabaseName = DatabaseName, + TenantId = eventData.Id + } + ); + } + } + catch (Exception ex) + { + await HandleErrorTenantCreatedAsync(eventData, ex); + } + + await AfterTenantCreated(eventData, schemaMigrated); + } + + protected virtual Task AfterTenantCreated(TenantCreatedEto eventData, bool schemaMigrated) + { + return Task.CompletedTask; + } + + public virtual async Task HandleEventAsync(TenantConnectionStringUpdatedEto eventData) + { + if (eventData.ConnectionStringName != DatabaseName && + eventData.ConnectionStringName != Volo.Abp.Data.ConnectionStrings.DefaultConnectionStringName || + eventData.NewValue.IsNullOrWhiteSpace()) + { + return; + } + + var schemaMigrated = false; + try + { + schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.Id); + await SeedAsync(eventData.Id); + + if (schemaMigrated) + { + await DistributedEventBus.PublishAsync( + new AppliedDatabaseMigrationsEto + { + DatabaseName = DatabaseName, + TenantId = eventData.Id + } + ); + } + } + catch (Exception ex) + { + await HandleErrorTenantConnectionStringUpdatedAsync(eventData, ex); + } + + await AfterTenantConnectionStringUpdated(eventData, schemaMigrated); + } + + protected virtual Task AfterTenantConnectionStringUpdated(TenantConnectionStringUpdatedEto eventData, + bool schemaMigrated) + { + return Task.CompletedTask; + } + + protected virtual Task SeedAsync(Guid? tenantId) + { + return Task.CompletedTask; + } + + /// + /// Apply pending EF Core schema migrations to the database. + /// Returns true if any migration has applied. + /// + protected virtual async Task MigrateDatabaseSchemaAsync(Guid? tenantId) + { + var result = false; + + using (CurrentTenant.Change(tenantId)) + { + using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) + { + async Task MigrateDatabaseSchemaWithDbContextAsync() + { + var dbContext = await uow.ServiceProvider + .GetRequiredService>() + .GetDbContextAsync(); + + if ((await dbContext.Database.GetPendingMigrationsAsync()).Any()) + { + await dbContext.Database.MigrateAsync(); + return true; + } + + return false; + } + + if (tenantId == null) + { + //Migrating the host database + Logger.LogInformation($"Migrating database of host. Database Name = {DatabaseName}"); + result = await MigrateDatabaseSchemaWithDbContextAsync(); + } + else + { + var tenantConfiguration = await TenantStore.FindAsync(tenantId.Value); + if (!tenantConfiguration.ConnectionStrings.Default.IsNullOrWhiteSpace() || + !tenantConfiguration.ConnectionStrings.GetOrDefault(DatabaseName).IsNullOrWhiteSpace()) + { + //Migrating the tenant database (only if tenant has a separate database) + Logger.LogInformation( + $"Migrating separate database of tenant. Database Name = {DatabaseName}, TenantId = {tenantId}"); + result = await MigrateDatabaseSchemaWithDbContextAsync(); + } + } + + await uow.CompleteAsync(); + } + } + + return result; + } + + protected virtual async Task HandleErrorOnApplyDatabaseMigrationAsync( + ApplyDatabaseMigrationsEto eventData, + Exception exception) + { + var tryCount = IncrementEventTryCount(eventData); + if (tryCount <= MaxEventTryCount) + { + Logger.LogWarning( + $"Could not apply database migrations. Re-queueing the operation. TenantId = {eventData.TenantId}, Database Name = {eventData.DatabaseName}."); + Logger.LogException(exception, LogLevel.Warning); + + await Task.Delay(RandomHelper.GetRandom(MinValueToWaitOnFailure, MaxValueToWaitOnFailure)); + await DistributedEventBus.PublishAsync(eventData); + } + else + { + Logger.LogError( + $"Could not apply database migrations. Canceling the operation. TenantId = {eventData.TenantId}, DatabaseName = {eventData.DatabaseName}."); + Logger.LogException(exception); + } + } + + protected virtual async Task HandleErrorTenantCreatedAsync( + TenantCreatedEto eventData, + Exception exception) + { + var tryCount = IncrementEventTryCount(eventData); + if (tryCount <= MaxEventTryCount) + { + Logger.LogWarning( + $"Could not perform tenant created event. Re-queueing the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}."); + Logger.LogException(exception, LogLevel.Warning); + + await Task.Delay(RandomHelper.GetRandom(5000, 15000)); + await DistributedEventBus.PublishAsync(eventData); + } + else + { + Logger.LogError( + $"Could not perform tenant created event. Canceling the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}."); + Logger.LogException(exception); + } + } + + protected virtual async Task HandleErrorTenantConnectionStringUpdatedAsync( + TenantConnectionStringUpdatedEto eventData, + Exception exception) + { + var tryCount = IncrementEventTryCount(eventData); + if (tryCount <= MaxEventTryCount) + { + Logger.LogWarning( + $"Could not perform tenant connection string updated event. Re-queueing the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}."); + Logger.LogException(exception, LogLevel.Warning); + + await Task.Delay(RandomHelper.GetRandom(5000, 15000)); + await DistributedEventBus.PublishAsync(eventData); + } + else + { + Logger.LogError( + $"Could not perform tenant connection string updated event. Canceling the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}."); + Logger.LogException(exception); + } + } + + private static int GetEventTryCount(EtoBase eventData) + { + var tryCountAsString = eventData.Properties.GetOrDefault(TryCountPropertyName); + if (tryCountAsString.IsNullOrEmpty()) + { + return 0; + } + + return int.Parse(tryCountAsString); + } + + private static void SetEventTryCount(EtoBase eventData, int count) + { + eventData.Properties[TryCountPropertyName] = count.ToString(); + } + + private static int IncrementEventTryCount(EtoBase eventData) + { + var count = GetEventTryCount(eventData) + 1; + SetEventTryCount(eventData, count); + return count; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs new file mode 100644 index 0000000000..9b9719ff1b --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs @@ -0,0 +1,145 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.Migrations; + +public abstract class EfCoreRuntimeDatabaseMigratorBase : ITransientDependency + where TDbContext : DbContext, IEfCoreDbContext +{ + protected int MinValueToWaitOnFailure { get; set; } = 5000; + protected int MaxValueToWaitOnFailure { get; set; } = 15000; + + protected string DatabaseName { get; } + + /// + /// Enabling this might be inefficient if you have many tenants! + /// If disabled (default), tenant databases will be seeded only + /// if there is a schema migration applied to the host database. + /// If enabled, tenant databases will be seeded always on every service startup. + /// + protected bool AlwaysSeedTenantDatabases { get; set; } = false; + + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected IServiceProvider ServiceProvider { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected IDistributedEventBus DistributedEventBus { get; } + protected ILogger> Logger { get; } + + protected EfCoreRuntimeDatabaseMigratorBase( + string databaseName, + IUnitOfWorkManager unitOfWorkManager, + IServiceProvider serviceProvider, + ICurrentTenant currentTenant, + IAbpDistributedLock abpDistributedLock, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory) + { + DatabaseName = databaseName; + UnitOfWorkManager = unitOfWorkManager; + ServiceProvider = serviceProvider; + CurrentTenant = currentTenant; + DistributedLock = abpDistributedLock; + DistributedEventBus = distributedEventBus; + Logger = loggerFactory.CreateLogger>(); + } + + public virtual async Task CheckAndApplyDatabaseMigrationsAsync() + { + await TryAsync(LockAndApplyDatabaseMigrationsAsync); + } + + protected virtual async Task LockAndApplyDatabaseMigrationsAsync() + { + Logger.LogInformation($"Trying to acquire the distributed lock for database migration: {DatabaseName}."); + + var schemaMigrated = false; + + await using (var handle = await DistributedLock.TryAcquireAsync("DatabaseMigration_" + DatabaseName)) + { + if (handle is null) + { + Logger.LogInformation($"Distributed lock could not be acquired for database migration: {DatabaseName}. Operation cancelled."); + return; + } + + Logger.LogInformation($"Distributed lock is acquired for database migration: {DatabaseName}..."); + + using (CurrentTenant.Change(null)) + { + // Create database tables if needed + using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) + { + var dbContext = await ServiceProvider + .GetRequiredService>() + .GetDbContextAsync(); + + var pendingMigrations = await dbContext + .Database + .GetPendingMigrationsAsync(); + + if (pendingMigrations.Any()) + { + await dbContext.Database.MigrateAsync(); + schemaMigrated = true; + } + + await uow.CompleteAsync(); + } + } + + await SeedAsync(); + + if (schemaMigrated || AlwaysSeedTenantDatabases) + { + await DistributedEventBus.PublishAsync( + new AppliedDatabaseMigrationsEto + { + DatabaseName = DatabaseName, + TenantId = null + } + ); + } + } + + Logger.LogInformation($"Distributed lock has been released for database migration: {DatabaseName}..."); + } + + protected virtual Task SeedAsync() + { + return Task.CompletedTask; + } + + protected virtual async Task TryAsync(Func task, int maxTryCount = 3) + { + try + { + await task(); + } + catch (Exception ex) + { + maxTryCount--; + + if (maxTryCount <= 0) + { + throw; + } + + Logger.LogWarning($"{ex.GetType().Name} has been thrown. The operation will be tried {maxTryCount} times more. Exception:\n{ex.Message}. Stack Trace:\n{ex.StackTrace}"); + + await Task.Delay(RandomHelper.GetRandom(MinValueToWaitOnFailure, MaxValueToWaitOnFailure)); + + await TryAsync(task, maxTryCount); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj b/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj index 40d03c2f15..dfd64e0b1a 100644 --- a/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj +++ b/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs index ba87dfb4ae..43cc829010 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs @@ -29,7 +29,7 @@ public class AbpTemplateRenderer : ITemplateRenderer, ITransientDependency string cultureName = null, Dictionary globalContext = null) { - var templateDefinition = TemplateDefinitionManager.Get(templateName); + var templateDefinition = await TemplateDefinitionManager.GetAsync(templateName); var renderEngine = templateDefinition.RenderEngine; diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs index 3c98daa22b..28b2e28ad0 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs @@ -12,10 +12,13 @@ public class AbpTextTemplatingOptions public string DefaultRenderingEngine { get; set; } + public HashSet DeletedTemplates { get; } + public AbpTextTemplatingOptions() { DefinitionProviders = new TypeList(); ContentContributors = new TypeList(); RenderingEngines = new Dictionary(); + DeletedTemplates = new HashSet(); } } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IDynamicTemplateDefinitionStore.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IDynamicTemplateDefinitionStore.cs new file mode 100644 index 0000000000..e275eb39be --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IDynamicTemplateDefinitionStore.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating; + +public interface IDynamicTemplateDefinitionStore +{ + Task GetAsync([NotNull] string name); + + Task> GetAllAsync(); + + Task GetOrNullAsync(string name); +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IStaticTemplateDefinitionStore.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IStaticTemplateDefinitionStore.cs new file mode 100644 index 0000000000..d298a42541 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IStaticTemplateDefinitionStore.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating; + +public interface IStaticTemplateDefinitionStore +{ + Task GetAsync([NotNull] string name); + + Task> GetAllAsync(); + + Task GetOrNullAsync(string name); +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs index 31af5d1da5..fa20673cea 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs @@ -1,16 +1,17 @@ using System.Collections.Generic; +using System.Threading.Tasks; using JetBrains.Annotations; namespace Volo.Abp.TextTemplating; public interface ITemplateDefinitionManager { - [NotNull] - TemplateDefinition Get([NotNull] string name); + [ItemNotNull] + Task GetAsync([NotNull] string name); - [NotNull] - IReadOnlyList GetAll(); + [ItemNotNull] + Task> GetAllAsync(); - [CanBeNull] - TemplateDefinition GetOrNull(string name); + [ItemCanBeNull] + Task GetOrNullAsync(string name); } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/NullIDynamicTemplateDefinitionStore.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/NullIDynamicTemplateDefinitionStore.cs new file mode 100644 index 0000000000..326b525c0e --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/NullIDynamicTemplateDefinitionStore.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating; + +public class NullIDynamicTemplateDefinitionStore : IDynamicTemplateDefinitionStore, ISingletonDependency +{ + private readonly static Task CachedTemplateResult = Task.FromResult((TemplateDefinition)null); + + private readonly static Task> CachedFeaturesResult = Task.FromResult((IReadOnlyList)Array.Empty().ToImmutableList()); + + public Task GetAsync(string name) + { + return CachedTemplateResult; + } + + public Task> GetAllAsync() + { + return CachedFeaturesResult; + } + + public Task GetOrNullAsync(string name) + { + return CachedTemplateResult; + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs new file mode 100644 index 0000000000..d12db86da8 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating; + +public class StaticTemplateDefinitionStore : IStaticTemplateDefinitionStore, ISingletonDependency +{ + protected Lazy> TemplateDefinitions { get; } + + protected AbpTextTemplatingOptions Options { get; } + + protected IServiceProvider ServiceProvider { get; } + + public StaticTemplateDefinitionStore(IOptions options, IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + Options = options.Value; + + TemplateDefinitions = new Lazy>(CreateTextTemplateDefinitions, true); + } + + public virtual Task GetAsync(string name) + { + Check.NotNull(name, nameof(name)); + + var template = GetOrNullAsync(name); + + if (template == null) + { + throw new AbpException("Undefined template: " + name); + } + + return template; + } + + public virtual Task> GetAllAsync() + { + return Task.FromResult>(TemplateDefinitions.Value.Values.ToImmutableList()); + } + + public virtual Task GetOrNullAsync(string name) + { + return Task.FromResult(TemplateDefinitions.Value.GetOrDefault(name)); + } + + protected virtual IDictionary CreateTextTemplateDefinitions() + { + var templates = new Dictionary(); + + using (var scope = ServiceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) + .ToList(); + + var context = new TemplateDefinitionContext(templates); + + foreach (var provider in providers) + { + provider.PreDefine(context); + } + + foreach (var provider in providers) + { + provider.Define(context); + } + + foreach (var provider in providers) + { + provider.PostDefine(context); + } + } + + return templates; + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 58ffcc491f..64ecd1f4c7 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -26,14 +26,14 @@ public class TemplateContentProvider : ITemplateContentProvider, ITransientDepen _templateDefinitionManager = templateDefinitionManager; } - public virtual Task GetContentOrNullAsync( + public virtual async Task GetContentOrNullAsync( [NotNull] string templateName, [CanBeNull] string cultureName = null, bool tryDefaults = true, bool useCurrentCultureIfCultureNameIsNull = true) { - var template = _templateDefinitionManager.Get(templateName); - return GetContentOrNullAsync(template, cultureName); + var template = await _templateDefinitionManager.GetAsync(templateName); + return await GetContentOrNullAsync(template, cultureName); } public virtual async Task GetContentOrNullAsync( diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs index d028f46702..64aeba815e 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -28,7 +28,7 @@ public class TemplateDefinition : IHasNameWithLocalizableDisplayName public string Layout { get; set; } [CanBeNull] - public Type LocalizationResource { get; set; } + public string LocalizationResourceName { get; set; } public bool IsInlineLocalized { get; set; } @@ -60,14 +60,26 @@ public class TemplateDefinition : IHasNameWithLocalizableDisplayName public TemplateDefinition( [NotNull] string name, - [CanBeNull] Type localizationResource = null, + [NotNull] Type localizationResource, + [CanBeNull] ILocalizableString displayName = null, + bool isLayout = false, + string layout = null, + string defaultCultureName = null) + : this(name, LocalizationResourceNameAttribute.GetName(localizationResource), displayName, isLayout, layout, defaultCultureName) + { + + } + + public TemplateDefinition( + [NotNull] string name, + [CanBeNull] string localizationResourceName = null, [CanBeNull] ILocalizableString displayName = null, bool isLayout = false, string layout = null, string defaultCultureName = null) { Name = Check.NotNullOrWhiteSpace(name, nameof(name), MaxNameLength); - LocalizationResource = localizationResource; + LocalizationResourceName = localizationResourceName; DisplayName = displayName ?? new FixedLocalizableString(Name); IsLayout = isLayout; Layout = layout; diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index a282b5e5e2..7e2920e446 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -1,85 +1,50 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating; public class TemplateDefinitionManager : ITemplateDefinitionManager, ISingletonDependency { - protected Lazy> TemplateDefinitions { get; } + protected readonly IStaticTemplateDefinitionStore StaticStore; + protected readonly IDynamicTemplateDefinitionStore DynamicStore; - protected AbpTextTemplatingOptions Options { get; } - - protected IServiceProvider ServiceProvider { get; } - - public TemplateDefinitionManager( - IOptions options, - IServiceProvider serviceProvider) + public TemplateDefinitionManager(IStaticTemplateDefinitionStore staticStore, IDynamicTemplateDefinitionStore dynamicStore) { - ServiceProvider = serviceProvider; - Options = options.Value; - - TemplateDefinitions = - new Lazy>(CreateTextTemplateDefinitions, true); + StaticStore = staticStore; + DynamicStore = dynamicStore; } - public virtual TemplateDefinition Get(string name) + public virtual async Task GetAsync(string name) { - Check.NotNull(name, nameof(name)); - - var template = GetOrNull(name); - - if (template == null) + var permission = await GetOrNullAsync(name); + if (permission == null) { - throw new AbpException("Undefined template: " + name); + throw new AbpException("Undefined Template: " + name); } - return template; + return permission; } - public virtual IReadOnlyList GetAll() + public virtual async Task GetOrNullAsync(string name) { - return TemplateDefinitions.Value.Values.ToImmutableList(); - } + Check.NotNull(name, nameof(name)); - public virtual TemplateDefinition GetOrNull(string name) - { - return TemplateDefinitions.Value.GetOrDefault(name); + return await StaticStore.GetOrNullAsync(name) ?? await DynamicStore.GetOrNullAsync(name); } - protected virtual IDictionary CreateTextTemplateDefinitions() + public virtual async Task> GetAllAsync() { - var templates = new Dictionary(); - - using (var scope = ServiceProvider.CreateScope()) - { - var providers = Options - .DefinitionProviders - .Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) - .ToList(); - - var context = new TemplateDefinitionContext(templates); + var staticTemplates = await StaticStore.GetAllAsync(); + var staticTemplateNames = staticTemplates + .Select(p => p.Name) + .ToImmutableHashSet(); - foreach (var provider in providers) - { - provider.PreDefine(context); - } - - foreach (var provider in providers) - { - provider.Define(context); - } - - foreach (var provider in providers) - { - provider.PostDefine(context); - } - } + var dynamicTemplates = await DynamicStore.GetAllAsync(); - return templates; + /* We prefer static Templates over dynamics */ + return staticTemplates.Concat(dynamicTemplates.Where(d => !staticTemplateNames.Contains(d.Name))).ToImmutableList(); } } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs index f496a8fea8..a2c354801d 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs @@ -31,9 +31,9 @@ public abstract class TemplateRenderingEngineBase : ITemplateRenderingEngine protected virtual IStringLocalizer GetLocalizerOrNull(TemplateDefinition templateDefinition) { - if (templateDefinition.LocalizationResource != null) + if (templateDefinition.LocalizationResourceName != null) { - return StringLocalizerFactory.Create(templateDefinition.LocalizationResource); + return StringLocalizerFactory.CreateByResourceName(templateDefinition.LocalizationResourceName); } return StringLocalizerFactory.CreateDefaultOrNull(); diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs index 0ae297a136..27e400c082 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs @@ -5,10 +5,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles; public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentReader { + private IFileInfo _fileInfo; private string _content; public async Task ReadContentsAsync(IFileInfo fileInfo) { + _fileInfo = fileInfo; _content = await fileInfo.ReadAsStringAsync(); } @@ -21,4 +23,13 @@ public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentR return null; } + + public TemplateContentFileInfo GetFile() + { + return new TemplateContentFileInfo() + { + FileName = _fileInfo.Name, + FileContent = _content + }; + } } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileInfo.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileInfo.cs new file mode 100644 index 0000000000..5469700432 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileInfo.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.TextTemplating.VirtualFiles; + +public class TemplateContentFileInfo +{ + public string FileName { get; set; } + + public string FileContent { get; set; } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider.cs new file mode 100644 index 0000000000..0b049b07ab --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles; + +public class TemplateContentFileProvider : ITransientDependency +{ + protected IVirtualFileProvider VirtualFileProvider { get; } + + public TemplateContentFileProvider(IVirtualFileProvider virtualFileProvider) + { + VirtualFileProvider = virtualFileProvider; + } + + public async Task> GetFilesAsync(TemplateDefinition templateDefinition) + { + var files = new List(); + + var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); + if (virtualPath == null) + { + return files; + } + + var fileInfo = VirtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) + { + var directoryContents = VirtualFileProvider.GetDirectoryContents(virtualPath); + if (!directoryContents.Exists) + { + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); + } + + fileInfo = new VirtualDirectoryFileInfo(virtualPath, virtualPath, DateTimeOffset.UtcNow); + } + + if (fileInfo.IsDirectory) + { + //TODO: Configure file extensions. + var folderReader = new VirtualFolderLocalizedTemplateContentReader(new[] { ".tpl", ".cshtml" }); + await folderReader.ReadContentsAsync(VirtualFileProvider, virtualPath); + files.AddRange(folderReader.GetFiles()); + } + else + { + var singleFileReader = new FileInfoLocalizedTemplateContentReader(); + await singleFileReader.ReadContentsAsync(fileInfo); + files.Add(singleFileReader.GetFile()); + } + + return files; + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs index 6ff24e4801..828978b1d2 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; using Volo.Abp.VirtualFileSystem; @@ -8,7 +9,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles; public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader { - private Dictionary _dictionary; + private Dictionary _dictionary; private readonly string[] _fileExtension; public VirtualFolderLocalizedTemplateContentReader(string[] fileExtension) @@ -20,7 +21,7 @@ public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateCon IVirtualFileProvider virtualFileProvider, string virtualPath) { - _dictionary = new Dictionary(); + _dictionary = new Dictionary(); var directoryContents = virtualFileProvider.GetDirectoryContents(virtualPath); if (!directoryContents.Exists) @@ -35,7 +36,11 @@ public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateCon continue; } - _dictionary.Add(file.Name.RemovePostFix(_fileExtension), await file.ReadAsStringAsync()); + _dictionary.Add(file.Name.RemovePostFix(_fileExtension), new TemplateContentFileInfo() + { + FileName = file.Name, + FileContent = await file.ReadAsStringAsync() + }); } } @@ -46,6 +51,11 @@ public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateCon return null; } - return _dictionary.GetOrDefault(cultureName); + return _dictionary.GetOrDefault(cultureName)?.FileContent; + } + + public List GetFiles() + { + return _dictionary.Values.ToList(); } } diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs index 10312e1513..109547ada5 100644 --- a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs @@ -45,6 +45,11 @@ public class DefaultAbpCompiledViewProvider : IAbpCompiledViewProvider, ITransie } var templateContent = await _templateContentProvider.GetContentOrNullAsync(templateDefinition); + if (templateContent == null) + { + throw new AbpException($"Razor template content of {templateDefinition.Name} is null!"); + } + return CachedAssembles.GetOrAdd((templateDefinition.Name + templateContent).ToMd5(), await CreateAssembly(templateContent)); } diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs index 582ebaf907..86298f2daf 100644 --- a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs @@ -74,7 +74,7 @@ public class RazorTemplateRenderingEngine : TemplateRenderingEngineBase, ITransi Dictionary globalContext, object model = null) { - var templateDefinition = TemplateDefinitionManager.Get(templateName); + var templateDefinition = await TemplateDefinitionManager.GetAsync(templateName); var renderedContent = await RenderSingleTemplateAsync( templateDefinition, diff --git a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs index 4612ec04df..a4da669893 100644 --- a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs @@ -61,7 +61,7 @@ public class ScribanTemplateRenderingEngine : TemplateRenderingEngineBase, ITran Dictionary globalContext, object model = null) { - var templateDefinition = TemplateDefinitionManager.Get(templateName); + var templateDefinition = await TemplateDefinitionManager.GetAsync(templateName); var renderedContent = await RenderSingleTemplateAsync( templateDefinition, diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs index 626c74478b..d5a7723e75 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -75,7 +75,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency if (Options != null) { - throw new AbpException("This unit of work is already initialized before!"); + throw new AbpException("This unit of work has already been initialized."); } Options = _defaultOptions.Normalize(options.Clone()); @@ -84,7 +84,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual void Reserve(string reservationName) { - Check.NotNull(reservationName, nameof(reservationName)); + Check.NotNullOrWhiteSpace(reservationName, nameof(reservationName)); ReservationName = reservationName; IsReserved = true; @@ -188,12 +188,12 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual void AddDatabaseApi(string key, IDatabaseApi api) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); Check.NotNull(api, nameof(api)); if (_databaseApis.ContainsKey(key)) { - throw new AbpException("There is already a database API in this unit of work with given key."); + throw new AbpException("This unit of work already contains a database API for the given key."); } _databaseApis.Add(key, api); @@ -201,7 +201,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual IDatabaseApi GetOrAddDatabaseApi(string key, Func factory) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); Check.NotNull(factory, nameof(factory)); return _databaseApis.GetOrAdd(key, factory); @@ -209,19 +209,19 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual ITransactionApi FindTransactionApi(string key) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); return _transactionApis.GetOrDefault(key); } public virtual void AddTransactionApi(string key, ITransactionApi api) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); Check.NotNull(api, nameof(api)); if (_transactionApis.ContainsKey(key)) { - throw new AbpException("There is already a transaction API in this unit of work with given key."); + throw new AbpException("This unit of work already contains a transaction API for the given key."); } _transactionApis.Add(key, api); @@ -229,7 +229,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual ITransactionApi GetOrAddTransactionApi(string key, Func factory) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); Check.NotNull(factory, nameof(factory)); return _transactionApis.GetOrAdd(key, factory); @@ -332,7 +332,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency { if (IsCompleted || _isCompleting) { - throw new AbpException("Complete is called before!"); + throw new AbpException("Completion has already been requested for this unit of work."); } } diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs index 6a0edcb3f5..fada3effe7 100644 --- a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs @@ -38,7 +38,7 @@ public class AbpCompiledViewProviderOptions_Tests : TemplateDefinitionTests +{ + [Fact] + public async Task GetRazorFilesAsync() + { + var definition = await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail); + var files = await TemplateContentFileProvider.GetFilesAsync(definition); + files.Count.ShouldBe(2); + files.ShouldContain(x => x.FileName == "en.cshtml" && x.FileContent.Contains("Welcome @Model.Name to the abp.io!")); + files.ShouldContain(x => x.FileName == "tr.cshtml" && x.FileContent.Contains("Merhaba @Model.Name, abp.io'ya hoşgeldiniz!")); + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ScribanTemplateContentFileProvider_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ScribanTemplateContentFileProvider_Tests.cs new file mode 100644 index 0000000000..674173313c --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ScribanTemplateContentFileProvider_Tests.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.TextTemplating.VirtualFiles; +using Xunit; + +namespace Volo.Abp.TextTemplating.Scriban.SampleTemplates; + +public class ScribanTemplateContentFileProvider_Tests : TemplateContentFileProvider_Tests +{ + [Fact] + public async Task GetScribanFilesAsync() + { + var definition = await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail); + var files = await TemplateContentFileProvider.GetFilesAsync(definition); + files.Count.ShouldBe(2); + files.ShouldContain(x => x.FileName == "en.tpl" && x.FileContent.Contains("Welcome {{model.name}} to the abp.io!")); + files.ShouldContain(x => x.FileName == "tr.tpl" && x.FileContent.Contains("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!")); + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs index 036080cc26..02ce27d2b7 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -1,4 +1,5 @@ -using Shouldly; +using System.Threading.Tasks; +using Shouldly; using Volo.Abp.Modularity; using Xunit; @@ -15,28 +16,28 @@ public abstract class TemplateDefinitionTests : AbpTextTemplatin } [Fact] - public void Should_Retrieve_Template_Definition_By_Name() + public async Task Should_Retrieve_Template_Definition_By_Name() { - var welcomeEmailTemplate = TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail); + var welcomeEmailTemplate = await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail); welcomeEmailTemplate.Name.ShouldBe(TestTemplates.WelcomeEmail); welcomeEmailTemplate.IsInlineLocalized.ShouldBeFalse(); - var forgotPasswordEmailTemplate = TemplateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail); + var forgotPasswordEmailTemplate = await TemplateDefinitionManager.GetAsync(TestTemplates.ForgotPasswordEmail); forgotPasswordEmailTemplate.Name.ShouldBe(TestTemplates.ForgotPasswordEmail); forgotPasswordEmailTemplate.IsInlineLocalized.ShouldBeTrue(); } [Fact] - public void Should_Get_Null_If_Template_Not_Found() + public async Task Should_Get_Null_If_Template_Not_Found() { - var definition = TemplateDefinitionManager.GetOrNull("undefined-template"); + var definition = await TemplateDefinitionManager.GetOrNullAsync("undefined-template"); definition.ShouldBeNull(); } [Fact] - public void Should_Retrieve_All_Template_Definitions() + public async Task Should_Retrieve_All_Template_Definitions() { - var definitions = TemplateDefinitionManager.GetAll(); + var definitions = await TemplateDefinitionManager.GetAllAsync(); definitions.Count.ShouldBeGreaterThan(1); } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs index 7ba1850d9c..aee41db4be 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs @@ -24,7 +24,7 @@ public abstract class LocalizedTemplateContentReaderFactory_Tests : AbpTextTemplatingTestBase + where TStartupModule : IAbpModule +{ + protected readonly TemplateContentFileProvider TemplateContentFileProvider; + protected readonly ITemplateDefinitionManager TemplateDefinitionManager; + + protected TemplateContentFileProvider_Tests() + { + TemplateContentFileProvider = GetRequiredService(); + TemplateDefinitionManager = GetRequiredService(); + } + + [Fact] + public async Task GetFilesAsync() + { + var definition = await TemplateDefinitionManager.GetAsync(TestTemplates.HybridTemplateScriban); + + var files = await TemplateContentFileProvider.GetFilesAsync(definition); + files.Count.ShouldBe(1); + files.ShouldContain(x => x.FileName == "TestScribanTemplate.tpl" && x.FileContent == "Hello {{model.name}}, {{L \"HowAreYou\" }}"); + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 89dc93051c..f2f0cdafef 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -24,13 +24,13 @@ public abstract class VirtualFileTemplateContributor_Tests : Abp public async Task Should_Get_Localized_Content_By_Culture() { (await VirtualFileTemplateContentContributor.GetOrNullAsync( - new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail), + new TemplateContentContributorContext(await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail), ServiceProvider, "en"))) .ShouldBe(WelcomeEmailEnglishContent); (await VirtualFileTemplateContentContributor.GetOrNullAsync( - new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail), + new TemplateContentContributorContext(await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail), ServiceProvider, "tr"))) .ShouldBe(WelcomeEmailTurkishContent); @@ -41,7 +41,7 @@ public abstract class VirtualFileTemplateContributor_Tests : Abp { (await VirtualFileTemplateContentContributor.GetOrNullAsync( new TemplateContentContributorContext( - TemplateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail), + await TemplateDefinitionManager.GetAsync(TestTemplates.ForgotPasswordEmail), ServiceProvider, null))) .ShouldBe(ForgotPasswordEmailEnglishContent); diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj index acad01b8e9..980afb22d5 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj @@ -6,7 +6,7 @@ - + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs index f20432c665..a983d239e3 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs @@ -1,12 +1,24 @@ using Volo.Abp.AspNetCore.Components.Web.Theming; +using Volo.Abp.AspNetCore.Components.Web.Theming.Theming; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme; [DependsOn( typeof(AbpAspNetCoreComponentsWebThemingModule) - )] +)] public class AbpAspNetCoreComponentsWebBasicThemeModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Themes.Add(); -} + if (options.DefaultThemeName == null) + { + options.DefaultThemeName = BasicTheme.Name; + } + }); + } +} \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/BasicTheme.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/BasicTheme.cs new file mode 100644 index 0000000000..2017cc8001 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/BasicTheme.cs @@ -0,0 +1,26 @@ +using System; +using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic; +using Volo.Abp.AspNetCore.Components.Web.Theming.Layout; +using Volo.Abp.AspNetCore.Components.Web.Theming.Theming; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme; + +[ThemeName(Name)] +public class BasicTheme : ITheme, ITransientDependency +{ + public const string Name = "Basic"; + + public virtual Type GetLayout(string name, bool fallbackToDefault = true) + { + switch (name) + { + case StandardLayouts.Application: + case StandardLayouts.Account: + case StandardLayouts.Empty: + return typeof(MainLayout); + default: + return fallbackToDefault ? typeof(MainLayout) : typeof(NullLayout); + } + } +} \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NullLayout.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NullLayout.razor new file mode 100644 index 0000000000..4e04080071 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NullLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs index 60abbb3054..2dc1963505 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs @@ -252,10 +252,10 @@ public class EfCoreIdentityUserRepository : EfCoreRepository x.Email == emailAddress) .WhereIf(!string.IsNullOrWhiteSpace(name), x => x.Name == name) .WhereIf(!string.IsNullOrWhiteSpace(surname), x => x.Surname == surname) - .WhereIf(isLockedOut.HasValue, x => x.LockoutEnabled && x.LockoutEnd.Value.CompareTo(DateTime.UtcNow) > 0) - .WhereIf(notActive.HasValue, x => !x.IsActive) - .WhereIf(emailConfirmed.HasValue, x => x.EmailConfirmed) - .WhereIf(isExternal.HasValue, x => x.IsExternal) + .WhereIf(isLockedOut.HasValue, x => (x.LockoutEnabled && x.LockoutEnd.HasValue && x.LockoutEnd.Value.CompareTo(DateTime.UtcNow) > 0) == isLockedOut.Value) + .WhereIf(notActive.HasValue, x => x.IsActive == !notActive.Value) + .WhereIf(emailConfirmed.HasValue, x => x.EmailConfirmed == emailConfirmed.Value) + .WhereIf(isExternal.HasValue, x => x.IsExternal == isExternal.Value) .WhereIf(maxCreationTime != null, p => p.CreationTime <= maxCreationTime) .WhereIf(minCreationTime != null, p => p.CreationTime >= minCreationTime) .WhereIf(maxModifitionTime != null, p => p.LastModificationTime <= maxModifitionTime) diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs index ae805d9a03..552e22afbe 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs @@ -257,10 +257,11 @@ public class MongoIdentityUserRepository : MongoDbRepository>(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email == emailAddress) .WhereIf>(!string.IsNullOrWhiteSpace(name), x => x.Name == name) .WhereIf>(!string.IsNullOrWhiteSpace(surname), x => x.Surname == surname) - .WhereIf>(isLockedOut.HasValue, x => x.LockoutEnabled && x.LockoutEnd > DateTimeOffset.UtcNow) - .WhereIf>(notActive.HasValue, x => !x.IsActive) - .WhereIf>(emailConfirmed.HasValue, x => x.EmailConfirmed) - .WhereIf>(isExternal.HasValue, x => x.IsExternal) + .WhereIf>(isLockedOut.HasValue && isLockedOut.Value, x => x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow) + .WhereIf>(isLockedOut.HasValue && !isLockedOut.Value, x => !(x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow)) + .WhereIf>(notActive.HasValue, x => x.IsActive == !notActive.Value) + .WhereIf>(emailConfirmed.HasValue, x => x.EmailConfirmed == emailConfirmed.Value) + .WhereIf>(isExternal.HasValue, x => x.IsExternal == isExternal.Value) .WhereIf>(maxCreationTime != null, p => p.CreationTime <= maxCreationTime) .WhereIf>(minCreationTime != null, p => p.CreationTime >= minCreationTime) .WhereIf>(maxModifitionTime != null, p => p.LastModificationTime <= maxModifitionTime) diff --git a/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs b/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs index 501cf0b543..2e55cc24b1 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs @@ -134,7 +134,7 @@ tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCreden ClientId = clientId, ClientSecret = clientSecret, - Scope = "AbpAPI profile roles email phone offline_access", + Scope = "AbpAPI offline_access", }); if (tokenResponse.IsError) diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.ClientCredentials.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.ClientCredentials.cs index 09f685ac83..f3cdd54485 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.ClientCredentials.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.ClientCredentials.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Collections.Immutable; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; @@ -47,27 +45,8 @@ public partial class TokenController principal.SetScopes(request.GetScopes()); principal.SetResources(await GetResourcesAsync(request.GetScopes())); - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim)); - } + await OpenIddictClaimsPrincipalManager.HandleAsync(request, principal); return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } - - - protected virtual IEnumerable GetDestinations(Claim claim) - { - // Note: by default, claims are NOT automatically included in the access and identity tokens. - // To allow OpenIddict to serialize them, you must attach them a destination, that specifies - // whether they should be included in access tokens, in identity tokens or in both. - - return claim.Type switch { - OpenIddictConstants.Claims.PreferredUsername or OpenIddictConstants.Claims.Subject - => ImmutableArray.Create(OpenIddictConstants.Destinations.AccessToken, - OpenIddictConstants.Destinations.IdentityToken), - - _ => ImmutableArray.Create(OpenIddictConstants.Destinations.AccessToken) - }; - } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateAuthorizedParty.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateAuthorizedParty.cs index 81c7d66788..89ebfee28c 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateAuthorizedParty.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateAuthorizedParty.cs @@ -25,7 +25,6 @@ public class AbpValidateAuthorizedParty : AbpOpenIddictWildcardDomainBase @foreach (var group in SettingComponentCreationContext.Groups) { - +

@group.DisplayName

diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.js b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.js index 07b40c2c38..3371f18261 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.js +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.js @@ -19,8 +19,8 @@ processData: false }).done(function (response) { $('#tab-content').children('.tab-pane').removeClass('show').removeClass('active'); - _this.attr('data-bs-target', '#' + $.escapeSelector($.escapeSelector(id))); - $('#tab-content').append('
' + response + '
'); + _this.attr('data-bs-target', '#' + $.escapeSelector(id)); + $('#tab-content').append('
' + response + '
'); }) }); }).first().click(); diff --git a/npm/ng-packs/apps/dev-app/src/app/app.module.ts b/npm/ng-packs/apps/dev-app/src/app/app.module.ts index ac56d2736e..7e903dff0d 100644 --- a/npm/ng-packs/apps/dev-app/src/app/app.module.ts +++ b/npm/ng-packs/apps/dev-app/src/app/app.module.ts @@ -12,7 +12,7 @@ import { SettingManagementConfigModule } from '@abp/ng.setting-management/config import { TenantManagementConfigModule } from '@abp/ng.tenant-management/config'; import { FeatureManagementModule } from '@abp/ng.feature-management'; import { AccountConfigModule } from '@abp/ng.account/config'; -import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account/account-layout'; +import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account'; import { environment } from '../environments/environment'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; diff --git a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html index d7e0b6737b..154b8cfba8 100644 --- a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html +++ b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html @@ -1,4 +1,4 @@ -
+