",
"ChangingDevelopers": "我将来可以更改我组织的注册开发人员吗?",
"ChangingDevelopersExplanation": "除了将新的开发人员添加到你的许可中之外,你还可以更改现有的开发人员(可以删除一个开发人员并将新的开发人员添加到同一位置),而无需任何额外费用.",
+ "WhatHappensWhenLicenseEnds": "我的许可证期间结束时,会发生什么?",
+ "WhatHappensWhenLicenseEndsExplanation1": "你的ABP商业许可证是一个永久许可证。你的许可证到期后,你可以继续开发你的项目。并且你不需要续费你的许可证。你的许可证是一年更新和支持方案默认的。为了继续获得新的功能,性能改进,修复,支持和继续使用ABP Suite,你需要续费你的许可证。你的许可证到期后,你将失去以下功能:",
+ "WhatHappensWhenLicenseEndsExplanation2": "你不能使用ABP商业创建新的解决方案,但是你可以继续开发你现有的应用程序。",
+ "WhatHappensWhenLicenseEndsExplanation3": "你将能够获得模块和主题的更新(除了RC或预览版本)。例如,如果你使用模块的v3.2.0版本,你仍然可以获得v3.2.x版本的更新(v3.2.1,v3.2.5...等等)。但是,你不能获得下一个主要或次要版本的更新(例如v3.3.0,v3.3.3,4.x.x...等等)。例如,你的许可证到期后,最新的发行是v4.4.3,之后,它发布了4.4.4版本和4.5.0版本,你将能够访问v4.4.X,但你不能访问v4.5.X。",
+ "WhatHappensWhenLicenseEndsExplanation4": "你不能在你的许可证到期后安装ABP商业平台上添加的新模块和主题。",
+ "WhatHappensWhenLicenseEndsExplanation5": "你不能使用ABP Suite。",
+ "WhatHappensWhenLicenseEndsExplanation6": "你不能再获得高级支持。",
+ "WhatHappensWhenLicenseEndsExplanation7": "如果你想继续获得这些奖励,你可以继续续费你的许可证。如果你在许可证到期后1个月内续费,将会应用以下折扣:团队许可证{0}%折扣,商业许可证{1}%折扣,企业许可证{2}%折扣。",
"WhatHappensWhenLicenseEndsExplanation8": "您生成的 ABP 项目未存储在我们的服务器上。 因此,您有责任保留下载的源代码。 当您的许可证到期时,将无法获取您生成的 ABP 项目源代码。",
"WhenShouldIRenewMyLicense": "我什么时候应该续订我的许可?",
"WhenShouldIRenewMyLicenseExplanation": "如果您在许可证到期后 1 个月内续订许可证,将享受以下折扣:团队许可证 {0}% 折扣、商业许可证 {1}% 折扣、企业许可证 {2}% 折扣 . 如果您在许可证到期后 1 个月续订许可证,续订价格将与许可证购买价格相同,并且续订不会有折扣。",
"TrialPlan": "你们有试用计划吗?",
+ "TrialPlanExplanation": "ABP商业团队许可证有14天的试用期。若要了解更多信息,请访问这里。此外,我们为团队许可证提供30天的金额返还保证。你只需要在30天内请求退款。商业和企业许可证将提供60%的金额返还保证。这是因为商业和企业许可证包含了所有模块和主题的全部源代码。",
"DoYouAcceptBankWireTransfer": "你们接受银行电汇吗?",
"DoYouAcceptBankWireTransferExplanation": "是的,我们接受银行电汇。 在通过银行转账发送许可费后,将您的收据和所需的许可类型通过电子邮件发送至accounting@abp.io。 我们的国际银行账户信息:",
"HowToUpgrade": "可用新版本时如何升级现有应用程序?",
@@ -365,7 +374,9 @@
"CompanySize": "公司规模",
"Next": "下一个",
"StartTrial": "开始我的免费试用",
+ "ContactUsQuestions": "如果你有任何问题,请联系我们",
"TrialActivatedWarning": "亲爱的{0},用户只能享受 1 个免费试用期。您已经使用了试用期。",
+ "ActivationRequirement": "你已经距离开始你的试用还有最后一步。 检查你的信息后,我们将激活你的许可证。一旦你的许可证激活,我们将向{0}发送电子邮件。请不要担心,这个过程不会太久!",
"SaveAndDownload": "保存和下载",
"CompanyNameValidationMessage": "公司名称太长!",
"AddressValidationMessage": "地址太长!",
@@ -389,6 +400,7 @@
"UserDeveloperDescription": "“开发人员”可以在 ABP 商业版项目中编写代码,下载 ABP 示例项目,并在支持的网站上进行提问。 然而另一方面,“开发者”无法管理这个组织。",
"RemoveCurrentUserFromOrganizationWarningMessage": "您正在将自己从自己的组织中移除。 您将无法再管理此组织,您确定吗?",
"RenewExistingOrganizationOrCreateNewOneMessage": "您可以通过单击下面的 \"立即延长\"按钮来更新您组织的许可证,因此您可以将许可证到期日期延长 1 年。 如果您继续结帐,您将拥有一个新的组织。 您想继续开始新的组织许可吗?",
+ "PurchaseTrialOrganizationOrCreateNewOneMessage": "你有试用许可证。如果你想购买试用许可证,请点击“立即购买”按钮。如果你继续支付,你将会有一个新的组织。你想继续使用一个新的组织吗?",
"ExtendNow": "立即延长",
"CreateNewOrganization": "建立新的组织",
"RenewLicenseEarly": "如果我提前更新我的许可证,我会得到一整年吗?",
@@ -524,6 +536,7 @@
"Pricing_Page_Testimonial_2": "我们看到了使用 ABP Commercial 能减少定制开发项目开销的价值。 并且团队能够在不同的项目流中统一代码模式。 我们在框架中看到了能比以前更快地构建新功能的更多潜力。 我们相信我们将会持续地看到使用 ABP Commercial 的价值。",
"Pricing_Page_Testimonial_3": "我们大爱 ABP。 我们不必从头开始编写所有内容。 我们从\"开箱即用\"的功能开始,只需关注我们真正需要编写的内容。 此外,ABP 架构良好,代码质量高,错误少。 如果我们需要自己来编写所需的一切,我们可能需要花费数年时间。 另一点让我们喜欢的是新版本、问题修复或改进每隔一周很快地就会出现。 我们不会等太久。",
"Pricing_Page_Testimonial_4": "ABP 商业版 是一款很值得推荐的出色产品。 是在一个可配置的平台上为我们的客户推向市场的商业产品。 其框架和工具为任何团队提供的快速启动值得每一分钱。 ABP 商业版 最适合我们的需求。",
+ "Pricing_Page_Testimonial_5": "ABP Framework 不仅是一个框架,它还是项目开发/管理的指南,因为它提供了 DDD、GenericRepository、DI、微服务和模块化培训。 即使你不打算使用框架本身,你也可以通过 docs.abp.io 进行自己的开发,该文档已经做好了专业的准备(OpenIddict、Redis、Quartz 等)。 因为很多东西都是预先构建的,它大大缩短了项目开发时间(例如登录页面、异常处理、数据过滤、种子、审计日志、本地化、自动 API 控制器等)。 作为我们应用程序的一个示例,我使用本地事件总线进行库存控制。 因此我可以通过编写库存处理程序来管理订单移动。 不为 CreationTime,CreatorId 浪费时间真是太好了。 它们正在自动填充。",
"AbpBookDownloadArea_ClaimYourEBook": "领取您的掌握ABP框架电子书",
"AddMemberModal_Warning_1": "如果您尝试添加的用户名在系统中不存在,请让您的团队成员在 {0} 上注册 a> 并与您分享他/她帐户的用户名。",
"MyOrganizations_Detail_WelcomeMessage": "欢迎加入您的组织,{0}",
@@ -591,6 +604,9 @@
"TrainingDetailsHeaderInfo_TrainingHour": "{0} 小时",
"Trainings_Content": "培训内容",
"Trial_Page_StartYourFreeTrial": "开始您的免费试用",
+ "TrialLicenseFeatures": "你将能够享受所有 ABP 商业特性",
+ "TrialPeriodDays": "你将有 {0} 天的团队许可证",
+ "TrialForumSupportIncident": "你将有 {0} 个论坛支持事件",
"Contact_Page_Title": "联系 ABP 开发团队",
"Contact_Page_Description": "如果您需要任何帮助或分享您的想法和意见,请与 ABP 开发团队联系! ABP 支持团队随时准备提供帮助。",
"Demo_Page_Title": "创建演示",
@@ -712,6 +728,13 @@
"Landing_Page_PaymentModuleDescription_1": "为不同的支付网关提供集成。",
"Landing_Page_PaymentModuleDescription_2": "该模块提供支付网关的集成,因此您可以轻松地从客户那里获得付款。",
"Landing_Page_PaymentModuleDescription_3": "该模块支持以下支付网关",
- "Welcome_Page_UseSameCredentialForCommercialWebsites": "commercial.abp.io 和 support.abp.io使用相同的凭据。"
+ "Welcome_Page_UseSameCredentialForCommercialWebsites": "commercial.abp.io 和 support.abp.io使用相同的凭据。",
+ "WatchCrudPagesVideo": "观看“创建 CRUD 页面的 ABP Suite”视频!",
+ "WatchGeneratingFromDatabaseVideo": "观看”ABP Suite:从现有数据库表生成 CRUD 页面”视频!",
+ "WatchTakeCloserLookVideo": "观看“详细了解ABP Suite 的代码生成”视频!",
+ "ConfirmedEmailAddressRequiredToStartTrial": "你应该有一个确认的电子邮件地址,以便开始试用许可证。",
+ "EmailVerificationMailNotSent": "电子邮件验证邮件不能发送。",
+ "GetConfirmationEmail": "点击这里获取确认邮件 如果你还没有收到。",
+ "WhichLicenseTypeYouAreInterestedIn": "你感兴趣的许可证类型是什么?"
}
-}
+}
\ No newline at end of file
diff --git a/build/common.ps1 b/build/common.ps1
index 01a7565fba..61d9cf03d2 100644
--- a/build/common.ps1
+++ b/build/common.ps1
@@ -20,8 +20,7 @@ $solutionPaths = @(
"../modules/background-jobs",
"../modules/account",
"../modules/cms-kit",
- "../modules/blob-storing-database",
- "../studio"
+ "../modules/blob-storing-database"
)
if ($full -eq "-f")
diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md
index 4ee58d84ef..869a8075af 100644
--- a/docs/en/Background-Jobs-Hangfire.md
+++ b/docs/en/Background-Jobs-Hangfire.md
@@ -80,6 +80,41 @@ After you have installed these NuGet packages, you need to configure your projec
}
````
+### Specifying Queue
+
+You can use the [`QueueAttribute`](https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html) to specify the queue.
+
+````csharp
+using System.Threading.Tasks;
+using Volo.Abp.BackgroundJobs;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Emailing;
+
+namespace MyProject
+{
+ [Queue("alpha")]
+ public class EmailSendingJob
+ : AsyncBackgroundJob, ITransientDependency
+ {
+ private readonly IEmailSender _emailSender;
+
+ public EmailSendingJob(IEmailSender emailSender)
+ {
+ _emailSender = emailSender;
+ }
+
+ public override async Task ExecuteAsync(EmailSendingArgs args)
+ {
+ await _emailSender.SendAsync(
+ args.EmailAddress,
+ args.Subject,
+ args.Body
+ );
+ }
+ }
+}
+````
+
### Dashboard Authorization
Hangfire Dashboard provides information about your background jobs, including method names and serialized arguments as well as gives you an opportunity to manage them by performing different actions – retry, delete, trigger, etc. So it is important to restrict access to the Dashboard.
diff --git a/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md b/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md
index 1961726b8f..4823cbaae9 100644
--- a/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md
+++ b/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md
@@ -86,7 +86,7 @@ Defining multiple connections is allowed. In this case, you can specify the conn
This allows you to use multiple RabbitMQ server in your application, but select one of them for the event bus.
-You can use any of the [ConnectionFactry](http://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.ConnectionFactory.html#properties) properties as the connection properties.
+You can use any of the [ConnectionFactory](http://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.ConnectionFactory.html#properties) properties as the connection properties.
**Example: Specify the connection port**
@@ -152,4 +152,4 @@ Configure(options =>
});
````
-Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file.
\ No newline at end of file
+Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file.
diff --git a/docs/en/Distributed-Locking.md b/docs/en/Distributed-Locking.md
index d29b60ad04..a1e653a6a6 100644
--- a/docs/en/Distributed-Locking.md
+++ b/docs/en/Distributed-Locking.md
@@ -101,6 +101,25 @@ namespace AbpDemo
* `timeout` (`TimeSpan`): A timeout value to wait to obtain the lock. Default value is `TimeSpan.Zero`, which means it doesn't wait if the lock is already owned by another application.
* `cancellationToken`: A cancellation token that can be triggered later to cancel the operation.
+### Configuration
+
+#### AbpDistributedLockOptions
+
+`AbpDistributedLockOptions` is the main options class to configure the distributed locking.
+
+**Example: Set the distributed lock key prefix for the application**
+
+Configure(options =>
+{
+ options.KeyPrefix = "MyApp1";
+});
+
+> Write that code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md).
+
+##### Available Options
+
+* KeyPrefix (string, default: null): Specify the lock name prefix.
+
### Using DistributedLock Library's API
ABP's `IAbpDistributedLock` service is very limited and mainly designed to be internally used by the ABP Framework. For your own applications, you can use the DistributedLock library's own API. See its [own documentation](https://github.com/madelson/DistributedLock) for details.
diff --git a/docs/en/Entities.md b/docs/en/Entities.md
index c57af27457..ef8f52a04f 100644
--- a/docs/en/Entities.md
+++ b/docs/en/Entities.md
@@ -112,6 +112,21 @@ For the example above, the composite key is composed of `UserId` and `RoleId`. F
> Also note that Entities with Composite Primary Keys cannot utilize the `IRepository` interface since it requires a single Id property. However, you can always use `IRepository`. See [repositories documentation](Repositories.md) for more.
+### EntityEquals
+
+`Entity.EntityEquals(...)` method is used to check if two Entity Objects are equals.
+
+Example:
+
+```csharp
+Book book1 = ...
+Book book2 = ...
+
+if (book1.EntityEquals(book2)) //Check equality
+{
+ ...
+}
+```
## AggregateRoot Class
diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md
index 569bfeb4ca..7dac3762aa 100644
--- a/docs/en/Multi-Tenancy.md
+++ b/docs/en/Multi-Tenancy.md
@@ -222,7 +222,6 @@ The following resolvers are provided and configured by default;
* `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**.
* `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default.
-* `FormTenantResolveContributor`:Tries to find current tenant id from form parameters. The parameter name is `__tenant` by default.
* `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route.
* `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default.
* `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default.
diff --git a/docs/en/Themes/LeptonXLite/AspNetCore.md b/docs/en/Themes/LeptonXLite/AspNetCore.md
index eeaf55f545..f480141120 100644
--- a/docs/en/Themes/LeptonXLite/AspNetCore.md
+++ b/docs/en/Themes/LeptonXLite/AspNetCore.md
@@ -9,22 +9,22 @@ LeptonX Lite has implementation for the ABP Framework Razor Pages. It's a simpli
This theme is **already installed** when you create a new solution using the startup templates. If you are using any other template, you can install this theme by following the steps below:
-- Add **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application.
+- Add the **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application.
```bash
dotnet add package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite --prerelease
```
-- Remove **Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic** reference from the project since it's not necessary after switching to LeptonX Lite.
+- Remove the **Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic** reference from the project since it's not necessary after switching to LeptonX Lite.
- Make sure the old theme is removed and LeptonX is added in your Module class.
```diff
[DependsOn(
- // Remove BasicTheme module from DependsOn attribute
+ // Remove the BasicTheme module from DependsOn attribute
- typeof(AbpAspNetCoreMvcUiBasicThemeModule),
- // Add LeptonX Lite module to DependsOn attribute
+ // Add the LeptonX Lite module to DependsOn attribute
+ typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule),
)]
```
@@ -35,9 +35,9 @@ dotnet add package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite --prerelease
Configure(options =>
{
options.StyleBundles.Configure(
- // Remove following line
+ // Remove the following line
- BasicThemeBundles.Styles.Global,
- // Add following line instead
+ // Add the following line instead
+ LeptonXLiteThemeBundles.Styles.Global
bundle =>
{
@@ -47,10 +47,24 @@ Configure(options =>
});
```
----
-
## Customization
+### Layouts
+
+LeptonX Lite Mvc provides **layouts** for your **user interface** based [ABP Framework Theming](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming). You can use **layouts** to **organize your user interface**.
+
+The main responsibility of a theme is to **provide** the layouts. There are **three pre-defined layouts that must be implemented by all the themes:**
+
+* **Application:** The **default** layout which is used by the **main** application pages.
+
+* **Account:** Mostly used by the **account module** for **login**, **register**, **forgot password**... pages.
+
+* **Empty:** The **Minimal** layout that **has no layout components** at all.
+
+**Layout names** are **constants** defined in the `LeptonXLiteTheme` class in the **Mvc** project **root**.
+
+> The layout pages define under the `Themes/LeptonXLite/Layouts` folder and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
### Toolbars
LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class.
@@ -74,3 +88,128 @@ public class MyProjectNameMainToolbarContributor : IToolbarContributor
}
}
```
+# LeptonX Lite Mvc Components
+
+Abp **helps** you make **highly customizable UI**. You can easily **customize** your themes to fit your needs. **The Virtual File System** makes it possible to **manage files** that **do not physically** exist on the **file system** (disk). It's mainly used to embed **(js, css, image..)** files into assemblies and **use them like** physical files at runtime. An application (or another module) can **override** a **virtual file of a module** just like placing a file with the **same name** and **extension** into the **same folder** of the **virtual file**.
+
+LeptonX Lite is built on the [Abp Framework](https://abp.io/), so you can **easily** customize your Asp.Net Core Mvc user interface by following [Abp Mvc UI Customization](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-xUser-Interface).
+
+## Brand Component
+
+The **brand component** is a simple component that can be used to display your brand. It contains a **logo** and a **company name**.
+
+
+
+### How to override the Brand Component in LeptonX Lite Mvc
+
+* The **brand component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Brand/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* The **brand component (C# file)** is defined in the `Themes/LeptonXLite/Components/Brand/MainNavbarBrandViewComponent.cs` file and you can **override it** by creating a file with the **same name** and under the **same folder**.
+
+## Breadcrumb Component
+
+On websites that have a lot of pages, **breadcrumb navigation** can greatly **enhance the way users find their way** around. In terms of **usability**, breadcrumbs reduce the number of actions a website **visitor** needs to take in order to get to a **higher-level page**, and they **improve** the **findability** of **website sections** and **pages**.
+
+
+
+### How to override the Breadcrumb Component in LeptonX Lite Mvc
+
+* The **breadcrumb component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Breadcrumbs/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* The **breadcrumb component (C# file)** is defined in the `Themes/LeptonXLite/Components/Breadcrumbs/BreadcrumbsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+## Sidebar Menu Component
+
+Sidebar menus have been used as **a directory for Related Pages** to a **Service** offering, **Navigation** items to a **specific service** or topic and even just as **Links** the user may be interested in.
+
+
+
+### How to override the Sidebar Menu Component in LeptonX Lite Mvc
+
+* **Sidebar menu page (.cshtml)** is defined in the `Themes/LeptonXLite/Components/Menu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* If you want to **override the menu component (C#)** you can override the `Themes/LeptonXLite/Components/Menu/MainMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+> The **sidebar menu** renders menu items **dynamically**. The **menu item** is a **partial view** and is defined in the `Themes/LeptonXLite/Components/Menu/_MenuItem.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+## Page Alerts Component
+
+Provides contextual **feedback messages** for typical user actions with the handful of **available** and **flexible** **alert messages**. Alerts are available for any length of text, as well as an **optional dismiss button**.
+
+
+
+### How to override the Page Alerts Component in LeptonX Lite Mvc
+
+* The **page alerts component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/PageAlerts/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* The **page alerts component (C#)** is defined in the `Themes/LeptonXLite/Components/PageAlerts/PageAlertsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+## Toolbar Component
+
+Toolbar items are used to add **extra functionality to the toolbar**. The toolbar is a **horizontal bar** that **contains** a group of **toolbar items**.
+
+### How to override the Toolbar Component in LeptonX Lite Mvc
+
+* The **toolbar component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Toolbar/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* The **toolbar component (C#)** is defined in the `Themes/LeptonXLite/Components/Toolbar/ToolbarViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+## Toolbar Item Component
+
+The toolbar item is a **single item** that **contains** a **link**, an **icon**, a **label** etc..
+
+### How to override the Toolbar Item Component in LeptonX Lite Mvc
+
+* The **toolbar item component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/ToolbarItems/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* The **toolbar item component (C#)** is defined in the `Themes/LeptonXLite/Components/ToolbarItems/ToolbarItemsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+You can find the toolbar components below:
+
+## Language Switch Component
+
+Think about a **multi-lingual** website and the first thing that could **hit your mind** is **the language switch component**. A **navigation bar** is a **great place** to **embed a language switch**. By embedding the language switch in the navigation bar of your website, you would **make it simpler** for users to **find it** and **easily** switch the **language** **without trying to locate it across the website.**
+
+
+
+### How to override the Language Switch Component in LeptonX Lite Mvc
+
+* The **language switch component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/LanguageSwitch/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* The **language switch component (C#)** is defined in the `Themes/LeptonXLite/Components/LanguageSwitch/LanguageSwitchViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+## Mobile Language Switch Component
+
+The **mobile** **language switch component** is used to switch the language of the website **on mobile devices**. The mobile language switch component is a **dropdown menu** that **contains all the languages** of the website.
+
+
+
+### How to override the Mobile Language Switch Component in LeptonX Lite Mvc
+
+* The **mobile language switch component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/MobileLanguageSwitch/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* The **mobile language switch component (C#)** is defined in the `Themes/LeptonXLite/Components/MobileLanguageSwitch/MobileLanguageSwitchViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+## User Menu Component
+
+The **User Menu** is the **menu** that **drops down** when you **click your name** or **profile picture** in the **upper right corner** of your page (**in the toolbar**). It drops down options such as **Settings**, **Logout**, etc.
+
+
+
+### How to override the User Menu Component in LeptonX Lite Mvc
+
+* The **user menu component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/UserMenu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* The **user menu component (C#)** is defined in the `Themes/LeptonXLite/Components/UserMenu/UserMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+## Mobile User Menu Component
+
+The **mobile user menu component** is used to display the **user menu on mobile devices**. The mobile user menu component is a **dropdown menu** that contains all the **options** of the **user menu**.
+
+
+
+### How to override the Mobile User Menu Component in LeptonX Lite Mvc
+
+* The **mobile user menu component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/MobileUserMenu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
+
+* The **mobile user menu component (C#)** is defined in the `Themes/LeptonLite/Components/MobileUserMenu/MobileUserMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**.
diff --git a/docs/en/UI/Blazor/Layout-Hooks.md b/docs/en/UI/Blazor/Layout-Hooks.md
new file mode 100644
index 0000000000..1118e02d37
--- /dev/null
+++ b/docs/en/UI/Blazor/Layout-Hooks.md
@@ -0,0 +1,125 @@
+# Blazor UI: Layout Hooks
+
+ABP Framework theming system places the page layout into the [theme](Theming.md) NuGet packages. That means the final application doesn't include a layout, so you can't directly change the layout code to customize it.
+
+> If you create a Blazor WASM project, the `index.html` file will be included within the template. You can also customize it to your needs.
+
+You can copy the theme code into your solution. In this case, you are completely free to customize it. However, then you won't be able to get automatic updates of the theme (by upgrading the theme NuGet package).
+
+The **Layout Hook System** allows you to **add code** at some specific parts of the layout. All layouts of all themes should implement these hooks. Finally, you can add a **razor component** into a hook point.
+
+## Example: Add a Simple Announcement Alert
+
+Assume that you need to add a simple banner to the layout (that will be available for all the pages) to make an announcement about your new product. First, **create a razor component** in your project:
+
+
+
+**AnnouncementComponent.razor.cs**
+
+```csharp
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components;
+using Microsoft.JSInterop;
+
+namespace Acme.BookStore.Blazor.Components;
+
+public partial class AnnouncementComponent : ComponentBase
+{
+ private const string AnnouncementLocalStorageKey = "product-announcement-status";
+
+ [Inject]
+ public IJSRuntime JsRuntime { get; set; }
+
+ public bool ShowBanner { get; set; }
+
+ protected override async Task OnInitializedAsync()
+ {
+ ShowBanner = await ShowAnnouncementBannerAsync();
+ }
+
+ private async Task ShowAnnouncementBannerAsync()
+ {
+ var announcementLocalStorageValue = await JsRuntime.InvokeAsync("localStorage.getItem", AnnouncementLocalStorageKey);
+
+ return announcementLocalStorageValue != null &&
+ bool.TryParse(announcementLocalStorageValue, out var showAnnouncementBanner) && showAnnouncementBanner;
+ }
+
+ private async Task HideAnnouncementBannerAsync()
+ {
+ await JsRuntime.InvokeVoidAsync("localStorage.setItem", AnnouncementLocalStorageKey, true);
+ ShowBanner = false;
+ StateHasChanged();
+ }
+}
+```
+
+**AnnouncementComponent.razor**
+
+```html
+@if(ShowBanner)
+{
+
+ A brand new product is in sale. Click here to learn more.
+
+
+}
+```
+
+Then, you can add this component to any of the hook points in the `ConfigureServices` of your module class:
+
+```csharp
+Configure(options =>
+{
+ options.Add(
+ LayoutHooks.Body.Last, //The hook name
+ typeof(AnnouncementComponent) //The component to add
+ );
+});
+```
+
+Now, the `AnnouncementComponent` will be rendered in the `body` of the page as the last item.
+
+### Specifying the Layout
+
+The configuration above adds the `AnnouncementComponent` to all layouts. You may want to only add it to a specific layout:
+
+````csharp
+Configure(options =>
+{
+ options.Add(
+ LayoutHooks.Body.Last,
+ typeof(AnnouncementComponent),
+ layout: StandardLayouts.Application //Set the layout to add
+ );
+});
+````
+
+See the *Layouts* section below to learn more about the layout system.
+
+## Layout Hook Points
+
+There are some pre-defined layout hook points. The standard hook points are:
+
+* `LayoutHooks.Body.First`: Used to add a component as the first item in the HTML body tag.
+* `LayoutHooks.Body.Last`: Used to add a component as the last item in the HTML body tag.
+
+> You (or the modules you are using) can add **multiple items to the same hook point**. All of them will be added to the layout in the order they were added.
+
+## Layouts
+
+The layout system allows themes to define the standard named layouts and allows any page to select a proper layout for its purpose. There is one pre-defined layout:
+
+* "**Application**": The main (and the default) layout for an application. It typically contains a header, menu (sidebar), footer, toolbar... etc.
+
+This layout is defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but this layout is the standard layout and it's implemented by all the themes out of the box.
+
+> If you don't specify the layout, your razor component will be rendered in all of the layouts.
+
+### Layout Location
+
+You can find the `MainLayout.razor` [here](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/MainLayout.razor) for the basic theme. You can take it as a reference to build your own layouts or you can override it, if necessary.
+
+## See Also
+
+* [Customization / Overriding Components](Customization-Overriding-Components.md)
diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json
index b919955c6e..035f150705 100644
--- a/docs/en/docs-nav.json
+++ b/docs/en/docs-nav.json
@@ -851,6 +851,10 @@
"text": "Page Header",
"path": "UI/Blazor/Page-Header.md"
},
+ {
+ "text": "Page Layout",
+ "path": "UI/Blazor/Page-Layout.md"
+ },
{
"text": "Toolbars",
"path": "UI/Blazor/Toolbars.md"
@@ -935,6 +939,10 @@
{
"text": "PWA Configuration",
"path": "UI/Blazor/Pwa-Configuration.md"
+ },
+ {
+ "text": "Layout Hooks",
+ "path": "UI/Blazor/Layout-Hooks.md"
}
]
},
diff --git a/docs/en/images/bookstore-banner-component.png b/docs/en/images/bookstore-banner-component.png
new file mode 100644
index 0000000000..2363b06fae
Binary files /dev/null and b/docs/en/images/bookstore-banner-component.png differ
diff --git a/docs/en/images/leptonxlite-brand-component.png b/docs/en/images/leptonxlite-brand-component.png
new file mode 100644
index 0000000000..c06a51612f
Binary files /dev/null and b/docs/en/images/leptonxlite-brand-component.png differ
diff --git a/docs/en/images/leptonxlite-breadcrumb-component.png b/docs/en/images/leptonxlite-breadcrumb-component.png
new file mode 100644
index 0000000000..9026ca4c52
Binary files /dev/null and b/docs/en/images/leptonxlite-breadcrumb-component.png differ
diff --git a/docs/en/images/leptonxlite-language-switch-component.png b/docs/en/images/leptonxlite-language-switch-component.png
new file mode 100644
index 0000000000..027910c23b
Binary files /dev/null and b/docs/en/images/leptonxlite-language-switch-component.png differ
diff --git a/docs/en/images/leptonxlite-mobile-language-switch-component.png b/docs/en/images/leptonxlite-mobile-language-switch-component.png
new file mode 100644
index 0000000000..a058820743
Binary files /dev/null and b/docs/en/images/leptonxlite-mobile-language-switch-component.png differ
diff --git a/docs/en/images/leptonxlite-mobile-user-menu-component.png b/docs/en/images/leptonxlite-mobile-user-menu-component.png
new file mode 100644
index 0000000000..1a51c341d7
Binary files /dev/null and b/docs/en/images/leptonxlite-mobile-user-menu-component.png differ
diff --git a/docs/en/images/leptonxlite-page-alerts-component.png b/docs/en/images/leptonxlite-page-alerts-component.png
new file mode 100644
index 0000000000..7976412fb2
Binary files /dev/null and b/docs/en/images/leptonxlite-page-alerts-component.png differ
diff --git a/docs/en/images/leptonxlite-sidebar-menu-component.png b/docs/en/images/leptonxlite-sidebar-menu-component.png
new file mode 100644
index 0000000000..6afe4029e8
Binary files /dev/null and b/docs/en/images/leptonxlite-sidebar-menu-component.png differ
diff --git a/docs/en/images/leptonxlite-user-menu-component.png b/docs/en/images/leptonxlite-user-menu-component.png
new file mode 100644
index 0000000000..a0039e996c
Binary files /dev/null and b/docs/en/images/leptonxlite-user-menu-component.png differ
diff --git a/docs/zh-Hans/Background-Jobs-Hangfire.md b/docs/zh-Hans/Background-Jobs-Hangfire.md
index b1bf7b8c79..fb899758cc 100644
--- a/docs/zh-Hans/Background-Jobs-Hangfire.md
+++ b/docs/zh-Hans/Background-Jobs-Hangfire.md
@@ -66,6 +66,41 @@ public class YourModule : AbpModule
}
````
+### 指定队列
+
+你可以使用 [`QueueAttribute`](https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html) 来指定队列.
+
+````csharp
+using System.Threading.Tasks;
+using Volo.Abp.BackgroundJobs;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Emailing;
+
+namespace MyProject
+{
+ [Queue("alpha")]
+ public class EmailSendingJob
+ : AsyncBackgroundJob, ITransientDependency
+ {
+ private readonly IEmailSender _emailSender;
+
+ public EmailSendingJob(IEmailSender emailSender)
+ {
+ _emailSender = emailSender;
+ }
+
+ public override async Task ExecuteAsync(EmailSendingArgs args)
+ {
+ await _emailSender.SendAsync(
+ args.EmailAddress,
+ args.Subject,
+ args.Body
+ );
+ }
+ }
+}
+````
+
1. 如果你想要使用Hangfire的面板,你可以在 `Module` 类的 `OnApplicationInitialization` 方法添加: `UseHangfireDashboard`
````csharp
diff --git a/docs/zh-Hans/Distributed-Locking.md b/docs/zh-Hans/Distributed-Locking.md
index 2176230403..a570f598af 100644
--- a/docs/zh-Hans/Distributed-Locking.md
+++ b/docs/zh-Hans/Distributed-Locking.md
@@ -101,6 +101,25 @@ namespace AbpDemo
* `timeout` (`TimeSpan`): 等待获取锁的超时值. 默认值为`TimeSpan.Zero`, 这意味着如果锁已经被另一个应用程序拥有, 它不会等待.
* `cancellationToken`: 取消令牌可在触发后取消操作.
+### 配置
+
+#### AbpDistributedLockOptions
+
+`AbpDistributedLockOptions` 是配置分布式锁的主要选项类.
+
+**示例: 设置应用程序的分布式锁Key前缀**
+
+Configure(options =>
+{
+ options.KeyPrefix = "MyApp1";
+});
+
+> 在你的[模块类](Module-Development-Basics.md)中的 `ConfigureServices` 方法进行配置.
+
+##### 可用选项
+
+* KeyPrefix (string, 默认值: null): 指定分布式锁名称前缀.
+
### 使用DistributedLock库的API
ABP的`IAbpDistributedLock`服务非常有限, 主要用于ABP框架的内部使用. 对于你自己的应用程序, 可以使用DistributedLock库自己的API. 参见[文档](https://github.com/madelson/DistributedLock)详细信息.
diff --git a/docs/zh-Hans/Entities.md b/docs/zh-Hans/Entities.md
index 8fbf16ddd3..b9c236e346 100644
--- a/docs/zh-Hans/Entities.md
+++ b/docs/zh-Hans/Entities.md
@@ -105,6 +105,22 @@ public class UserRole : Entity
> 需要注意,复合主键实体不可以使用 `IRepository` 接口,因为它需要一个唯一的Id属性. 但你可以使用 `IRepository`.更多信息请参见[仓储](Repositories.md)的文档.
+### EntityEquals
+
+`Entity.EntityEquals(...)` 方法用于检查两个实体对象是否相等.
+
+示例:
+
+```csharp
+Book book1 = ...
+Book book2 = ...
+
+if (book1.EntityEquals(book2)) //Check equality
+{
+ ...
+}
+```
+
## 聚合根
"*聚合是域驱动设计中的一种模式.DDD的聚合是一组可以作为一个单元处理的域对象.例如,订单及订单系列的商品,这些是独立的对象,但将订单(连同订单系列的商品)视为一个聚合通常是很有用的*"( [查看详细介绍](http://martinfowler.com/bliki/DDD_Aggregate.html))
diff --git a/docs/zh-Hans/Multi-Tenancy.md b/docs/zh-Hans/Multi-Tenancy.md
index 472dde9eab..6db625e7d9 100644
--- a/docs/zh-Hans/Multi-Tenancy.md
+++ b/docs/zh-Hans/Multi-Tenancy.md
@@ -322,7 +322,6 @@ Volo.Abp.AspNetCore.MultiTenancy 添加了下面这些租户解析器,从当前W
* **CurrentUserTenantResolveContributor**: 如果当前用户已登录,从当前用户的声明中获取租户Id. **出于安全考虑,应该始终将其做为第一个Contributor**.
* **QueryStringTenantResolveContributor**: 尝试从query string参数中获取当前租户,默认参数名为"__tenant".
-* **FormTenantResolveContributor**: 尝试从form参数中获取当前租户,默认参数名为"__tenant".
* **RouteTenantResolveContributor**:尝试从当前路由中获取(URL路径),默认是变量名是"__tenant".所以,如果你的路由中定义了这个变量,就可以从路由中确定当前租户.
* **HeaderTenantResolveContributor**: 尝试从HTTP header中获取当前租户,默认的header名称是"__tenant".
* **CookieTenantResolveContributor**: 尝试从当前cookie中获取当前租户.默认的Cookie名称是"__tenant".
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor
new file mode 100644
index 0000000000..f512232b86
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor
@@ -0,0 +1,7 @@
+@if (LayoutHookViewModel.Hooks.Any())
+{
+ foreach (var hook in LayoutHookViewModel.Hooks)
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs
new file mode 100644
index 0000000000..18fbcd89c2
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components;
+using Microsoft.Extensions.Options;
+using Volo.Abp.Ui.LayoutHooks;
+
+namespace Volo.Abp.AspNetCore.Components.Web.Theming.Components.LayoutHooks;
+
+public partial class LayoutHook : ComponentBase
+{
+ [Parameter]
+ public string Name { get; set; }
+
+ [Parameter]
+ public string Layout { get; set; }
+
+ [Inject]
+ protected IOptions LayoutHookOptions { get; set; }
+
+ protected LayoutHookViewModel LayoutHookViewModel { get; private set; }
+
+ protected override Task OnInitializedAsync()
+ {
+ if (LayoutHookOptions.Value.Hooks.TryGetValue(Name, out var layoutHooks))
+ {
+ layoutHooks = layoutHooks
+ .WhereIf(string.IsNullOrWhiteSpace(Layout), x => x.Layout == Layout)
+ .ToList();
+ }
+
+ layoutHooks ??= new List();
+
+ LayoutHookViewModel = new LayoutHookViewModel(layoutHooks.ToArray(), Layout);
+
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..ea73a5673f
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs
@@ -0,0 +1,6 @@
+namespace Volo.Abp.AspNetCore.Components.Web.Theming.Layout;
+
+public static class StandardLayouts
+{
+ public const string Application = "Application";
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs
index 523a770a17..c3ba260a88 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs
@@ -9,26 +9,43 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly;
public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency
{
- protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; }
+ protected AbpApplicationConfigurationClientProxy ApplicationConfigurationClientProxy { get; }
+
+ protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; }
protected ApplicationConfigurationCache Cache { get; }
protected ICurrentTenantAccessor CurrentTenantAccessor { get; }
public WebAssemblyCachedApplicationConfigurationClient(
- AbpApplicationConfigurationClientProxy applicationConfigurationAppService,
+ AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy,
ApplicationConfigurationCache cache,
- ICurrentTenantAccessor currentTenantAccessor)
+ ICurrentTenantAccessor currentTenantAccessor,
+ AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy)
{
- ApplicationConfigurationAppService = applicationConfigurationAppService;
+ ApplicationConfigurationClientProxy = applicationConfigurationClientProxy;
Cache = cache;
CurrentTenantAccessor = currentTenantAccessor;
+ ApplicationLocalizationClientProxy = applicationLocalizationClientProxy;
}
public virtual async Task InitializeAsync()
{
- var configurationDto = await ApplicationConfigurationAppService.GetAsync();
+ var configurationDto = await ApplicationConfigurationClientProxy.GetAsync(
+ new ApplicationConfigurationRequestOptions {
+ IncludeLocalizationResources = false
+ }
+ );
+
+ var localizationDto = await ApplicationLocalizationClientProxy.GetAsync(
+ new ApplicationLocalizationRequestDto {
+ CultureName = configurationDto.Localization.CurrentCulture.Name,
+ OnlyDynamics = true
+ }
+ );
+ configurationDto.Localization.Resources = localizationDto.Resources;
+
Cache.Set(configurationDto);
CurrentTenantAccessor.Current = new BasicTenantInfo(
diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs
index ad36cf0f11..e0d8ecd806 100644
--- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs
+++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs
@@ -15,7 +15,6 @@ public class AbpAspNetCoreMultiTenancyModule : AbpModule
Configure(options =>
{
options.TenantResolvers.Add(new QueryStringTenantResolveContributor());
- options.TenantResolvers.Add(new FormTenantResolveContributor());
options.TenantResolvers.Add(new RouteTenantResolveContributor());
options.TenantResolvers.Add(new HeaderTenantResolveContributor());
options.TenantResolvers.Add(new CookieTenantResolveContributor());
diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs
index 67ef0a4c2c..d55343c94c 100644
--- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs
@@ -1,10 +1,11 @@
-using System.Linq;
+using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.MultiTenancy;
+[Obsolete("This may make some features of ASP NET Core unavailable, Will be removed in future versions.")]
public class FormTenantResolveContributor : HttpTenantResolveContributorBase
{
public const string ContributorName = "Form";
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs
index 198cfce711..2d57dc66ab 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs
@@ -15,8 +15,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies;
[ExposeServices(typeof(IAbpApplicationConfigurationAppService), typeof(AbpApplicationConfigurationClientProxy))]
public partial class AbpApplicationConfigurationClientProxy : ClientProxyBase, IAbpApplicationConfigurationAppService
{
- public virtual async Task GetAsync()
+ public virtual async Task GetAsync(ApplicationConfigurationRequestOptions options)
{
- return await RequestAsync(nameof(GetAsync));
+ return await RequestAsync(nameof(GetAsync), new ClientProxyRequestTypeValue
+ {
+ { typeof(ApplicationConfigurationRequestOptions), options }
+ });
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.Generated.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.Generated.cs
new file mode 100644
index 0000000000..74db054d18
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.Generated.cs
@@ -0,0 +1,25 @@
+// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Http.Client;
+using Volo.Abp.Http.Modeling;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Http.Client.ClientProxying;
+using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+
+// ReSharper disable once CheckNamespace
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies;
+
+[Dependency(ReplaceServices = true)]
+[ExposeServices(typeof(IAbpApplicationLocalizationAppService), typeof(AbpApplicationLocalizationClientProxy))]
+public partial class AbpApplicationLocalizationClientProxy : ClientProxyBase, IAbpApplicationLocalizationAppService
+{
+ public virtual async Task GetAsync(ApplicationLocalizationRequestDto input)
+ {
+ return await RequestAsync(nameof(GetAsync), new ClientProxyRequestTypeValue
+ {
+ { typeof(ApplicationLocalizationRequestDto), input }
+ });
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.cs
new file mode 100644
index 0000000000..11bef4cecf
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.cs
@@ -0,0 +1,7 @@
+// This file is part of AbpApplicationLocalizationClientProxy, you can customize it here
+// ReSharper disable once CheckNamespace
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies;
+
+public partial class AbpApplicationLocalizationClientProxy
+{
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json
index bf15515078..ce18e3eefd 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json
@@ -7,6 +7,8 @@
"Pages.Abp.MultiTenancy.AbpTenantController": {
"controllerName": "AbpTenant",
"controllerGroupName": "AbpTenant",
+ "isRemoteService": true,
+ "apiVersion": null,
"type": "Pages.Abp.MultiTenancy.AbpTenantController",
"interfaces": [
{
@@ -93,6 +95,8 @@
"Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationConfigurationController": {
"controllerName": "AbpApplicationConfiguration",
"controllerGroupName": "AbpApplicationConfiguration",
+ "isRemoteService": true,
+ "apiVersion": null,
"type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationConfigurationController",
"interfaces": [
{
@@ -100,14 +104,36 @@
}
],
"actions": {
- "GetAsync": {
- "uniqueName": "GetAsync",
+ "GetAsyncByOptions": {
+ "uniqueName": "GetAsyncByOptions",
"name": "GetAsync",
"httpMethod": "GET",
"url": "api/abp/application-configuration",
"supportedVersions": [],
- "parametersOnMethod": [],
- "parameters": [],
+ "parametersOnMethod": [
+ {
+ "name": "options",
+ "typeAsString": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationRequestOptions, Volo.Abp.AspNetCore.Mvc.Contracts",
+ "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationRequestOptions",
+ "typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationRequestOptions",
+ "isOptional": false,
+ "defaultValue": null
+ }
+ ],
+ "parameters": [
+ {
+ "nameOnMethod": "options",
+ "name": "IncludeLocalizationResources",
+ "jsonName": null,
+ "type": "System.Boolean",
+ "typeSimple": "boolean",
+ "isOptional": false,
+ "defaultValue": null,
+ "constraintTypes": null,
+ "bindingSourceId": "ModelBinding",
+ "descriptorName": "options"
+ }
+ ],
"returnValue": {
"type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationDto",
"typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationDto"
@@ -117,9 +143,74 @@
}
}
},
+ "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationLocalizationController": {
+ "controllerName": "AbpApplicationLocalization",
+ "controllerGroupName": "AbpApplicationLocalization",
+ "isRemoteService": true,
+ "apiVersion": null,
+ "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationLocalizationController",
+ "interfaces": [
+ {
+ "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.IAbpApplicationLocalizationAppService"
+ }
+ ],
+ "actions": {
+ "GetAsyncByInput": {
+ "uniqueName": "GetAsyncByInput",
+ "name": "GetAsync",
+ "httpMethod": "GET",
+ "url": "api/abp/application-localization",
+ "supportedVersions": [],
+ "parametersOnMethod": [
+ {
+ "name": "input",
+ "typeAsString": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationRequestDto, Volo.Abp.AspNetCore.Mvc.Contracts",
+ "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationRequestDto",
+ "typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationRequestDto",
+ "isOptional": false,
+ "defaultValue": null
+ }
+ ],
+ "parameters": [
+ {
+ "nameOnMethod": "input",
+ "name": "CultureName",
+ "jsonName": null,
+ "type": "System.String",
+ "typeSimple": "string",
+ "isOptional": false,
+ "defaultValue": null,
+ "constraintTypes": null,
+ "bindingSourceId": "ModelBinding",
+ "descriptorName": "input"
+ },
+ {
+ "nameOnMethod": "input",
+ "name": "OnlyDynamics",
+ "jsonName": null,
+ "type": "System.Boolean",
+ "typeSimple": "boolean",
+ "isOptional": false,
+ "defaultValue": null,
+ "constraintTypes": null,
+ "bindingSourceId": "ModelBinding",
+ "descriptorName": "input"
+ }
+ ],
+ "returnValue": {
+ "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationDto",
+ "typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationDto"
+ },
+ "allowAnonymous": null,
+ "implementFrom": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.IAbpApplicationLocalizationAppService"
+ }
+ }
+ },
"Volo.Abp.AspNetCore.Mvc.ApiExploring.AbpApiDefinitionController": {
"controllerName": "AbpApiDefinition",
"controllerGroupName": "AbpApiDefinition",
+ "isRemoteService": true,
+ "apiVersion": null,
"type": "Volo.Abp.AspNetCore.Mvc.ApiExploring.AbpApiDefinitionController",
"interfaces": [],
"actions": {
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteExternalLocalizationStore.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteExternalLocalizationStore.cs
new file mode 100644
index 0000000000..c5548acc62
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteExternalLocalizationStore.cs
@@ -0,0 +1,87 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Localization;
+using Volo.Abp.Localization.External;
+
+namespace Volo.Abp.AspNetCore.Mvc.Client;
+
+public class RemoteExternalLocalizationStore : IExternalLocalizationStore, ITransientDependency
+{
+ protected ICachedApplicationConfigurationClient ConfigurationClient { get; }
+ protected AbpLocalizationOptions LocalizationOptions { get; }
+
+ public RemoteExternalLocalizationStore(
+ ICachedApplicationConfigurationClient configurationClient,
+ IOptions localizationOptions)
+ {
+ ConfigurationClient = configurationClient;
+ LocalizationOptions = localizationOptions.Value;
+ }
+
+ public virtual LocalizationResourceBase GetResourceOrNull(string resourceName)
+ {
+ var configurationDto = ConfigurationClient.Get();
+ return CreateLocalizationResourceFromConfigurationOrNull(resourceName, configurationDto);
+ }
+
+ public virtual async Task GetResourceOrNullAsync(string resourceName)
+ {
+ var configurationDto = await ConfigurationClient.GetAsync();
+ return CreateLocalizationResourceFromConfigurationOrNull(resourceName, configurationDto);
+ }
+
+ public virtual async Task GetResourceNamesAsync()
+ {
+ var configurationDto = await ConfigurationClient.GetAsync();
+ return configurationDto
+ .Localization
+ .Resources
+ .Keys
+ .Where(x => !LocalizationOptions.Resources.ContainsKey(x))
+ .ToArray();
+; }
+
+ public virtual async Task GetResourcesAsync()
+ {
+ var configurationDto = await ConfigurationClient.GetAsync();
+ var resources = new List();
+
+ foreach (var resource in configurationDto.Localization.Resources)
+ {
+ if (LocalizationOptions.Resources.ContainsKey(resource.Key))
+ {
+ continue;
+ }
+
+ resources.Add(CreateNonTypedLocalizationResource(resource.Key, resource.Value));
+ }
+
+ return resources.ToArray();
+ }
+
+ protected virtual LocalizationResourceBase CreateLocalizationResourceFromConfigurationOrNull(
+ string resourceName,
+ ApplicationConfigurationDto configurationDto)
+ {
+ var resourceDto = configurationDto.Localization.Resources.GetOrDefault(resourceName);
+
+ if (resourceDto == null)
+ {
+ return null;
+ }
+
+ return CreateNonTypedLocalizationResource(resourceName, resourceDto);
+ }
+
+ protected virtual NonTypedLocalizationResource CreateNonTypedLocalizationResource(
+ string resourceName,
+ ApplicationLocalizationResourceDto resourceDto)
+ {
+ return new NonTypedLocalizationResource(resourceName)
+ .AddBaseResources(resourceDto.BaseResources);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs
index c01fc6a1af..e6e180c5f2 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs
@@ -1,15 +1,20 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Localization;
namespace Volo.Abp.AspNetCore.Mvc.Client;
public class RemoteLocalizationContributor : ILocalizationResourceContributor
{
- private LocalizationResource _resource;
+ public bool IsDynamic => true;
+
+ private LocalizationResourceBase _resource;
private ICachedApplicationConfigurationClient _applicationConfigurationClient;
private ILogger _logger;
@@ -21,50 +26,137 @@ public class RemoteLocalizationContributor : ILocalizationResourceContributor
?? NullLogger.Instance;
}
- public LocalizedString GetOrNull(string cultureName, string name)
+ public virtual LocalizedString GetOrNull(string cultureName, string name)
+ {
+ /* cultureName is not used because remote localization can only
+ * be done in the current culture. */
+
+ return GetOrNullInternal(_resource.ResourceName, name);
+ }
+
+ protected virtual LocalizedString GetOrNullInternal(string resourceName, string name)
{
- var resource = GetResourceOrNull();
+ var resource = GetResourceOrNull(resourceName);
if (resource == null)
{
return null;
}
- var value = resource.GetOrDefault(name);
- if (value == null)
+ var value = resource.Texts.GetOrDefault(name);
+ if (value != null)
{
- return null;
+ return new LocalizedString(name, value);
+ }
+
+ foreach (var baseResource in resource.BaseResources)
+ {
+ value = GetOrNullInternal(baseResource, name);
+ if (value != null)
+ {
+ return new LocalizedString(name, value);
+ }
+ }
+
+ return null;
+ }
+
+ public virtual void Fill(string cultureName, Dictionary dictionary)
+ {
+ /* cultureName is not used because remote localization can only
+ * be done in the current culture. */
+
+ FillInternal(_resource.ResourceName, dictionary);
+ }
+
+ protected virtual void FillInternal(string resourceName, Dictionary dictionary)
+ {
+ var resource = GetResourceOrNull(resourceName);
+ if (resource == null)
+ {
+ return;
+ }
+
+ foreach (var baseResource in resource.BaseResources)
+ {
+ FillInternal(baseResource, dictionary);
}
- return new LocalizedString(name, value);
+ foreach (var keyValue in resource.Texts)
+ {
+ dictionary[keyValue.Key] = new LocalizedString(keyValue.Key, keyValue.Value);
+ }
}
- public void Fill(string cultureName, Dictionary dictionary)
+ public virtual async Task FillAsync(string cultureName, Dictionary dictionary)
+ {
+ /* cultureName is not used because remote localization can only
+ * be done in the current culture. */
+
+ await FillInternalAsync(_resource.ResourceName, dictionary);
+ }
+
+ protected virtual async Task FillInternalAsync(string resourceName, Dictionary dictionary)
{
- var resource = GetResourceOrNull();
+ var resource = await GetResourceOrNullAsync(resourceName);
if (resource == null)
{
return;
}
+
+ foreach (var baseResource in resource.BaseResources)
+ {
+ await FillInternalAsync(baseResource, dictionary);
+ }
- foreach (var keyValue in resource)
+ foreach (var keyValue in resource.Texts)
{
dictionary[keyValue.Key] = new LocalizedString(keyValue.Key, keyValue.Value);
}
}
- private Dictionary GetResourceOrNull()
+ public virtual Task> GetSupportedCulturesAsync()
+ {
+ /* This contributor does not know all the supported cultures by the
+ remote localization resource, and it is not needed on the client side */
+ return Task.FromResult((IEnumerable)Array.Empty());
+ }
+
+ protected virtual ApplicationLocalizationResourceDto GetResourceOrNull(string resourceName)
{
var applicationConfigurationDto = _applicationConfigurationClient.Get();
+ return GetResourceOrNull(applicationConfigurationDto, resourceName);
+ }
+
+ protected virtual async Task GetResourceOrNullAsync(string resourceName)
+ {
+ var applicationConfigurationDto = await _applicationConfigurationClient.GetAsync();
+ return GetResourceOrNull(applicationConfigurationDto, resourceName);
+ }
- var resource = applicationConfigurationDto
+ protected virtual ApplicationLocalizationResourceDto GetResourceOrNull(
+ ApplicationConfigurationDto applicationConfigurationDto,
+ string resourceName)
+ {
+ var resource = applicationConfigurationDto.Localization.Resources.GetOrDefault(resourceName);
+ if (resource != null)
+ {
+ return resource;
+ }
+
+ var legacyResource = applicationConfigurationDto
.Localization.Values
- .GetOrDefault(_resource.ResourceName);
+ .GetOrDefault(resourceName);
- if (resource == null)
+ if (legacyResource != null)
{
- _logger.LogWarning($"Could not find the localization resource {_resource.ResourceName} on the remote server!");
+ return new ApplicationLocalizationResourceDto
+ {
+ Texts = legacyResource,
+ BaseResources = Array.Empty()
+ };
}
- return resource;
+ _logger.LogWarning($"Could not find the localization resource {resourceName} on the remote server!");
+ return null;
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs
index 7c21b9fe09..ae5421d947 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs
@@ -1,8 +1,5 @@
-using System.Threading.Tasks;
-using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EventBus;
using Volo.Abp.Modularity;
-using Volo.Abp.Threading;
namespace Volo.Abp.AspNetCore.Mvc.Client;
@@ -12,13 +9,5 @@ namespace Volo.Abp.AspNetCore.Mvc.Client;
)]
public class AbpAspNetCoreMvcClientModule : AbpModule
{
- public override void OnApplicationInitialization(ApplicationInitializationContext context)
- {
- AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context));
- }
-
- public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
- {
- await context.ServiceProvider.GetRequiredService().InitializeAsync();
- }
+
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs
index 59690b2d03..405d43906d 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs
@@ -15,6 +15,7 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
{
protected IHttpContextAccessor HttpContextAccessor { get; }
protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; }
+ protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; }
protected ICurrentUser CurrentUser { get; }
protected IDistributedCache Cache { get; }
@@ -22,19 +23,16 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
IDistributedCache cache,
AbpApplicationConfigurationClientProxy applicationConfigurationAppService,
ICurrentUser currentUser,
- IHttpContextAccessor httpContextAccessor)
+ IHttpContextAccessor httpContextAccessor,
+ AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy)
{
ApplicationConfigurationAppService = applicationConfigurationAppService;
CurrentUser = currentUser;
HttpContextAccessor = httpContextAccessor;
+ ApplicationLocalizationClientProxy = applicationLocalizationClientProxy;
Cache = cache;
}
- public async Task InitializeAsync()
- {
- await GetAsync();
- }
-
public async Task GetAsync()
{
var cacheKey = CreateCacheKey();
@@ -45,10 +43,9 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
return configuration;
}
-
configuration = await Cache.GetOrAddAsync(
cacheKey,
- async () => await ApplicationConfigurationAppService.GetAsync(),
+ async () => await GetRemoteConfigurationAsync(),
() => new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(300) //TODO: Should be configurable.
@@ -63,6 +60,27 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
return configuration;
}
+ private async Task GetRemoteConfigurationAsync()
+ {
+ var config = await ApplicationConfigurationAppService.GetAsync(
+ new ApplicationConfigurationRequestOptions
+ {
+ IncludeLocalizationResources = false
+ }
+ );
+
+ var localizationDto = await ApplicationLocalizationClientProxy.GetAsync(
+ new ApplicationLocalizationRequestDto {
+ CultureName = config.Localization.CurrentCulture.Name,
+ OnlyDynamics = true
+ }
+ );
+
+ config.Localization.Resources = localizationDto.Resources;
+
+ return config;
+ }
+
public ApplicationConfigurationDto Get()
{
var cacheKey = CreateCacheKey();
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs
new file mode 100644
index 0000000000..a01534775a
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs
@@ -0,0 +1,9 @@
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+
+public class ApplicationConfigurationRequestOptions
+{
+ ///
+ /// Set to true to fill the Values property in .
+ ///
+ public bool IncludeLocalizationResources { get; set; } = true;
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs
index f92c9d7bb9..b1c0c16e76 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs
@@ -7,8 +7,23 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
[Serializable]
public class ApplicationLocalizationConfigurationDto
{
+ ///
+ /// This is not filled if is false.
+ ///
public Dictionary> Values { get; set; }
+ ///
+ /// This property will never be filled by the application configuration endpoint
+ /// (by AbpApplicationConfigurationAppService). However, it is here to be filled
+ /// using the application localization endpoint (AbpApplicationLocalizationAppService).
+ /// This is an ugly design, but it is the best solution for backward-compability and
+ /// simple implementation.
+ ///
+ /// It's client's responsibility to fill this property
+ /// using the application localization endpoint.
+ ///
+ public Dictionary Resources { get; set; } = new();
+
public List Languages { get; set; }
public CurrentCultureDto CurrentCulture { get; set; }
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs
new file mode 100644
index 0000000000..d301490bed
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+
+[Serializable]
+public class ApplicationLocalizationDto
+{
+ public Dictionary Resources { get; set; }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationRequestDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationRequestDto.cs
new file mode 100644
index 0000000000..535d572b3f
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationRequestDto.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+
+public class ApplicationLocalizationRequestDto
+{
+ [Required]
+ public string CultureName { get; set; }
+
+ public bool OnlyDynamics { get; set; }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs
new file mode 100644
index 0000000000..a7200423d2
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+
+[Serializable]
+public class ApplicationLocalizationResourceDto
+{
+ public Dictionary Texts { get; set; }
+
+ public string[] BaseResources { get; set; }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs
index 8f1896e31f..83f1e0c803 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs
@@ -1,5 +1,8 @@
-namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+using System;
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+
+[Serializable]
public class CurrentCultureDto
{
public string DisplayName { get; set; }
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs
index 9c8fd3d414..ae4f22cc06 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs
@@ -5,5 +5,5 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
public interface IAbpApplicationConfigurationAppService : IApplicationService
{
- Task GetAsync();
-}
+ Task GetAsync(ApplicationConfigurationRequestOptions options);
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationLocalizationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationLocalizationAppService.cs
new file mode 100644
index 0000000000..af72c8f7ee
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationLocalizationAppService.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+using Volo.Abp.Application.Services;
+
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+
+public interface IAbpApplicationLocalizationAppService : IApplicationService
+{
+ Task GetAsync(ApplicationLocalizationRequestDto input);
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs
index 097fad5f40..56ae47f0c8 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button;
@@ -15,7 +16,7 @@ public class AbpPageToolbarButtonViewComponent : AbpViewComponent
StringLocalizerFactory = stringLocalizerFactory;
}
- public IViewComponentResult Invoke(
+ public async Task InvokeAsync(
ILocalizableString text,
string name,
string icon,
@@ -31,11 +32,11 @@ public class AbpPageToolbarButtonViewComponent : AbpViewComponent
return View(
"~/Pages/Shared/Components/AbpPageToolbar/Button/Default.cshtml",
new AbpPageToolbarButtonViewModel(
- text.Localize(StringLocalizerFactory),
+ await text.LocalizeAsync(StringLocalizerFactory),
name,
icon,
id,
- busyText?.Localize(StringLocalizerFactory),
+ busyText == null ? null : await busyText.LocalizeAsync(StringLocalizerFactory),
iconType,
type,
size,
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml
index 61e9afbf6e..34536ec373 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml
@@ -1,5 +1,4 @@
-@using Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook
-@model LayoutHookViewModel
+@model Volo.Abp.Ui.LayoutHooks.LayoutHookViewModel
@if (Model.Hooks.Any())
{
foreach (var hook in Model.Hooks)
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs
index 45eaa2b6a3..5bd18e4c31 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
+using Volo.Abp.Ui.LayoutHooks;
namespace Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook;
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
index a22cefc9e1..0deae4b5b3 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
@@ -17,6 +17,7 @@ using Volo.Abp.Data;
using Volo.Abp.Features;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Localization;
+using Volo.Abp.Localization.External;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
@@ -82,7 +83,7 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp
_multiTenancyOptions = multiTenancyOptions.Value;
}
- public virtual async Task GetAsync()
+ public virtual async Task GetAsync(ApplicationConfigurationRequestOptions options)
{
//TODO: Optimize & cache..?
@@ -93,7 +94,7 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp
Auth = await GetAuthConfigAsync(),
Features = await GetFeaturesConfigAsync(),
GlobalFeatures = await GetGlobalFeaturesConfigAsync(),
- Localization = await GetLocalizationConfigAsync(),
+ Localization = await GetLocalizationConfigAsync(options),
CurrentUser = GetCurrentUser(),
Setting = await GetSettingConfigAsync(),
MultiTenancy = GetMultiTenancy(),
@@ -205,26 +206,42 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp
return authConfig;
}
- protected virtual async Task GetLocalizationConfigAsync()
+ protected virtual async Task GetLocalizationConfigAsync(
+ ApplicationConfigurationRequestOptions options)
{
var localizationConfig = new ApplicationLocalizationConfigurationDto();
localizationConfig.Languages.AddRange(await _languageProvider.GetLanguagesAsync());
- foreach (var resource in _localizationOptions.Resources.Values)
+ if (options.IncludeLocalizationResources)
{
- var dictionary = new Dictionary();
-
- var localizer = (IStringLocalizer) _serviceProvider.GetRequiredService(
- typeof(IStringLocalizer<>).MakeGenericType(resource.ResourceType)
- );
-
- foreach (var localizedString in localizer.GetAllStrings())
+ var resourceNames = _localizationOptions
+ .Resources
+ .Values
+ .Select(x => x.ResourceName)
+ .Union(
+ await LazyServiceProvider
+ .LazyGetRequiredService()
+ .GetResourceNamesAsync()
+ );
+
+ foreach (var resourceName in resourceNames)
{
- dictionary[localizedString.Name] = localizedString.Value;
- }
+ var dictionary = new Dictionary();
+
+ var localizer = await StringLocalizerFactory
+ .CreateByResourceNameOrNullAsync(resourceName);
+
+ if (localizer != null)
+ {
+ foreach (var localizedString in await localizer.GetAllStringsAsync())
+ {
+ dictionary[localizedString.Name] = localizedString.Value;
+ }
+ }
- localizationConfig.Values[resource.ResourceName] = dictionary;
+ localizationConfig.Values[resourceName] = dictionary;
+ }
}
localizationConfig.CurrentCulture = GetCurrentCultureInfo();
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs
index 6c410a8a7b..427aaf65a6 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs
@@ -21,9 +21,10 @@ public class AbpApplicationConfigurationController : AbpControllerBase, IAbpAppl
}
[HttpGet]
- public virtual async Task GetAsync()
+ public virtual async Task GetAsync(
+ ApplicationConfigurationRequestOptions options)
{
_antiForgeryManager.SetCookie();
- return await _applicationConfigurationAppService.GetAsync();
+ return await _applicationConfigurationAppService.GetAsync(options);
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs
index 92a4bdef0d..7fafee261c 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs
@@ -17,14 +17,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
[ApiExplorerSettings(IgnoreApi = true)]
public class AbpApplicationConfigurationScriptController : AbpController
{
- private readonly IAbpApplicationConfigurationAppService _configurationAppService;
+ private readonly AbpApplicationConfigurationAppService _configurationAppService;
private readonly IJsonSerializer _jsonSerializer;
private readonly AbpAspNetCoreMvcOptions _options;
private readonly IJavascriptMinifier _javascriptMinifier;
private readonly IAbpAntiForgeryManager _antiForgeryManager;
public AbpApplicationConfigurationScriptController(
- IAbpApplicationConfigurationAppService configurationAppService,
+ AbpApplicationConfigurationAppService configurationAppService,
IJsonSerializer jsonSerializer,
IOptions options,
IJavascriptMinifier javascriptMinifier,
@@ -41,7 +41,13 @@ public class AbpApplicationConfigurationScriptController : AbpController
[Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)]
public async Task Get()
{
- var script = CreateAbpExtendScript(await _configurationAppService.GetAsync());
+ var script = CreateAbpExtendScript(
+ await _configurationAppService.GetAsync(
+ new ApplicationConfigurationRequestOptions {
+ IncludeLocalizationResources = false
+ }
+ )
+ );
_antiForgeryManager.SetCookie();
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs
new file mode 100644
index 0000000000..48d7f949c3
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Options;
+using Volo.Abp.Application.Services;
+using Volo.Abp.Localization;
+using Volo.Abp.Localization.External;
+
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+
+public class AbpApplicationLocalizationAppService :
+ ApplicationService,
+ IAbpApplicationLocalizationAppService
+{
+ protected IExternalLocalizationStore ExternalLocalizationStore { get; }
+ protected AbpLocalizationOptions LocalizationOptions { get; }
+
+ public AbpApplicationLocalizationAppService(
+ IExternalLocalizationStore externalLocalizationStore,
+ IOptions localizationOptions)
+ {
+ ExternalLocalizationStore = externalLocalizationStore;
+ LocalizationOptions = localizationOptions.Value;
+ }
+
+ public async Task GetAsync(ApplicationLocalizationRequestDto input)
+ {
+ using (CultureHelper.Use(input.CultureName))
+ {
+ var resources = LocalizationOptions
+ .Resources
+ .Values
+ .Union(
+ await ExternalLocalizationStore.GetResourcesAsync()
+ ).ToArray();
+
+ var localizationConfig = new ApplicationLocalizationDto {
+ Resources = new Dictionary(resources.Length)
+ };
+
+ foreach (var resource in resources)
+ {
+ var dictionary = new Dictionary();
+ var localizer = await StringLocalizerFactory.CreateByResourceNameOrNullAsync(resource.ResourceName);
+ if (localizer != null)
+ {
+ Dictionary staticLocalizedStrings = null;
+
+ if (input.OnlyDynamics)
+ {
+ staticLocalizedStrings = (await localizer.GetAllStringsAsync(
+ includeParentCultures: true,
+ includeBaseLocalizers: false,
+ includeDynamicContributors: false
+ )).ToDictionary(x => x.Name);
+ }
+
+ var localizedStringsWithDynamics = await localizer.GetAllStringsAsync(
+ includeParentCultures: true,
+ includeBaseLocalizers: false,
+ includeDynamicContributors: true
+ );
+
+ foreach (var localizedString in localizedStringsWithDynamics)
+ {
+ if (input.OnlyDynamics)
+ {
+ var staticLocalizedString = staticLocalizedStrings.GetOrDefault(localizedString.Name);
+ if (staticLocalizedString != null &&
+ localizedString.Value == staticLocalizedString.Value)
+ {
+ continue;
+ }
+ }
+
+ dictionary[localizedString.Name] = localizedString.Value;
+ }
+ }
+
+ localizationConfig.Resources[resource.ResourceName] =
+ new ApplicationLocalizationResourceDto {
+ Texts = dictionary,
+ BaseResources = resource.BaseResourceNames.ToArray()
+ };
+ }
+
+ return localizationConfig;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationController.cs
new file mode 100644
index 0000000000..a056746022
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationController.cs
@@ -0,0 +1,23 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+
+[Area("abp")]
+[RemoteService(Name = "abp")]
+[Route("api/abp/application-localization")]
+public class AbpApplicationLocalizationController: AbpControllerBase, IAbpApplicationLocalizationAppService
+{
+ private readonly IAbpApplicationLocalizationAppService _localizationAppService;
+
+ public AbpApplicationLocalizationController(IAbpApplicationLocalizationAppService localizationAppService)
+ {
+ _localizationAppService = localizationAppService;
+ }
+
+ [HttpGet]
+ public virtual async Task GetAsync(ApplicationLocalizationRequestDto input)
+ {
+ return await _localizationAppService.GetAsync(input);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
index 8acfcefd3d..0f9b05830d 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
@@ -204,9 +204,7 @@ public class CachedObjectExtensionsDtoService : ICachedObjectExtensionsDtoServic
{
return new LocalizableStringDto(
localizableStringInstance.Name,
- localizableStringInstance.ResourceType != null
- ? LocalizationResourceNameAttribute.GetName(localizableStringInstance.ResourceType)
- : null
+ localizableStringInstance.ResourceName
);
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpApplicationLocalizationScriptController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpApplicationLocalizationScriptController.cs
new file mode 100644
index 0000000000..d8da85f871
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpApplicationLocalizationScriptController.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Options;
+using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+using Volo.Abp.Auditing;
+using Volo.Abp.Http;
+using Volo.Abp.Json;
+using Volo.Abp.Localization;
+using Volo.Abp.Minify.Scripts;
+
+namespace Volo.Abp.AspNetCore.Mvc.Localization;
+
+[Area("Abp")]
+[Route("Abp/ApplicationLocalizationScript")]
+[DisableAuditing]
+[RemoteService(false)]
+[ApiExplorerSettings(IgnoreApi = true)]
+public class AbpApplicationLocalizationScriptController : AbpController
+{
+ protected AbpApplicationLocalizationAppService LocalizationAppService { get; }
+ protected AbpAspNetCoreMvcOptions Options { get; }
+ protected IJsonSerializer JsonSerializer { get; }
+ protected IJavascriptMinifier JavascriptMinifier { get; }
+
+ public AbpApplicationLocalizationScriptController(
+ AbpApplicationLocalizationAppService localizationAppService,
+ IOptions options,
+ IJsonSerializer jsonSerializer,
+ IJavascriptMinifier javascriptMinifier)
+ {
+ LocalizationAppService = localizationAppService;
+ JsonSerializer = jsonSerializer;
+ JavascriptMinifier = javascriptMinifier;
+ Options = options.Value;
+ }
+
+ [HttpGet]
+ [Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)]
+ public async Task GetAsync(ApplicationLocalizationRequestDto input)
+ {
+ var script = CreateScript(
+ await LocalizationAppService.GetAsync(input)
+ );
+
+ return Content(
+ Options.MinifyGeneratedScript == true
+ ? JavascriptMinifier.Minify(script)
+ : script,
+ MimeTypes.Application.Javascript
+ );
+ }
+
+ private string CreateScript(ApplicationLocalizationDto localizationDto)
+ {
+ var script = new StringBuilder();
+
+ script.AppendLine("(function(){");
+ script.AppendLine();
+ script.AppendLine(
+ $"$.extend(true, abp.localization, {JsonSerializer.Serialize(localizationDto, indented: true)})");
+ script.AppendLine();
+ script.Append("})();");
+
+ return script.ToString();
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs
index dc44f12124..9f5d6aa530 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs
@@ -3,12 +3,14 @@ using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.RequestLocalization;
+using Volo.Abp.Auditing;
using Volo.Abp.Localization;
namespace Volo.Abp.AspNetCore.Mvc.Localization;
[Area("Abp")]
[Route("Abp/Languages/[action]")]
+[DisableAuditing]
[RemoteService(false)]
[ApiExplorerSettings(IgnoreApi = true)]
public class AbpLanguagesController : AbpController
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs
index 20ac2e4cb8..3501882e3a 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs
@@ -5,7 +5,7 @@ using Volo.Abp.Http.ProxyScripting.Generators.JQuery;
namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting;
-public class ServiceProxyGenerationModel //: TODO: IShouldNormalize
+public class ServiceProxyGenerationModel
{
public string Type { get; set; }
diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs
index 1324df0865..6fd02a0470 100644
--- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs
+++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs
@@ -27,7 +27,9 @@ public class AbpRequestLocalizationMiddleware : IMiddleware, ITransientDependenc
{
var middleware = new RequestLocalizationMiddleware(
next,
- new OptionsWrapper(await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()),
+ new OptionsWrapper(
+ await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()
+ ),
_loggerFactory
);
diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/EntityPropertyChangeInfo.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/EntityPropertyChangeInfo.cs
index f6e1b76cd7..5e92f6622e 100644
--- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/EntityPropertyChangeInfo.cs
+++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/EntityPropertyChangeInfo.cs
@@ -9,19 +9,19 @@ public class EntityPropertyChangeInfo
/// Maximum length of property.
/// Value: 96.
///
- public const int MaxPropertyNameLength = 96;
+ public static int MaxPropertyNameLength = 96;
///
/// Maximum length of and properties.
/// Value: 512.
///
- public const int MaxValueLength = 512;
+ public static int MaxValueLength = 512;
///
/// Maximum length of property.
/// Value: 512.
///
- public const int MaxPropertyTypeFullNameLength = 192;
+ public static int MaxPropertyTypeFullNameLength = 192;
public virtual string NewValue { get; set; }
diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs
index 2ebad0797f..2583ada9fc 100644
--- a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs
+++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs
@@ -1,6 +1,9 @@
using System;
+using System.Reflection;
using System.Threading.Tasks;
using Hangfire;
+using Hangfire.States;
+using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BackgroundJobs.Hangfire;
@@ -8,16 +11,35 @@ namespace Volo.Abp.BackgroundJobs.Hangfire;
[Dependency(ReplaceServices = true)]
public class HangfireBackgroundJobManager : IBackgroundJobManager, ITransientDependency
{
+ protected AbpBackgroundJobOptions Options { get; }
+
+ public HangfireBackgroundJobManager(IOptions options)
+ {
+ Options = options.Value;
+ }
+
public virtual Task EnqueueAsync(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null)
{
return Task.FromResult(delay.HasValue
? BackgroundJob.Schedule>(
- adapter => adapter.ExecuteAsync(args),
+ adapter => adapter.ExecuteAsync(GetQueueName(typeof(TArgs)),args),
delay.Value
)
: BackgroundJob.Enqueue>(
- adapter => adapter.ExecuteAsync(args)
+ adapter => adapter.ExecuteAsync(GetQueueName(typeof(TArgs)) ,args)
));
}
+
+ protected virtual string GetQueueName(Type argsType)
+ {
+ var queueName = EnqueuedState.DefaultQueue;
+ var queueAttribute = Options.GetJob(argsType).JobType.GetCustomAttribute();
+ if (queueAttribute != null)
+ {
+ queueName = queueAttribute.Queue;
+ }
+
+ return queueName;
+ }
}
diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
index a2ad7dc77d..7ba0cd7db4 100644
--- a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
+++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@@ -20,7 +21,8 @@ public class HangfireJobExecutionAdapter
Options = options.Value;
}
- public async Task ExecuteAsync(TArgs args)
+ [Queue("{0}")]
+ public async Task ExecuteAsync(string queue, TArgs args)
{
if (!Options.IsJobExecutionEnabled)
{
diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs
index a1a079fe27..359ea1a422 100644
--- a/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs
+++ b/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs
@@ -5,6 +5,7 @@ using Blazorise.Snackbar;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Components.Notifications;
+using Volo.Abp.Localization;
namespace Volo.Abp.BlazoriseUI.Components;
@@ -54,13 +55,15 @@ public partial class UiNotificationAlert : ComponentBase, IDisposable
Title = e.Title;
Options = e.Options;
- var okButtonText = Options?.OkButtonText?.Localize(StringLocalizerFactory);
+ var okButtonText = Options?.OkButtonText == null
+ ? null
+ : await Options.OkButtonText.LocalizeAsync(StringLocalizerFactory);
await SnackbarStack.PushAsync(Message, Title, GetSnackbarColor(e.NotificationType), (options) =>
- {
- options.CloseButtonIcon = IconName.Times;
- options.ActionButtonText = okButtonText;
- });
+ {
+ options.CloseButtonIcon = IconName.Times;
+ options.ActionButtonText = okButtonText;
+ });
}
public virtual void Dispose()
diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
index c51ec76fc1..a72a369655 100644
--- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
+++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
@@ -21,26 +21,115 @@ namespace Volo.Abp.Caching;
///
/// The type of cache item being cached.
public class DistributedCache :
- DistributedCache,
IDistributedCache
where TCacheItem : class
{
- public DistributedCache(
- IOptions distributedCacheOption,
- IDistributedCache cache,
- ICancellationTokenProvider cancellationTokenProvider,
- IDistributedCacheSerializer serializer,
- IDistributedCacheKeyNormalizer keyNormalizer,
- IServiceScopeFactory serviceScopeFactory,
- IUnitOfWorkManager unitOfWorkManager) : base(
- distributedCacheOption: distributedCacheOption,
- cache: cache,
- cancellationTokenProvider: cancellationTokenProvider,
- serializer: serializer,
- keyNormalizer: keyNormalizer,
- serviceScopeFactory: serviceScopeFactory,
- unitOfWorkManager: unitOfWorkManager)
+ public IDistributedCache InternalCache { get; }
+
+ public DistributedCache(IDistributedCache internalCache)
+ {
+ InternalCache = internalCache;
+ }
+
+ public TCacheItem Get(string key, bool? hideErrors = null, bool considerUow = false)
+ {
+ return InternalCache.Get(key, hideErrors, considerUow);
+ }
+
+ public KeyValuePair[] GetMany(IEnumerable keys, bool? hideErrors = null, bool considerUow = false)
+ {
+ return InternalCache.GetMany(keys, hideErrors, considerUow);
+ }
+
+ public Task[]> GetManyAsync(IEnumerable keys, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ return InternalCache.GetManyAsync(keys, hideErrors, considerUow, token);
+ }
+
+ public Task GetAsync(string key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ return InternalCache.GetAsync(key, hideErrors, considerUow, token);
+ }
+
+ public TCacheItem GetOrAdd(string key, Func factory, Func optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
+ {
+ return InternalCache.GetOrAdd(key, factory, optionsFactory, hideErrors, considerUow);
+ }
+
+ public Task GetOrAddAsync(string key, Func> factory, Func optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ return InternalCache.GetOrAddAsync(key, factory, optionsFactory, hideErrors, considerUow, token);
+ }
+
+ public KeyValuePair[] GetOrAddMany(IEnumerable keys, Func, List>> factory, Func optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
+ {
+ return InternalCache.GetOrAddMany(keys, factory, optionsFactory, hideErrors, considerUow);
+ }
+
+ public Task[]> GetOrAddManyAsync(IEnumerable keys, Func, Task>>> factory, Func optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ return InternalCache.GetOrAddManyAsync(keys, factory, optionsFactory, hideErrors, considerUow, token);
+ }
+
+ public void Set(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false)
+ {
+ InternalCache.Set(key, value, options, hideErrors, considerUow);
+ }
+
+ public Task SetAsync(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ return InternalCache.SetAsync(key, value, options, hideErrors, considerUow, token);
+ }
+
+ public void SetMany(IEnumerable> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false)
+ {
+ InternalCache.SetMany(items, options, hideErrors, considerUow);
+ }
+
+ public Task SetManyAsync(IEnumerable> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ return InternalCache.SetManyAsync(items, options, hideErrors, considerUow, token);
+ }
+
+ public void Refresh(string key, bool? hideErrors = null)
+ {
+ InternalCache.Refresh(key, hideErrors);
+ }
+
+ public Task RefreshAsync(string key, bool? hideErrors = null, CancellationToken token = default)
+ {
+ return InternalCache.RefreshAsync(key, hideErrors, token);
+ }
+
+ public void RefreshMany(IEnumerable keys, bool? hideErrors = null)
+ {
+ InternalCache.RefreshMany(keys, hideErrors);
+ }
+
+ public Task RefreshManyAsync(IEnumerable keys, bool? hideErrors = null, CancellationToken token = default)
+ {
+ return InternalCache.RefreshManyAsync(keys, hideErrors, token);
+ }
+
+ public void Remove(string key, bool? hideErrors = null, bool considerUow = false)
+ {
+ InternalCache.Remove(key, hideErrors, considerUow);
+ }
+
+ public Task RemoveAsync(string key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ return InternalCache.RemoveAsync(key, hideErrors, considerUow, token);
+ }
+
+ public void RemoveMany(IEnumerable keys, bool? hideErrors = null, bool considerUow = false)
+ {
+ InternalCache.RemoveMany(keys, hideErrors, considerUow);
+ }
+
+ public Task RemoveManyAsync(IEnumerable keys, bool? hideErrors = null, bool considerUow = false,
+ CancellationToken token = default)
{
+ return InternalCache.RemoveManyAsync(keys, hideErrors, considerUow, token);
}
}
diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs
index 21a2842e15..ab3ca560a8 100644
--- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs
+++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs
@@ -14,6 +14,7 @@ namespace Volo.Abp.Caching;
public interface IDistributedCache : IDistributedCache
where TCacheItem : class
{
+ IDistributedCache InternalCache { get; }
}
///
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
index 646d8d4c08..813900ba18 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
@@ -333,14 +333,14 @@ public abstract class ProjectCreationCommandBase
var tieredYesNo = tiered ? "yes" : "no";
var url = $"https://{urlPrefix}.abp.io/project-created-success?ui={uiFramework:g}&db={databaseProvider:g}&tiered={tieredYesNo}";
- CmdHelper.OpenWebPage(url);
+ CmdHelper.Open(url);
}
protected void OpenMicroserviceDocumentPage()
{
var url = "https://docs.abp.io/en/commercial/latest/startup-templates/microservice/index";
- CmdHelper.OpenWebPage(url);
+ CmdHelper.Open(url);
}
protected bool GetCreateSolutionFolderPreference(CommandLineArgs commandLineArgs)
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
index 10c65d95e5..6f013272c3 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
@@ -221,7 +221,9 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
}
catch (Exception ex)
{
- throw new Exception($"Error occured while getting the versions from {url} : {ex.Message}");
+ Logger.LogWarning($"Error occured while getting the versions from {url} : {ex.Message}");
+ // The remote service is currently unavailable, try to work offline.
+ return true;
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
index 33ecad0719..f3b0fed914 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
@@ -61,7 +61,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
ChangeNamespace(
context,
"/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs",
- $"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Themes.{defaultThemeName}",
+ $"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Components",
"Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic"
);
@@ -69,7 +69,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
#region Blazor.Server Projects
- ChangeThemeToBasicForBlazorServerProjects(context, defaultThemeName);
+ ChangeThemeToBasicForBlazorProjects(context, defaultThemeName);
#endregion
@@ -105,6 +105,24 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
"node_modules/bootstrap-icons/font/bootstrap-icons.css"
);
+ if(defaultThemeName == "LeptonX")
+ {
+ ReplaceMethodNames(
+ context,
+ "/angular/src/app/app.module.ts",
+ "HttpErrorComponent, ",
+ ""
+ );
+
+ ChangeModuleImportBetweenStatements(
+ context,
+ "/angular/src/app/app.module.ts",
+ "ThemeSharedModule.forRoot",
+ "AccountAdminConfigModule.forRoot",
+ "ThemeSharedModule.forRoot(),"
+ );
+ }
+
#endregion
}
@@ -510,6 +528,41 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
file.SetLines(lines.Where(x => x != null));
}
+ private void ChangeModuleImportBetweenStatements(
+ ProjectBuildContext context,
+ string filePath,
+ string firstStatement,
+ string lastStatement,
+ string newStatement)
+ {
+ var file = context.Files.FirstOrDefault(x => x.Name.Contains(filePath));
+ if (file == null)
+ {
+ return;
+ }
+
+ file.NormalizeLineEndings();
+
+ var lines = file.GetLines();
+ var firstLineIndex = lines.FindIndex(line => line.Contains(firstStatement));
+ var lastLineIndex = lines.FindIndex(line => line.Contains(lastStatement));
+
+ if(firstLineIndex == -1 || lastLineIndex == -1)
+ {
+ return;
+ }
+
+ lines[firstLineIndex] = newStatement;
+
+ for (var i = firstLineIndex + 1; i <= lastLineIndex; i++)
+ {
+ lines[i] = null;
+ }
+
+
+ file.SetLines(lines.Where(x => x != null));
+ }
+
protected void ReplaceMethodNames(
ProjectBuildContext context,
string filePath,
@@ -668,7 +721,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
}
}
- private void ChangeThemeToBasicForBlazorServerProjects(ProjectBuildContext context, string defaultThemeName)
+ private void ChangeThemeToBasicForBlazorProjects(ProjectBuildContext context, string defaultThemeName)
{
var projects = new Dictionary
{
@@ -733,7 +786,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{project.Key}/Pages/_Host.cshtml",
- $"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Themes.{defaultThemeName}",
+ $"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Components",
"Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic",
$"Blazor{defaultThemeName}ThemeBundles.Styles.Global",
"BlazorBasicThemeBundles.Styles.Global"
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveDependencyFromPackageJsonFileStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveDependencyFromPackageJsonFileStep.cs
index 3eabb6621e..bff28d703b 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveDependencyFromPackageJsonFileStep.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveDependencyFromPackageJsonFileStep.cs
@@ -17,8 +17,7 @@ public class RemoveDependencyFromPackageJsonFileStep : ProjectBuildPipelineStep
public override void Execute(ProjectBuildContext context)
{
- var packageJsonFile = context.Files.FirstOrDefault(f => f.Name.Contains(_packageJsonFilePath));
-
+ var packageJsonFile = context.Files.FirstOrDefault(f => f.Name.Contains(_packageJsonFilePath) && !f.Name.Contains("node_modules"));
if (packageJsonFile == null)
{
return;
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
index 2d7da80211..22d33be136 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
@@ -194,19 +194,23 @@ public abstract class AppTemplateBase : TemplateInfo
return;
}
+ if (context.BuildArgs.Theme != Theme.NotSpecified)
+ {
+ context.Symbols.Add(context.BuildArgs.Theme.Value.ToString().ToUpper());
+ }
+
if (context.BuildArgs.Theme == Theme.LeptonX)
{
- context.Symbols.Add("LEPTONX");
steps.Add(new ChangeThemeStyleStep());
}
-
+
if (IsDefaultThemeForTemplate(context.BuildArgs.Theme.Value))
{
return;
}
steps.Add(new ChangeThemeStep());
- RemoveLeptonXThemePackagesFromPackageJsonFiles(steps, isProTemplate: IsPro());
+ RemoveLeptonXThemePackagesFromPackageJsonFiles(steps, isProTemplate: IsPro(), uiFramework: context.BuildArgs.UiFramework);
}
private static bool IsDefaultThemeForTemplate(Theme theme)
@@ -220,50 +224,55 @@ public abstract class AppTemplateBase : TemplateInfo
return defaultThemesForTemplates.Any(defaultTheme => defaultTheme == theme);
}
- private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List steps, bool isProTemplate)
+ private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List steps, bool isProTemplate, UiFramework uiFramework)
{
+ var mvcUiPackageName = isProTemplate ? "@volo/abp.aspnetcore.mvc.ui.theme.leptonx" : "@abp/aspnetcore.mvc.ui.theme.leptonxlite";
var packageJsonFilePaths = new List
{
"/MyCompanyName.MyProjectName.Web/package.json",
"/MyCompanyName.MyProjectName.Web.Host/package.json",
"/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json",
+ "/MyCompanyName.MyProjectName.HttpApi.Host/package.json",
"/MyCompanyName.MyProjectName.AuthServer/package.json",
"/MyCompanyName.MyProjectName/package.json",
"/MyCompanyName.MyProjectName.Host/package.json",
"/MyCompanyName.MyProjectName.Host.Mongo/package.json"
};
-
- var blazorServerPackageJsonFilePaths = new List
- {
- "/MyCompanyName.MyProjectName.Blazor.Server/package.json",
- "/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json",
- "/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json"
- };
-
- var angularPackageJsonFilePaths = new List
- {
- "/angular/package.json"
- };
-
- var mvcUiPackageName = isProTemplate ? "@volo/abp.aspnetcore.mvc.ui.theme.leptonx" : "@abp/aspnetcore.mvc.ui.theme.leptonxlite";
- var blazorServerUiPackageName = isProTemplate ? "@volo/aspnetcore.components.server.leptonxtheme" : "@abp/aspnetcore.components.server.leptonxlitetheme";
- var ngUiPackageName = isProTemplate ? "@volosoft/abp.ng.theme.lepton-x" : "@abp/ng.theme.lepton-x";
foreach (var packageJsonFilePath in packageJsonFilePaths)
{
steps.Add(new RemoveDependencyFromPackageJsonFileStep(packageJsonFilePath, mvcUiPackageName));
}
- foreach (var blazorServerPackageJsonFilePath in blazorServerPackageJsonFilePaths)
+ if (uiFramework == UiFramework.BlazorServer)
{
- steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, mvcUiPackageName));
- steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, blazorServerUiPackageName));
+ var blazorServerUiPackageName = isProTemplate ? "@volo/aspnetcore.components.server.leptonxtheme" : "@abp/aspnetcore.components.server.leptonxlitetheme";
+ var blazorServerPackageJsonFilePaths = new List
+ {
+ "/MyCompanyName.MyProjectName.Blazor/package.json",
+ "/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json",
+ "/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json"
+ };
+
+ foreach (var blazorServerPackageJsonFilePath in blazorServerPackageJsonFilePaths)
+ {
+ steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, mvcUiPackageName));
+ steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, blazorServerUiPackageName));
+ }
}
-
- foreach (var angularPackageJsonFilePath in angularPackageJsonFilePaths)
+ else if (uiFramework == UiFramework.Angular)
{
- steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, ngUiPackageName));
- steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, "bootstrap-icons"));
+ var ngUiPackageName = isProTemplate ? "@volosoft/abp.ng.theme.lepton-x" : "@abp/ng.theme.lepton-x";
+ var angularPackageJsonFilePaths = new List
+ {
+ "/angular/package.json"
+ };
+
+ foreach (var angularPackageJsonFilePath in angularPackageJsonFilePaths)
+ {
+ steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, ngUiPackageName));
+ steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, "bootstrap-icons"));
+ }
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
index c6a15389ef..54d234b5c3 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
@@ -35,55 +35,63 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
{
return;
}
+
+ if (context.BuildArgs.Theme != Theme.NotSpecified)
+ {
+ context.Symbols.Add(context.BuildArgs.Theme.Value.ToString().ToUpper());
+ }
if (context.BuildArgs.Theme == Theme.LeptonX)
{
- context.Symbols.Add("LEPTONX");
steps.Add(new ChangeThemeStyleStep());
return;
}
steps.Add(new ChangeThemeStep());
- RemoveLeptonXThemePackagesFromPackageJsonFiles(steps);
+ RemoveLeptonXThemePackagesFromPackageJsonFiles(steps, uiFramework: context.BuildArgs.UiFramework);
}
- private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List steps)
+ private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List steps, UiFramework uiFramework)
{
+ var mvcUiPackageName = "@volo/abp.aspnetcore.mvc.ui.theme.leptonx";
var packageJsonFilePaths = new List
{
"/MyCompanyName.MyProjectName.AuthServer/package.json",
"/MyCompanyName.MyProjectName.Web/package.json"
};
- var blazorServerPackageJsonFilePaths = new List
- {
- "/MyCompanyName.MyProjectName.Blazor.Server/package.json"
- };
-
- var angularPackageJsonFilePaths = new List
- {
- "/angular/package.json"
- };
-
- var mvcUiPackageName = "@volo/abp.aspnetcore.mvc.ui.theme.leptonx";
- var blazorServerUiPackageName = "@volo/aspnetcore.components.server.leptonxtheme";
- var ngUiPackageName = "@volosoft/abp.ng.theme.lepton-x";
-
foreach (var packageJsonFilePath in packageJsonFilePaths)
{
steps.Add(new RemoveDependencyFromPackageJsonFileStep(packageJsonFilePath, mvcUiPackageName));
}
- foreach (var blazorServerPackageJsonFilePath in blazorServerPackageJsonFilePaths)
+ if (uiFramework == UiFramework.BlazorServer)
{
- steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, mvcUiPackageName));
- steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, blazorServerUiPackageName));
+ var blazorServerUiPackageName = "@volo/aspnetcore.components.server.leptonxtheme";
+ var blazorServerPackageJsonFilePaths = new List
+ {
+ "/MyCompanyName.MyProjectName.Blazor/package.json"
+ };
+
+ foreach (var blazorServerPackageJsonFilePath in blazorServerPackageJsonFilePaths)
+ {
+ steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, mvcUiPackageName));
+ steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, blazorServerUiPackageName));
+ }
}
-
- foreach (var angularPackageJsonFilePath in angularPackageJsonFilePaths)
+ else if (uiFramework == UiFramework.Angular)
{
- steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, ngUiPackageName));
- steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, "bootstrap-icons"));
+ var ngUiPackageName = "@volosoft/abp.ng.theme.lepton-x";
+ var angularPackageJsonFilePaths = new List
+ {
+ "/angular/package.json"
+ };
+
+ foreach (var angularPackageJsonFilePath in angularPackageJsonFilePaths)
+ {
+ steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, ngUiPackageName));
+ steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, "bootstrap-icons"));
+ }
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
index 7f2831351a..ed90d16cfd 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
@@ -153,7 +153,7 @@ public class SolutionModuleAdder : ITransientDependency
var documentationLink = module.GetFirstDocumentationLinkOrNull();
if (documentationLink != null)
{
- CmdHelper.OpenWebPage(documentationLink);
+ CmdHelper.Open(documentationLink);
}
return module;
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs
index 95eaa8136c..32c1acf5be 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs
@@ -18,20 +18,20 @@ public class CmdHelper : ICmdHelper, ITransientDependency
CliOptions = cliOptions.Value;
}
- public void OpenWebPage(string url)
+ public void Open(string pathOrUrl)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- url = url.Replace("&", "^&");
- Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
+ pathOrUrl = pathOrUrl.Replace("&", "^&");
+ Process.Start(new ProcessStartInfo("cmd", $"/c start {pathOrUrl}") { CreateNoWindow = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- Process.Start("xdg-open", url);
+ Process.Start("xdg-open", pathOrUrl);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- Process.Start("open", url);
+ Process.Start("open", pathOrUrl);
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ICmdHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ICmdHelper.cs
index f96697b1cc..2f71bda7b5 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ICmdHelper.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ICmdHelper.cs
@@ -4,7 +4,7 @@ namespace Volo.Abp.Cli.Utils;
public interface ICmdHelper
{
- void OpenWebPage(string url);
+ void Open(string pathOrUrl);
void Run(string file, string arguments);
diff --git a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs
index 2d19fa8bc2..24ece0e52e 100644
--- a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs
+++ b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs
@@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
namespace System;
@@ -17,6 +18,7 @@ public static class AbpObjectExtensions
/// Type to be casted
/// Object to cast
/// Casted object
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T As(this object obj)
where T : class
{
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs
index dded0d794e..74a62116a4 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
+using System.Linq;
using JetBrains.Annotations;
namespace Volo.Abp.Localization;
@@ -59,4 +60,45 @@ public static class CultureHelper
{
return new CultureInfo(cultureName).Parent.Name;
}
+
+ public static bool IsCompatibleCulture(
+ string sourceCultureName,
+ string targetCultureName)
+ {
+ if (sourceCultureName == targetCultureName)
+ {
+ return true;
+ }
+
+ if (sourceCultureName.StartsWith("zh") && targetCultureName.StartsWith("zh"))
+ {
+ var culture = new CultureInfo(targetCultureName);
+ do
+ {
+ if (culture.Name == sourceCultureName)
+ {
+ return true;
+ }
+
+ culture = new CultureInfo(culture.Name).Parent;
+ } while (!culture.Equals(CultureInfo.InvariantCulture));
+ }
+
+ if (sourceCultureName.Contains("-"))
+ {
+ return false;
+ }
+
+ if (!targetCultureName.Contains("-"))
+ {
+ return false;
+ }
+
+ if (sourceCultureName == GetBaseCultureName(targetCultureName))
+ {
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/AbpDistributedLockOptions.cs b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/AbpDistributedLockOptions.cs
new file mode 100644
index 0000000000..8a885ae877
--- /dev/null
+++ b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/AbpDistributedLockOptions.cs
@@ -0,0 +1,14 @@
+namespace Volo.Abp.DistributedLocking;
+
+public class AbpDistributedLockOptions
+{
+ ///
+ /// DistributedLock key prefix.
+ ///
+ public string KeyPrefix { get; set; }
+
+ public AbpDistributedLockOptions()
+ {
+ KeyPrefix = "";
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/DistributedLockKeyNormalizer.cs b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/DistributedLockKeyNormalizer.cs
new file mode 100644
index 0000000000..bce237c5e3
--- /dev/null
+++ b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/DistributedLockKeyNormalizer.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.Options;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.DistributedLocking;
+
+public class DistributedLockKeyNormalizer : IDistributedLockKeyNormalizer, ITransientDependency
+{
+ protected AbpDistributedLockOptions Options { get; }
+
+ public DistributedLockKeyNormalizer(IOptions options)
+ {
+ Options = options.Value;
+ }
+
+ public virtual string NormalizeKey(string name)
+ {
+ return $"{Options.KeyPrefix}{name}";
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/IDistributedLockKeyNormalizer.cs b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/IDistributedLockKeyNormalizer.cs
new file mode 100644
index 0000000000..13f30cca28
--- /dev/null
+++ b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/IDistributedLockKeyNormalizer.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.DistributedLocking;
+
+public interface IDistributedLockKeyNormalizer
+{
+ string NormalizeKey(string name);
+
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs
index 9eb7a76b9d..fdec4f1d3f 100644
--- a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs
+++ b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs
@@ -9,15 +9,22 @@ namespace Volo.Abp.DistributedLocking;
public class LocalAbpDistributedLock : IAbpDistributedLock, ISingletonDependency
{
private readonly ConcurrentDictionary _localSyncObjects = new();
+ protected IDistributedLockKeyNormalizer DistributedLockKeyNormalizer { get; }
+ public LocalAbpDistributedLock(IDistributedLockKeyNormalizer distributedLockKeyNormalizer)
+ {
+ DistributedLockKeyNormalizer = distributedLockKeyNormalizer;
+ }
+
public async Task TryAcquireAsync(
string name,
TimeSpan timeout = default,
CancellationToken cancellationToken = default)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
-
- var semaphore = _localSyncObjects.GetOrAdd(name, _ => new SemaphoreSlim(1, 1));
+ var key = DistributedLockKeyNormalizer.NormalizeKey(name);
+
+ var semaphore = _localSyncObjects.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
if (!await semaphore.WaitAsync(timeout, cancellationToken))
{
diff --git a/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/DaprAbpDistributedLock.cs b/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/DaprAbpDistributedLock.cs
index 7f95a56542..b4116c105c 100644
--- a/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/DaprAbpDistributedLock.cs
+++ b/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/DaprAbpDistributedLock.cs
@@ -10,13 +10,16 @@ public class DaprAbpDistributedLock : IAbpDistributedLock, ITransientDependency
protected AbpDaprClientFactory DaprClientFactory { get; }
protected AbpDistributedLockDaprOptions DistributedLockDaprOptions { get; }
protected AbpDaprOptions DaprOptions { get; }
+ protected IDistributedLockKeyNormalizer DistributedLockKeyNormalizer { get; }
public DaprAbpDistributedLock(
AbpDaprClientFactory daprClientFactory,
IOptions distributedLockDaprOptions,
- IOptions daprOptions)
+ IOptions daprOptions,
+ IDistributedLockKeyNormalizer distributedLockKeyNormalizer)
{
DaprClientFactory = daprClientFactory;
+ DistributedLockKeyNormalizer = distributedLockKeyNormalizer;
DaprOptions = daprOptions.Value;
DistributedLockDaprOptions = distributedLockDaprOptions.Value;
}
@@ -32,10 +35,11 @@ public class DaprAbpDistributedLock : IAbpDistributedLock, ITransientDependency
}
var daprClient = await DaprClientFactory.CreateAsync();
+ var key = DistributedLockKeyNormalizer.NormalizeKey(name);
var lockResponse = await daprClient.Lock(
DistributedLockDaprOptions.StoreName,
- name,
+ key,
DaprOptions.AppId,
(int)timeout.TotalSeconds,
cancellationToken);
diff --git a/framework/src/Volo.Abp.DistributedLocking/Volo/Abp/DistributedLocking/MedallionAbpDistributedLock.cs b/framework/src/Volo.Abp.DistributedLocking/Volo/Abp/DistributedLocking/MedallionAbpDistributedLock.cs
index 769303a348..0defc5a75f 100644
--- a/framework/src/Volo.Abp.DistributedLocking/Volo/Abp/DistributedLocking/MedallionAbpDistributedLock.cs
+++ b/framework/src/Volo.Abp.DistributedLocking/Volo/Abp/DistributedLocking/MedallionAbpDistributedLock.cs
@@ -12,13 +12,17 @@ public class MedallionAbpDistributedLock : IAbpDistributedLock, ITransientDepend
{
protected IDistributedLockProvider DistributedLockProvider { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
+
+ protected IDistributedLockKeyNormalizer DistributedLockKeyNormalizer { get; }
public MedallionAbpDistributedLock(
IDistributedLockProvider distributedLockProvider,
- ICancellationTokenProvider cancellationTokenProvider)
+ ICancellationTokenProvider cancellationTokenProvider,
+ IDistributedLockKeyNormalizer distributedLockKeyNormalizer)
{
DistributedLockProvider = distributedLockProvider;
CancellationTokenProvider = cancellationTokenProvider;
+ DistributedLockKeyNormalizer = distributedLockKeyNormalizer;
}
public async Task TryAcquireAsync(
@@ -27,11 +31,12 @@ public class MedallionAbpDistributedLock : IAbpDistributedLock, ITransientDepend
CancellationToken cancellationToken = default)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
-
+ var key = DistributedLockKeyNormalizer.NormalizeKey(name);
+
CancellationTokenProvider.FallbackToProvider(cancellationToken);
var handle = await DistributedLockProvider.TryAcquireLockAsync(
- name,
+ key,
timeout,
CancellationTokenProvider.FallbackToProvider(cancellationToken)
);
diff --git a/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpClientProxyExtensions.cs b/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpClientProxyExtensions.cs
index 7bc239813d..57233ac972 100644
--- a/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpClientProxyExtensions.cs
+++ b/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpClientProxyExtensions.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Net.Http;
using System.Reflection;
using Castle.DynamicProxy;
using JetBrains.Annotations;
@@ -196,6 +197,16 @@ public static class ServiceCollectionHttpClientProxyExtensions
{
clientBuildAction(remoteServiceConfigurationName, provider, client);
}
+ }).ConfigurePrimaryHttpMessageHandler((provider) =>
+ {
+ var handler = new HttpClientHandler { UseCookies = false };
+
+ foreach (var handlerAction in preOptions.ProxyClientHandlerActions)
+ {
+ handlerAction(remoteServiceConfigurationName, provider, handler);
+ }
+
+ return handler;
});
foreach (var clientBuildAction in preOptions.ProxyClientBuildActions)
diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs
index 240f35fe0a..b2ffae98a5 100644
--- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs
+++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs
@@ -12,11 +12,14 @@ public class AbpHttpClientBuilderOptions
internal HashSet ConfiguredProxyClients { get; }
public List> ProxyClientActions { get; }
+
+ public List> ProxyClientHandlerActions { get; }
public AbpHttpClientBuilderOptions()
{
ProxyClientBuildActions = new List>();
ConfiguredProxyClients = new HashSet();
ProxyClientActions = new List>();
+ ProxyClientHandlerActions = new List>();
}
}
diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs
index 7147c81271..7c73df0d51 100644
--- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs
+++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs
@@ -37,7 +37,7 @@ public class ConsumerPool : IConsumerPool, ISingletonDependency
return Consumers.GetOrAdd(
connectionName, connection => new Lazy>(() =>
{
- var config = new ConsumerConfig(Options.Connections.GetOrDefault(connection))
+ var config = new ConsumerConfig(Options.Connections.GetOrDefault(connection).ToDictionary(k => k.Key, v => v.Value))
{
GroupId = groupId,
EnableAutoCommit = false
diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs
index 691a29c1d2..ac2edf31a7 100644
--- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs
+++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs
@@ -39,7 +39,7 @@ public class ProducerPool : IProducerPool, ISingletonDependency
return Producers.GetOrAdd(
connectionName, connection => new Lazy>(() =>
{
- var producerConfig = new ProducerConfig(Options.Connections.GetOrDefault(connection));
+ var producerConfig = new ProducerConfig(Options.Connections.GetOrDefault(connection).ToDictionary(k => k.Key, v => v.Value));
Options.ConfigureProducer?.Invoke(producerConfig);
return new ProducerBuilder(producerConfig).Build();
diff --git a/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/AbpStringLocalizerFactoryExtensions.cs b/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/AbpStringLocalizerFactoryExtensions.cs
index 896fdb6fa0..8aa2515a54 100644
--- a/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/AbpStringLocalizerFactoryExtensions.cs
+++ b/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/AbpStringLocalizerFactoryExtensions.cs
@@ -1,10 +1,66 @@
-namespace Microsoft.Extensions.Localization;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Volo.Abp;
+
+namespace Microsoft.Extensions.Localization;
public static class AbpStringLocalizerFactoryExtensions
{
+ [CanBeNull]
public static IStringLocalizer CreateDefaultOrNull(this IStringLocalizerFactory localizerFactory)
{
- return (localizerFactory as IAbpStringLocalizerFactoryWithDefaultResourceSupport)
+ return (localizerFactory as IAbpStringLocalizerFactory)
?.CreateDefaultOrNull();
}
+
+ [CanBeNull]
+ public static IStringLocalizer CreateByResourceNameOrNull(
+ this IStringLocalizerFactory localizerFactory,
+ string resourceName)
+ {
+ return (localizerFactory as IAbpStringLocalizerFactory)
+ ?.CreateByResourceNameOrNull(resourceName);
+ }
+
+ [NotNull]
+ public static IStringLocalizer CreateByResourceName(
+ this IStringLocalizerFactory localizerFactory,
+ string resourceName)
+ {
+ var localizer = localizerFactory.CreateByResourceNameOrNull(resourceName);
+ if (localizer == null)
+ {
+ throw new AbpException("Couldn't find a localizer with given resource name: " + resourceName);
+ }
+
+ return localizer;
+ }
+
+ [ItemCanBeNull]
+ public static async Task CreateByResourceNameOrNullAsync(
+ this IStringLocalizerFactory localizerFactory,
+ string resourceName)
+ {
+ var abpLocalizerFactory = localizerFactory as IAbpStringLocalizerFactory;
+ if (abpLocalizerFactory == null)
+ {
+ return null;
+ }
+
+ return await abpLocalizerFactory.CreateByResourceNameOrNullAsync(resourceName);
+ }
+
+ [NotNull]
+ public async static Task CreateByResourceNameAsync(
+ this IStringLocalizerFactory localizerFactory,
+ string resourceName)
+ {
+ var localizer = await localizerFactory.CreateByResourceNameOrNullAsync(resourceName);
+ if (localizer == null)
+ {
+ throw new AbpException("Couldn't find a localizer with given resource name: " + resourceName);
+ }
+
+ return localizer;
+ }
}
diff --git a/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactory.cs b/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactory.cs
new file mode 100644
index 0000000000..8f6699a13d
--- /dev/null
+++ b/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactory.cs
@@ -0,0 +1,16 @@
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+
+namespace Microsoft.Extensions.Localization;
+
+public interface IAbpStringLocalizerFactory
+{
+ [CanBeNull]
+ IStringLocalizer CreateDefaultOrNull();
+
+ [CanBeNull]
+ IStringLocalizer CreateByResourceNameOrNull([NotNull] string resourceName);
+
+ [ItemCanBeNull]
+ Task CreateByResourceNameOrNullAsync([NotNull] string resourceName);
+}
diff --git a/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactoryWithDefaultResourceSupport.cs b/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactoryWithDefaultResourceSupport.cs
deleted file mode 100644
index 0ab20aec4b..0000000000
--- a/framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactoryWithDefaultResourceSupport.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using JetBrains.Annotations;
-
-namespace Microsoft.Extensions.Localization;
-
-public interface IAbpStringLocalizerFactoryWithDefaultResourceSupport
-{
- [CanBeNull]
- IStringLocalizer CreateDefaultOrNull();
-}
diff --git a/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/IAsyncLocalizableString.cs b/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/IAsyncLocalizableString.cs
new file mode 100644
index 0000000000..b8389d546f
--- /dev/null
+++ b/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/IAsyncLocalizableString.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+using Microsoft.Extensions.Localization;
+
+namespace Volo.Abp.Localization;
+
+public interface IAsyncLocalizableString
+{
+ Task LocalizeAsync(IStringLocalizerFactory stringLocalizerFactory);
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/ILocalizableString.cs b/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/ILocalizableString.cs
index 2aecd696ac..d04e61a257 100644
--- a/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/ILocalizableString.cs
+++ b/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/ILocalizableString.cs
@@ -5,4 +5,4 @@ namespace Volo.Abp.Localization;
public interface ILocalizableString
{
LocalizedString Localize(IStringLocalizerFactory stringLocalizerFactory);
-}
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs b/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs
index 88eb8a399d..a98f207a55 100644
--- a/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs
+++ b/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs
@@ -1,11 +1,15 @@
using System;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
-public class LocalizableString : ILocalizableString
+public class LocalizableString : ILocalizableString, IAsyncLocalizableString
{
+ [CanBeNull]
+ public string ResourceName { get; }
+
[CanBeNull]
public Type ResourceType { get; }
@@ -16,22 +20,53 @@ public class LocalizableString : ILocalizableString
{
Name = Check.NotNullOrEmpty(name, nameof(name));
ResourceType = resourceType;
+
+ if (resourceType != null)
+ {
+ ResourceName = LocalizationResourceNameAttribute.GetName(resourceType);
+ }
+ }
+
+ public LocalizableString([NotNull] string name, [CanBeNull] string resourceName = null)
+ {
+ Name = Check.NotNullOrEmpty(name, nameof(name));
+ ResourceName = resourceName;
}
public LocalizedString Localize(IStringLocalizerFactory stringLocalizerFactory)
{
- var localizer = ResourceType != null
- ? stringLocalizerFactory.Create(ResourceType)
- : stringLocalizerFactory.CreateDefaultOrNull();
+ var localizer = CreateStringLocalizerOrNull(stringLocalizerFactory);
+ if (localizer == null)
+ {
+ return new LocalizedString(Name, Name, resourceNotFound: true);
+ }
+
+ var result = localizer[Name];
+
+ if (result.ResourceNotFound && ResourceName != null)
+ {
+ /* Search in the default resource if not found in the provided resource */
+ localizer = stringLocalizerFactory.CreateDefaultOrNull();
+ if (localizer != null)
+ {
+ result = localizer[Name];
+ }
+ }
+ return result;
+ }
+
+ public async Task LocalizeAsync(IStringLocalizerFactory stringLocalizerFactory)
+ {
+ var localizer = await CreateStringLocalizerOrNullAsync(stringLocalizerFactory);
if (localizer == null)
{
- throw new AbpException($"Set {nameof(ResourceType)} or configure the default localization resource type (in the AbpLocalizationOptions)!");
+ throw new AbpException($"Set {nameof(ResourceName)} or configure the default localization resource type (in the AbpLocalizationOptions)!");
}
var result = localizer[Name];
- if (result.ResourceNotFound && ResourceType != null)
+ if (result.ResourceNotFound && ResourceName != null)
{
/* Search in the default resource if not found in the provided resource */
localizer = stringLocalizerFactory.CreateDefaultOrNull();
@@ -44,8 +79,56 @@ public class LocalizableString : ILocalizableString
return result;
}
+ private IStringLocalizer CreateStringLocalizerOrNull(IStringLocalizerFactory stringLocalizerFactory)
+ {
+ if (ResourceType != null)
+ {
+ return stringLocalizerFactory.Create(ResourceType);
+ }
+
+ if (ResourceName != null)
+ {
+ var localizerByName = stringLocalizerFactory.CreateByResourceNameOrNull(ResourceName);
+ if (localizerByName != null)
+ {
+ return localizerByName;
+ }
+ }
+
+ return stringLocalizerFactory.CreateDefaultOrNull();
+ }
+
+ private async Task CreateStringLocalizerOrNullAsync(IStringLocalizerFactory stringLocalizerFactory)
+ {
+ if (ResourceType != null)
+ {
+ return stringLocalizerFactory.Create(ResourceType);
+ }
+
+ if (ResourceName != null)
+ {
+ var localizerByName = await stringLocalizerFactory.CreateByResourceNameOrNullAsync(ResourceName);
+ if (localizerByName != null)
+ {
+ return localizerByName;
+ }
+ }
+
+ return stringLocalizerFactory.CreateDefaultOrNull();
+ }
+
public static LocalizableString Create([NotNull] string name)
{
- return new LocalizableString(typeof(TResource), name);
+ return Create(typeof(TResource), name);
+ }
+
+ public static LocalizableString Create(Type resourceType,[NotNull] string name)
+ {
+ return new LocalizableString(resourceType, name);
+ }
+
+ public static LocalizableString Create([NotNull] string name, [CanBeNull] string resourceName = null)
+ {
+ return new LocalizableString(name, resourceName);
}
}
diff --git a/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableStringExtensions.cs b/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableStringExtensions.cs
new file mode 100644
index 0000000000..b7cff0819f
--- /dev/null
+++ b/framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableStringExtensions.cs
@@ -0,0 +1,19 @@
+using System.Threading.Tasks;
+using Microsoft.Extensions.Localization;
+
+namespace Volo.Abp.Localization;
+
+public static class LocalizableStringExtensions
+{
+ public static async Task LocalizeAsync(
+ this ILocalizableString localizableString,
+ IStringLocalizerFactory stringLocalizerFactory)
+ {
+ if (localizableString is IAsyncLocalizableString asyncLocalizableString)
+ {
+ return await asyncLocalizableString.LocalizeAsync(stringLocalizerFactory);
+ }
+
+ return localizableString.Localize(stringLocalizerFactory);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj
index 64352257f3..53e71d1c38 100644
--- a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj
+++ b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj
@@ -22,6 +22,8 @@
+
+
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs
index 61621c2479..cf5c1ebb82 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs
@@ -4,13 +4,14 @@ using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Resources;
+using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
-public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance
+public class AbpDictionaryBasedStringLocalizer : IAbpStringLocalizer
{
- public LocalizationResource Resource { get; }
+ public LocalizationResourceBase Resource { get; }
public List BaseLocalizers { get; }
@@ -20,7 +21,10 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
public virtual LocalizedString this[string name, params object[] arguments] => GetLocalizedStringFormatted(name, arguments);
- public AbpDictionaryBasedStringLocalizer(LocalizationResource resource, List baseLocalizers, AbpLocalizationOptions abpLocalizationOptions)
+ public AbpDictionaryBasedStringLocalizer(
+ LocalizationResourceBase resource,
+ List baseLocalizers,
+ AbpLocalizationOptions abpLocalizationOptions)
{
Resource = resource;
BaseLocalizers = baseLocalizers;
@@ -34,16 +38,46 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
includeParentCultures
);
}
+
+ public async Task> GetAllStringsAsync(bool includeParentCultures)
+ {
+ return await GetAllStringsAsync(
+ CultureInfo.CurrentUICulture.Name,
+ includeParentCultures
+ );
+ }
- public IEnumerable GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers)
+ public IEnumerable GetAllStrings(
+ bool includeParentCultures,
+ bool includeBaseLocalizers,
+ bool includeDynamicContributors)
{
return GetAllStrings(
CultureInfo.CurrentUICulture.Name,
includeParentCultures,
- includeBaseLocalizers
+ includeBaseLocalizers,
+ includeDynamicContributors
+ );
+ }
+
+ public async Task> GetAllStringsAsync(
+ bool includeParentCultures,
+ bool includeBaseLocalizers,
+ bool includeDynamicContributors)
+ {
+ return await GetAllStringsAsync(
+ CultureInfo.CurrentUICulture.Name,
+ includeParentCultures,
+ includeBaseLocalizers,
+ includeDynamicContributors
);
}
+ public Task> GetSupportedCulturesAsync()
+ {
+ return Resource.Contributors.GetSupportedCulturesAsync();
+ }
+
protected virtual LocalizedString GetLocalizedStringFormatted(string name, params object[] arguments)
{
return GetLocalizedStringFormatted(name, CultureInfo.CurrentUICulture.Name, arguments);
@@ -84,7 +118,10 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
return value;
}
- protected virtual LocalizedString GetLocalizedStringOrNull(string name, string cultureName, bool tryDefaults = true)
+ protected virtual LocalizedString GetLocalizedStringOrNull(
+ string name,
+ string cultureName,
+ bool tryDefaults = true)
{
//Try to get from original dictionary (with country code)
var strOriginal = Resource.Contributors.GetOrNull(cultureName, name);
@@ -131,7 +168,67 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
protected virtual IReadOnlyList GetAllStrings(
string cultureName,
bool includeParentCultures = true,
- bool includeBaseLocalizers = true)
+ bool includeBaseLocalizers = true,
+ bool includeDynamicContributors = true)
+ {
+ //TODO: Can be optimized (example: if it's already default dictionary, skip overriding)
+
+ var allStrings = new Dictionary();
+
+ if (includeBaseLocalizers)
+ {
+ foreach (var baseLocalizer in BaseLocalizers.Select(l => l))
+ {
+ using (CultureHelper.Use(CultureInfo.GetCultureInfo(cultureName)))
+ {
+ //TODO: Try/catch is a workaround here!
+ try
+ {
+ var baseLocalizedString = baseLocalizer.GetAllStrings(
+ includeParentCultures,
+ includeBaseLocalizers, // Always true, I know!
+ includeDynamicContributors
+ );
+
+ foreach (var localizedString in baseLocalizedString)
+ {
+ allStrings[localizedString.Name] = localizedString;
+ }
+ }
+ catch (MissingManifestResourceException)
+ {
+
+ }
+ }
+ }
+ }
+
+ if (includeParentCultures)
+ {
+ //Fill all strings from default culture
+ if (!Resource.DefaultCultureName.IsNullOrEmpty())
+ {
+ Resource.Contributors.Fill(Resource.DefaultCultureName, allStrings, includeDynamicContributors);
+ }
+
+ //Overwrite all strings from the language based on country culture
+ if (cultureName.Contains("-"))
+ {
+ Resource.Contributors.Fill(CultureHelper.GetBaseCultureName(cultureName), allStrings, includeDynamicContributors);
+ }
+ }
+
+ //Overwrite all strings from the original culture
+ Resource.Contributors.Fill(cultureName, allStrings, includeDynamicContributors);
+
+ return allStrings.Values.ToImmutableList();
+ }
+
+ protected virtual async Task> GetAllStringsAsync(
+ string cultureName,
+ bool includeParentCultures = true,
+ bool includeBaseLocalizers = true,
+ bool includeDynamicContributors = true)
{
//TODO: Can be optimized (example: if it's already default dictionary, skip overriding)
@@ -146,7 +243,12 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
//TODO: Try/catch is a workaround here!
try
{
- var baseLocalizedString = baseLocalizer.GetAllStrings(includeParentCultures);
+ var baseLocalizedString = await baseLocalizer.GetAllStringsAsync(
+ includeParentCultures,
+ includeBaseLocalizers, // Always true, I know!
+ includeDynamicContributors
+ );
+
foreach (var localizedString in baseLocalizedString)
{
allStrings[localizedString.Name] = localizedString;
@@ -165,23 +267,35 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
//Fill all strings from default culture
if (!Resource.DefaultCultureName.IsNullOrEmpty())
{
- Resource.Contributors.Fill(Resource.DefaultCultureName, allStrings);
+ await Resource.Contributors.FillAsync(
+ Resource.DefaultCultureName,
+ allStrings,
+ includeDynamicContributors
+ );
}
//Overwrite all strings from the language based on country culture
if (cultureName.Contains("-"))
{
- Resource.Contributors.Fill(CultureHelper.GetBaseCultureName(cultureName), allStrings);
+ await Resource.Contributors.FillAsync(
+ CultureHelper.GetBaseCultureName(cultureName),
+ allStrings,
+ includeDynamicContributors
+ );
}
}
//Overwrite all strings from the original culture
- Resource.Contributors.Fill(cultureName, allStrings);
+ await Resource.Contributors.FillAsync(
+ cultureName,
+ allStrings,
+ includeDynamicContributors
+ );
return allStrings.Values.ToImmutableList();
}
- public class CultureWrapperStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance
+ public class CultureWrapperStringLocalizer : IAbpStringLocalizer
{
private readonly string _cultureName;
private readonly AbpDictionaryBasedStringLocalizer _innerLocalizer;
@@ -201,9 +315,28 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
return _innerLocalizer.GetAllStrings(_cultureName, includeParentCultures);
}
- public IEnumerable GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers)
+ public IEnumerable GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers, bool includeDynamicContributors)
+ {
+ return _innerLocalizer.GetAllStrings(_cultureName, includeParentCultures, includeBaseLocalizers, includeDynamicContributors);
+ }
+
+ public Task> GetAllStringsAsync(bool includeParentCultures)
+ {
+ return _innerLocalizer.GetAllStringsAsync(includeParentCultures);
+ }
+
+ public Task> GetAllStringsAsync(bool includeParentCultures, bool includeBaseLocalizers, bool includeDynamicContributors)
+ {
+ return _innerLocalizer.GetAllStringsAsync(
+ includeParentCultures,
+ includeBaseLocalizers,
+ includeDynamicContributors
+ );
+ }
+
+ public Task> GetSupportedCulturesAsync()
{
- return _innerLocalizer.GetAllStrings(_cultureName, includeParentCultures, includeBaseLocalizers);
+ return _innerLocalizer.GetSupportedCulturesAsync();
}
}
}
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs
index 4ce8c32f88..85be669593 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs
@@ -1,6 +1,7 @@
using Volo.Abp.Localization.Resources.AbpLocalization;
using Volo.Abp.Modularity;
using Volo.Abp.Settings;
+using Volo.Abp.Threading;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Localization;
@@ -8,7 +9,8 @@ namespace Volo.Abp.Localization;
[DependsOn(
typeof(AbpVirtualFileSystemModule),
typeof(AbpSettingsModule),
- typeof(AbpLocalizationAbstractionsModule)
+ typeof(AbpLocalizationAbstractionsModule),
+ typeof(AbpThreadingModule)
)]
public class AbpLocalizationModule : AbpModule
{
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerExtensions.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerExtensions.cs
index 8201043c6e..9b3bb2c201 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerExtensions.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerExtensions.cs
@@ -1,5 +1,7 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Reflection;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Localization;
using Volo.Abp.DynamicProxy;
@@ -39,14 +41,16 @@ public static class AbpStringLocalizerExtensions
public static IEnumerable GetAllStrings(
this IStringLocalizer stringLocalizer,
bool includeParentCultures,
- bool includeBaseLocalizers)
+ bool includeBaseLocalizers,
+ bool includeDynamicContributors)
{
- var internalLocalizer = (ProxyHelper.UnProxy(stringLocalizer) as IStringLocalizer).GetInternalLocalizer();
- if (internalLocalizer is IStringLocalizerSupportsInheritance stringLocalizerSupportsInheritance)
+ var internalLocalizer = ((IStringLocalizer)ProxyHelper.UnProxy(stringLocalizer)).GetInternalLocalizer();
+ if (internalLocalizer is IAbpStringLocalizer abpStringLocalizer)
{
- return stringLocalizerSupportsInheritance.GetAllStrings(
+ return abpStringLocalizer.GetAllStrings(
includeParentCultures,
- includeBaseLocalizers
+ includeBaseLocalizers,
+ includeDynamicContributors
);
}
@@ -54,4 +58,59 @@ public static class AbpStringLocalizerExtensions
includeParentCultures
);
}
+
+ public static async Task> GetAllStringsAsync(
+ this IStringLocalizer stringLocalizer,
+ bool includeParentCultures,
+ bool includeBaseLocalizers,
+ bool includeDynamicContributors)
+ {
+ var internalLocalizer = ((IStringLocalizer)ProxyHelper.UnProxy(stringLocalizer)).GetInternalLocalizer();
+ if (internalLocalizer is IAbpStringLocalizer abpStringLocalizer)
+ {
+ return await abpStringLocalizer.GetAllStringsAsync(
+ includeParentCultures,
+ includeBaseLocalizers,
+ includeDynamicContributors
+ );
+ }
+
+ return stringLocalizer.GetAllStrings(
+ includeParentCultures
+ );
+ }
+
+ public static async Task> GetSupportedCulturesAsync(this IStringLocalizer localizer)
+ {
+ var internalLocalizer = ((IStringLocalizer)ProxyHelper.UnProxy(localizer)).GetInternalLocalizer();
+ if (internalLocalizer is IAbpStringLocalizer abpStringLocalizer)
+ {
+ return await abpStringLocalizer.GetSupportedCulturesAsync();
+ }
+
+ return Array.Empty();
+ }
+
+ public static Task> GetAllStringsAsync(
+ this IStringLocalizer localizer)
+ {
+ return localizer.GetAllStringsAsync(includeParentCultures: true);
+ }
+
+ public static Task> GetAllStringsAsync(
+ this IStringLocalizer localizer,
+ bool includeParentCultures)
+ {
+ Check.NotNull(localizer, nameof(localizer));
+
+ var internalLocalizer = ((IStringLocalizer)ProxyHelper.UnProxy(localizer)).GetInternalLocalizer();
+ if (internalLocalizer is IAbpStringLocalizer abpStringLocalizer)
+ {
+ return abpStringLocalizer.GetAllStringsAsync(includeParentCultures: includeParentCultures);
+ }
+
+ return Task.FromResult(
+ localizer.GetAllStrings(includeParentCultures: true)
+ );
+ }
}
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs
index 3f47fd779d..b89a54abd9 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs
@@ -2,60 +2,182 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
+using Volo.Abp.Localization.External;
+using Volo.Abp.Threading;
namespace Volo.Abp.Localization;
-public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLocalizerFactoryWithDefaultResourceSupport
+public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLocalizerFactory
{
protected internal AbpLocalizationOptions AbpLocalizationOptions { get; }
protected ResourceManagerStringLocalizerFactory InnerFactory { get; }
protected IServiceProvider ServiceProvider { get; }
- protected ConcurrentDictionary LocalizerCache { get; }
+ protected IExternalLocalizationStore ExternalLocalizationStore { get; }
+ protected ConcurrentDictionary LocalizerCache { get; }
+ protected SemaphoreSlim LocalizerCacheSemaphore { get; } = new(1, 1);
- //TODO: It's better to use decorator pattern for IStringLocalizerFactory instead of getting ResourceManagerStringLocalizerFactory as a dependency.
public AbpStringLocalizerFactory(
ResourceManagerStringLocalizerFactory innerFactory,
IOptions abpLocalizationOptions,
- IServiceProvider serviceProvider)
+ IServiceProvider serviceProvider,
+ IExternalLocalizationStore externalLocalizationStore)
{
InnerFactory = innerFactory;
ServiceProvider = serviceProvider;
+ ExternalLocalizationStore = externalLocalizationStore;
AbpLocalizationOptions = abpLocalizationOptions.Value;
- LocalizerCache = new ConcurrentDictionary();
+ LocalizerCache = new ConcurrentDictionary();
}
public virtual IStringLocalizer Create(Type resourceType)
{
- var resource = AbpLocalizationOptions.Resources.GetOrDefault(resourceType);
+ return Create(resourceType, lockCache: true);
+ }
+
+ private IStringLocalizer Create(Type resourceType, bool lockCache)
+ {
+ var resource = AbpLocalizationOptions.Resources.GetOrNull(resourceType);
if (resource == null)
{
return InnerFactory.Create(resourceType);
}
- if (LocalizerCache.TryGetValue(resourceType, out var cacheItem))
+ return CreateInternal(resource.ResourceName, resource, lockCache);
+ }
+
+ public IStringLocalizer CreateByResourceNameOrNull(string resourceName)
+ {
+ return CreateByResourceNameOrNullInternal(resourceName, lockCache: true);
+ }
+
+ private IStringLocalizer CreateByResourceNameOrNullInternal(
+ string resourceName,
+ bool lockCache)
+ {
+ var resource = AbpLocalizationOptions.Resources.GetOrDefault(resourceName);
+ if (resource == null)
+ {
+ resource = ExternalLocalizationStore.GetResourceOrNull(resourceName);
+ if (resource == null)
+ {
+ return null;
+ }
+ }
+
+ return CreateInternal(resourceName, resource, lockCache);
+ }
+
+ public Task CreateByResourceNameOrNullAsync(string resourceName)
+ {
+ return CreateByResourceNameOrNullInternalAsync(resourceName, lockCache: true);
+ }
+
+ private async Task CreateByResourceNameOrNullInternalAsync(
+ string resourceName,
+ bool lockCache)
+ {
+ var resource = AbpLocalizationOptions.Resources.GetOrDefault(resourceName);
+ if (resource == null)
+ {
+ resource = await ExternalLocalizationStore.GetResourceOrNullAsync(resourceName);
+ if (resource == null)
+ {
+ return null;
+ }
+ }
+
+ return await CreateInternalAsync(resourceName, resource, lockCache);
+ }
+
+ private IStringLocalizer CreateInternal(
+ string resourceName,
+ LocalizationResourceBase resource,
+ bool lockCache)
+ {
+ if (LocalizerCache.TryGetValue(resourceName, out var cacheItem))
{
return cacheItem.Localizer;
}
- lock (LocalizerCache)
+ IStringLocalizer GetOrCreateLocalizer()
{
+ // Double check
+ if (LocalizerCache.TryGetValue(resourceName, out var cacheItem2))
+ {
+ return cacheItem2.Localizer;
+ }
+
return LocalizerCache.GetOrAdd(
- resourceType,
+ resourceName,
_ => CreateStringLocalizerCacheItem(resource)
).Localizer;
}
+
+ if (lockCache)
+ {
+ using (LocalizerCacheSemaphore.Lock())
+ {
+ return GetOrCreateLocalizer();
+ }
+ }
+ else
+ {
+ return GetOrCreateLocalizer();
+ }
}
+
+ private async Task CreateInternalAsync(
+ string resourceName,
+ LocalizationResourceBase resource,
+ bool lockCache)
+ {
+ if (LocalizerCache.TryGetValue(resourceName, out var cacheItem))
+ {
+ return cacheItem.Localizer;
+ }
- private StringLocalizerCacheItem CreateStringLocalizerCacheItem(LocalizationResource resource)
+ async Task GetOrCreateLocalizerAsync()
+ {
+ // Double check
+ if (LocalizerCache.TryGetValue(resourceName, out var cacheItem2))
+ {
+ return cacheItem2.Localizer;
+ }
+
+ var newCacheItem = await CreateStringLocalizerCacheItemAsync(resource);
+ LocalizerCache[resourceName] = newCacheItem;
+ return newCacheItem.Localizer;
+ }
+
+ if (lockCache)
+ {
+ using (await LocalizerCacheSemaphore.LockAsync())
+ {
+ return await GetOrCreateLocalizerAsync();
+ }
+ }
+ else
+ {
+ return await GetOrCreateLocalizerAsync();
+ }
+ }
+
+ private StringLocalizerCacheItem CreateStringLocalizerCacheItem(LocalizationResourceBase resource)
{
- foreach (var globalContributor in AbpLocalizationOptions.GlobalContributors)
+ foreach (var globalContributorType in AbpLocalizationOptions.GlobalContributors)
{
- resource.Contributors.Add((ILocalizationResourceContributor)Activator.CreateInstance(globalContributor));
+ resource.Contributors.Add(
+ Activator
+ .CreateInstance(globalContributorType)
+ .As()
+ );
}
var context = new LocalizationResourceInitializationContext(resource, ServiceProvider);
@@ -68,7 +190,49 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca
return new StringLocalizerCacheItem(
new AbpDictionaryBasedStringLocalizer(
resource,
- resource.BaseResourceTypes.Select(Create).ToList(),
+ resource
+ .BaseResourceNames
+ .Select(x => CreateByResourceNameOrNullInternal(x, lockCache: false))
+ .Where(x => x != null)
+ .ToList(),
+ AbpLocalizationOptions
+ )
+ );
+ }
+
+ private async Task CreateStringLocalizerCacheItemAsync(LocalizationResourceBase resource)
+ {
+ foreach (var globalContributorType in AbpLocalizationOptions.GlobalContributors)
+ {
+ resource.Contributors.Add(
+ Activator
+ .CreateInstance(globalContributorType)
+ .As()
+ );
+ }
+
+ var context = new LocalizationResourceInitializationContext(resource, ServiceProvider);
+
+ foreach (var contributor in resource.Contributors)
+ {
+ contributor.Initialize(context);
+ }
+
+ var baseLocalizers = new List();
+
+ foreach (var baseResourceName in resource.BaseResourceNames)
+ {
+ var baseLocalizer = await CreateByResourceNameOrNullInternalAsync(baseResourceName, lockCache: false);
+ if (baseLocalizer != null)
+ {
+ baseLocalizers.Add(baseLocalizer);
+ }
+ }
+
+ return new StringLocalizerCacheItem(
+ new AbpDictionaryBasedStringLocalizer(
+ resource,
+ baseLocalizers,
AbpLocalizationOptions
)
);
@@ -76,8 +240,6 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca
public virtual IStringLocalizer Create(string baseName, string location)
{
- //TODO: Investigate when this is called?
-
return InnerFactory.Create(baseName, location);
}
@@ -106,4 +268,4 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca
return Create(AbpLocalizationOptions.DefaultResourceType);
}
-}
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/IExternalLocalizationStore.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/IExternalLocalizationStore.cs
new file mode 100644
index 0000000000..cd48bb1bd9
--- /dev/null
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/IExternalLocalizationStore.cs
@@ -0,0 +1,17 @@
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Localization.External;
+
+public interface IExternalLocalizationStore
+{
+ [CanBeNull]
+ LocalizationResourceBase GetResourceOrNull([NotNull] string resourceName);
+
+ [ItemCanBeNull]
+ Task GetResourceOrNullAsync([NotNull] string resourceName);
+
+ Task GetResourceNamesAsync();
+
+ Task GetResourcesAsync();
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/NullExternalLocalizationStore.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/NullExternalLocalizationStore.cs
new file mode 100644
index 0000000000..508f947981
--- /dev/null
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/NullExternalLocalizationStore.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Localization.External;
+
+public class NullExternalLocalizationStore : IExternalLocalizationStore, ISingletonDependency
+{
+ public LocalizationResourceBase GetResourceOrNull(string resourceName)
+ {
+ return null;
+ }
+
+ public Task GetResourceOrNullAsync(string resourceName)
+ {
+ return Task.FromResult(null);
+ }
+
+ public Task GetResourceNamesAsync()
+ {
+ return Task.FromResult(Array.Empty());
+ }
+
+ public Task GetResourcesAsync()
+ {
+ return Task.FromResult(Array.Empty());
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IAbpStringLocalizer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IAbpStringLocalizer.cs
new file mode 100644
index 0000000000..59fbbcb7b4
--- /dev/null
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IAbpStringLocalizer.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Localization;
+
+namespace Volo.Abp.Localization;
+
+public interface IAbpStringLocalizer : IStringLocalizer
+{
+ IEnumerable GetAllStrings(
+ bool includeParentCultures,
+ bool includeBaseLocalizers,
+ bool includeDynamicContributors
+ );
+
+ Task> GetAllStringsAsync(
+ bool includeParentCultures
+ );
+
+ Task> GetAllStringsAsync(
+ bool includeParentCultures,
+ bool includeBaseLocalizers,
+ bool includeDynamicContributors
+ );
+
+ Task> GetSupportedCulturesAsync();
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationResourceContributor.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationResourceContributor.cs
index bfccb660a0..2a79d20821 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationResourceContributor.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationResourceContributor.cs
@@ -1,13 +1,20 @@
using System.Collections.Generic;
+using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public interface ILocalizationResourceContributor
{
+ bool IsDynamic { get; }
+
void Initialize(LocalizationResourceInitializationContext context);
LocalizedString GetOrNull(string cultureName, string name);
void Fill(string cultureName, Dictionary dictionary);
+
+ Task FillAsync(string cultureName, Dictionary dictionary);
+
+ Task> GetSupportedCulturesAsync();
}
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IStringLocalizerSupportsInheritance.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IStringLocalizerSupportsInheritance.cs
deleted file mode 100644
index cf0bff3631..0000000000
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IStringLocalizerSupportsInheritance.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Collections.Generic;
-using Microsoft.Extensions.Localization;
-
-namespace Volo.Abp.Localization;
-
-public interface IStringLocalizerSupportsInheritance
-{
- IEnumerable GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers);
-}
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs
index f721baf870..5006d62ea8 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs
@@ -22,7 +22,7 @@ public class LocalizableStringSerializer : ILocalizableStringSerializer, ITransi
if (localizableString is LocalizableString realLocalizableString)
{
- return $"L:{LocalizationResourceNameAttribute.GetName(realLocalizableString.ResourceType)},{realLocalizableString.Name}";
+ return $"L:{realLocalizableString.ResourceName},{realLocalizableString.Name}";
}
if (localizableString is FixedLocalizableString fixedLocalizableString)
@@ -61,12 +61,7 @@ public class LocalizableStringSerializer : ILocalizableStringSerializer, ITransi
throw new AbpException("Invalid LocalizableString value: " + value);
}
- var resourceType = LocalizationOptions.Resources.GetOrNull(resourceName)?.ResourceType;
-
- return new LocalizableString(
- resourceType,
- name
- );
+ return LocalizableString.Create(name, resourceName);
default:
return new FixedLocalizableString(value);
}
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs
index a2202dc959..7a55f1f8a3 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs
@@ -5,39 +5,21 @@ using JetBrains.Annotations;
namespace Volo.Abp.Localization;
-public class LocalizationResource
+public class LocalizationResource : LocalizationResourceBase
{
[NotNull]
public Type ResourceType { get; }
- [NotNull]
- public string ResourceName => LocalizationResourceNameAttribute.GetName(ResourceType);
-
- [CanBeNull]
- public string DefaultCultureName { get; set; }
-
- [NotNull]
- public LocalizationResourceContributorList Contributors { get; }
-
- [NotNull]
- public List BaseResourceTypes { get; }
-
public LocalizationResource(
[NotNull] Type resourceType,
[CanBeNull] string defaultCultureName = null,
[CanBeNull] ILocalizationResourceContributor initialContributor = null)
+ : base(
+ LocalizationResourceNameAttribute.GetName(resourceType),
+ defaultCultureName,
+ initialContributor)
{
ResourceType = Check.NotNull(resourceType, nameof(resourceType));
- DefaultCultureName = defaultCultureName;
-
- BaseResourceTypes = new List();
- Contributors = new LocalizationResourceContributorList();
-
- if (initialContributor != null)
- {
- Contributors.Add(initialContributor);
- }
-
AddBaseResourceTypes();
}
@@ -51,8 +33,8 @@ public class LocalizationResource
{
foreach (var baseResourceType in descriptor.GetInheritedResourceTypes())
{
- BaseResourceTypes.AddIfNotContains(baseResourceType);
+ BaseResourceNames.AddIfNotContains(LocalizationResourceNameAttribute.GetName(baseResourceType));
}
}
}
-}
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceBase.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceBase.cs
new file mode 100644
index 0000000000..a7ec4fb93b
--- /dev/null
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceBase.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Localization;
+
+public abstract class LocalizationResourceBase
+{
+ [NotNull]
+ public string ResourceName { get; }
+
+ public List BaseResourceNames { get; }
+
+ [CanBeNull]
+ public string DefaultCultureName { get; set; }
+
+ [NotNull]
+ public LocalizationResourceContributorList Contributors { get; }
+
+ public LocalizationResourceBase(
+ [NotNull] string resourceName,
+ [CanBeNull] string defaultCultureName = null,
+ [CanBeNull] ILocalizationResourceContributor initialContributor = null)
+ {
+ ResourceName = Check.NotNullOrWhiteSpace(resourceName, nameof(resourceName));
+ DefaultCultureName = defaultCultureName;
+
+ Contributors = new LocalizationResourceContributorList();
+ BaseResourceNames = new();
+
+ if (initialContributor != null)
+ {
+ Contributors.Add(initialContributor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs
index d52fe78943..6df15920da 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs
@@ -1,15 +1,24 @@
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public class LocalizationResourceContributorList : List
{
- public LocalizedString GetOrNull(string cultureName, string name)
+ public LocalizedString GetOrNull(
+ string cultureName,
+ string name,
+ bool includeDynamicContributors = true)
{
foreach (var contributor in this.Select(x => x).Reverse())
{
+ if (!includeDynamicContributors && contributor.IsDynamic)
+ {
+ continue;
+ }
+
var localString = contributor.GetOrNull(cultureName, name);
if (localString != null)
{
@@ -20,11 +29,47 @@ public class LocalizationResourceContributorList : List dictionary)
+ public void Fill(
+ string cultureName,
+ Dictionary dictionary,
+ bool includeDynamicContributors = true)
{
foreach (var contributor in this)
{
+ if (!includeDynamicContributors && contributor.IsDynamic)
+ {
+ continue;
+ }
+
contributor.Fill(cultureName, dictionary);
}
}
+
+ public async Task FillAsync(
+ string cultureName,
+ Dictionary dictionary,
+ bool includeDynamicContributors = true)
+ {
+ foreach (var contributor in this)
+ {
+ if (!includeDynamicContributors && contributor.IsDynamic)
+ {
+ continue;
+ }
+
+ await contributor.FillAsync(cultureName, dictionary);
+ }
+ }
+
+ internal async Task> GetSupportedCulturesAsync()
+ {
+ var cultures = new List();
+
+ foreach (var contributor in this)
+ {
+ cultures.AddRange(await contributor.GetSupportedCulturesAsync());
+ }
+
+ return cultures;
+ }
}
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs
index d672a2f17a..4a88eac199 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs
@@ -4,9 +4,9 @@ using JetBrains.Annotations;
namespace Volo.Abp.Localization;
-public class LocalizationResourceDictionary : Dictionary
+public class LocalizationResourceDictionary : Dictionary
{
- private readonly Dictionary _resourcesByNames = new();
+ private readonly Dictionary _resourcesByTypes = new();
public LocalizationResource Add([CanBeNull] string defaultCultureName = null)
{
@@ -15,24 +15,41 @@ public class LocalizationResourceDictionary : Dictionary()
+ return resource;
+ }
+
+ public LocalizationResourceBase Get()
{
var resourceType = typeof(TResource);
- var resource = this.GetOrDefault(resourceType);
+ var resource = _resourcesByTypes.GetOrDefault(resourceType);
if (resource == null)
{
throw new AbpException("Can not find a resource with given type: " + resourceType.AssemblyQualifiedName);
@@ -41,9 +58,9 @@ public class LocalizationResourceDictionary : Dictionary(
+ [NotNull] this TLocalizationResource localizationResource,
[NotNull] string virtualPath)
+ where TLocalizationResource : LocalizationResourceBase
{
Check.NotNull(localizationResource, nameof(localizationResource));
Check.NotNull(virtualPath, nameof(virtualPath));
@@ -21,16 +22,35 @@ public static class LocalizationResourceExtensions
return localizationResource;
}
- public static LocalizationResource AddBaseTypes(
- [NotNull] this LocalizationResource localizationResource,
+ public static TLocalizationResource AddBaseTypes(
+ [NotNull] this TLocalizationResource localizationResource,
[NotNull] params Type[] types)
+ where TLocalizationResource : LocalizationResourceBase
{
Check.NotNull(localizationResource, nameof(localizationResource));
Check.NotNull(types, nameof(types));
foreach (var type in types)
{
- localizationResource.BaseResourceTypes.AddIfNotContains(type);
+ localizationResource
+ .BaseResourceNames
+ .AddIfNotContains(LocalizationResourceNameAttribute.GetName(type));
+ }
+
+ return localizationResource;
+ }
+
+ public static TLocalizationResource AddBaseResources(
+ [NotNull] this TLocalizationResource localizationResource,
+ [NotNull] params string[] baseResourceNames)
+ where TLocalizationResource : LocalizationResourceBase
+ {
+ Check.NotNull(localizationResource, nameof(localizationResource));
+ Check.NotNull(baseResourceNames, nameof(baseResourceNames));
+
+ foreach (var baseResourceName in baseResourceNames)
+ {
+ localizationResource.BaseResourceNames.AddIfNotContains(baseResourceName);
}
return localizationResource;
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceInitializationContext.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceInitializationContext.cs
index 845938efb2..7f26466877 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceInitializationContext.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceInitializationContext.cs
@@ -4,11 +4,11 @@ namespace Volo.Abp.Localization;
public class LocalizationResourceInitializationContext
{
- public LocalizationResource Resource { get; }
+ public LocalizationResourceBase Resource { get; }
public IServiceProvider ServiceProvider { get; }
- public LocalizationResourceInitializationContext(LocalizationResource resource, IServiceProvider serviceProvider)
+ public LocalizationResourceInitializationContext(LocalizationResourceBase resource, IServiceProvider serviceProvider)
{
Resource = resource;
ServiceProvider = serviceProvider;
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/NonTypedLocalizationResource.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/NonTypedLocalizationResource.cs
new file mode 100644
index 0000000000..2a8300d522
--- /dev/null
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/NonTypedLocalizationResource.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Localization;
+
+public class NonTypedLocalizationResource : LocalizationResourceBase
+{
+ public NonTypedLocalizationResource(
+ [NotNull] string resourceName,
+ [CanBeNull] string defaultCultureName = null,
+ [CanBeNull] ILocalizationResourceContributor initialContributor = null
+ ) : base(
+ resourceName,
+ defaultCultureName,
+ initialContributor)
+ {
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs
index 1ac41dba06..15b45efaff 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Localization;
@@ -11,6 +12,8 @@ namespace Volo.Abp.Localization.VirtualFiles;
public abstract class VirtualFileLocalizationResourceContributorBase : ILocalizationResourceContributor
{
+ public bool IsDynamic => false;
+
private readonly string _virtualPath;
private IVirtualFileProvider _virtualFileProvider;
private Dictionary _dictionaries;
@@ -37,6 +40,17 @@ public abstract class VirtualFileLocalizationResourceContributorBase : ILocaliza
GetDictionaries().GetOrDefault(cultureName)?.Fill(dictionary);
}
+ public Task FillAsync(string cultureName, Dictionary dictionary)
+ {
+ Fill(cultureName, dictionary);
+ return Task.CompletedTask;
+ }
+
+ public Task> GetSupportedCulturesAsync()
+ {
+ return Task.FromResult((IEnumerable)GetDictionaries().Keys);
+ }
+
private Dictionary GetDictionaries()
{
var dictionaries = _dictionaries;
diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs
index 8b9dca76be..085e486756 100644
--- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs
+++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs
@@ -8,20 +8,18 @@ public static class ExtensionPropertyConfigurationExtensions
public static string GetLocalizationResourceNameOrNull(
this ExtensionPropertyConfiguration property)
{
- var resourceType = property.GetLocalizationResourceTypeOrNull();
- if (resourceType == null)
+ if (property.DisplayName is LocalizableString localizableString)
{
- return null;
+ return localizableString.ResourceName;
}
- return LocalizationResourceNameAttribute.GetName(resourceType);
+ return null;
}
public static Type GetLocalizationResourceTypeOrNull(
this ExtensionPropertyConfiguration property)
{
- if (property.DisplayName != null &&
- property.DisplayName is LocalizableString localizableString)
+ if (property.DisplayName is LocalizableString localizableString)
{
return localizableString.ResourceType;
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/AbpLayoutHookOptions.cs b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/AbpLayoutHookOptions.cs
similarity index 89%
rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/AbpLayoutHookOptions.cs
rename to framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/AbpLayoutHookOptions.cs
index 68851c1860..d5ba4aa239 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/AbpLayoutHookOptions.cs
+++ b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/AbpLayoutHookOptions.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-namespace Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook;
+namespace Volo.Abp.Ui.LayoutHooks;
public class AbpLayoutHookOptions
{
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookInfo.cs b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/LayoutHookInfo.cs
similarity index 84%
rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookInfo.cs
rename to framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/LayoutHookInfo.cs
index e2bb26dac5..db4ca39466 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookInfo.cs
+++ b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/LayoutHookInfo.cs
@@ -1,11 +1,11 @@
using System;
-namespace Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook;
+namespace Volo.Abp.Ui.LayoutHooks;
public class LayoutHookInfo
{
///
- /// ViewComponent type.
+ /// Component type.
///
public Type ComponentType { get; }
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewModel.cs b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/LayoutHookViewModel.cs
similarity index 79%
rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewModel.cs
rename to framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/LayoutHookViewModel.cs
index fbc73d8dad..107ad6990a 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewModel.cs
+++ b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/LayoutHookViewModel.cs
@@ -1,4 +1,4 @@
-namespace Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook;
+namespace Volo.Abp.Ui.LayoutHooks;
public class LayoutHookViewModel
{
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHooks.cs b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/LayoutHooks.cs
similarity index 88%
rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHooks.cs
rename to framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/LayoutHooks.cs
index 588872c46b..fafab40161 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHooks.cs
+++ b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/LayoutHooks/LayoutHooks.cs
@@ -1,4 +1,4 @@
-namespace Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook;
+namespace Volo.Abp.Ui.LayoutHooks;
public static class LayoutHooks
{
diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json
index 0a41ce1ed5..3c1a566112 100644
--- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json
+++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json
@@ -24,6 +24,8 @@
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "字段必须是最小长度为'{0}'的字符串或数组.",
"ThisFieldIsNotAValidPhoneNumber.": "字段不是有效的手机号码.",
"ThisFieldMustBeBetween{0}And{1}": "字段值必须在{0}和{1}范围内.",
+ "ThisFieldMustBeGreaterThanOrEqual{0}": "该字段必须大于或等于 {0}。",
+ "ThisFieldMustBeLessOrEqual{0}": "该字段必须小于或等于 {0}。",
"ThisFieldMustMatchTheRegularExpression{0}": "字段必须匹配正则表达式'{0}'.",
"ThisFieldIsRequired.": "字段不可为空.",
"ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "字段必须是长度为{0}的字符串.",
@@ -31,4 +33,4 @@
"ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "字段{0}不是有效的完全限定的http,https或ftp URL.",
"ThisFieldIsInvalid.": "该字段无效."
}
-}
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationBuilder_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationBuilder_Tests.cs
index 5d3cb09749..56fc8a583a 100644
--- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationBuilder_Tests.cs
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationBuilder_Tests.cs
@@ -12,7 +12,7 @@ public class ApplicationConfigurationBuilder_Tests : AspNetCoreMvcTestBase
{
var applicationConfigurationBuilder = GetRequiredService();
- var config = await applicationConfigurationBuilder.GetAsync();
+ var config = await applicationConfigurationBuilder.GetAsync(new ApplicationConfigurationRequestOptions());
config.Auth.ShouldNotBeNull();
config.Localization.ShouldNotBeNull();
diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs
index 12c8a16833..513423a781 100644
--- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs
+++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs
@@ -34,7 +34,19 @@ public class DistributedCache_ConfigureOptions_Test : AbpIntegratedTest
{
var cache1 = GetRequiredService>();
var cache2 = GetRequiredService>();
-
+
+ cache1.InternalCache.ShouldBe(cache2);
+
await cache1.SetAsync("john", new PersonCacheItem("John Doe"));
var item1 = await cache1.GetAsync("john");
diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Localization/CultureHelper_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Localization/CultureHelper_Tests.cs
new file mode 100644
index 0000000000..d55df57800
--- /dev/null
+++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Localization/CultureHelper_Tests.cs
@@ -0,0 +1,36 @@
+using Shouldly;
+using Xunit;
+
+namespace Volo.Abp.Localization;
+
+public class CultureHelper_Tests
+{
+ [Fact]
+ public void IsCompatibleCulture()
+ {
+ CultureHelper.IsCompatibleCulture("tr", "tr").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("tr", "tr-TR").ShouldBeTrue();
+
+ CultureHelper.IsCompatibleCulture("en", "tr").ShouldBeFalse();
+ CultureHelper.IsCompatibleCulture("en", "tr-TR").ShouldBeFalse();
+
+ CultureHelper.IsCompatibleCulture("en-US", "en").ShouldBeFalse();
+ CultureHelper.IsCompatibleCulture("en-US", "en-GB").ShouldBeFalse();
+
+ CultureHelper.IsCompatibleCulture("zh", "zh-CN").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh", "zh-HK").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh", "zh-MO").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh", "zh-SG").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh", "zh-TW").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh", "zh-Hans").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh", "zh-Hant").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh-Hans", "zh-CN").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh-Hans", "zh-SG").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh-Hant", "zh-HK").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh-Hant", "zh-MO").ShouldBeTrue();
+ CultureHelper.IsCompatibleCulture("zh-Hant", "zh-TW").ShouldBeTrue();
+
+ CultureHelper.IsCompatibleCulture("zh-Hans", "zh-HK").ShouldBeFalse();
+ CultureHelper.IsCompatibleCulture("zh-Hant", "zh-SG").ShouldBeFalse();
+ }
+}
diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs
index 340d091330..7e01a1c42c 100644
--- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs
+++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs
@@ -1,5 +1,4 @@
-using Volo.Abp.Localization.TestResources.Base.CountryNames;
-using Volo.Abp.Localization.TestResources.Base.Validation;
+using Volo.Abp.Localization.TestResources.Base.Validation;
using Volo.Abp.Localization.TestResources.Source;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
@@ -24,12 +23,13 @@ public class AbpLocalizationTestModule : AbpModule
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Base/Validation");
options.Resources
- .Add("en")
+ .Add("LocalizationTestCountryNames")
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Base/CountryNames");
options.Resources
.Add("en")
- .AddVirtualJson("/Volo/Abp/Localization/TestResources/Source");
+ .AddVirtualJson("/Volo/Abp/Localization/TestResources/Source")
+ .AddBaseResources("LocalizationTestCountryNames");
options.Resources
.Get()
diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs
index d643f163b9..ee0e929e32 100644
--- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs
+++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs
@@ -1,5 +1,6 @@
using System.Globalization;
using System.Linq;
+using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using Shouldly;
using Volo.Abp.Localization.TestResources.Source;
@@ -310,7 +311,7 @@ public class AbpLocalization_Tests : AbpIntegratedTest
+{
+ private readonly IStringLocalizerFactory _factory;
+
+ public AbpStringLocalizerFactory_Tests()
+ {
+ _factory = GetRequiredService();
+ }
+
+ [Fact]
+ public void Factory_Type_Should_Be_AbpStringLocalizerFactory()
+ {
+ ProxyHelper.UnProxy(_factory).ShouldBeOfType();
+ }
+
+ [Fact]
+ public void Should_Create_Resource_By_Name()
+ {
+ using (CultureHelper.Use("en"))
+ {
+ var localizer = _factory.CreateByResourceNameOrNull("Test");
+ localizer.ShouldNotBeNull();
+ localizer["CarPlural"].Value.ShouldBe("Cars");
+ }
+ }
+
+ [Fact]
+ public async Task Should_Create_Resource_By_Name_Async()
+ {
+ using (CultureHelper.Use("en"))
+ {
+ var localizer = await _factory.CreateByResourceNameOrNullAsync("Test");
+ localizer.ShouldNotBeNull();
+ localizer["CarPlural"].Value.ShouldBe("Cars");
+ }
+ }
+
+ [Fact]
+ public void Should_Throw_Exception_For_Unknown_Resource_Names()
+ {
+ Assert.Throws(
+ () => _factory.CreateByResourceName("UnknownResourceName")
+ );
+ }
+
+ [Fact]
+ public async Task Should_Throw_Exception_For_Unknown_Resource_Names_Async()
+ {
+ await Assert.ThrowsAsync(
+ async () => await _factory.CreateByResourceNameAsync("UnknownResourceName")
+ );
+ }
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/LocalizableStringSerializer_Tests.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/LocalizableStringSerializer_Tests.cs
index d8fc1717d2..9261d19dbc 100644
--- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/LocalizableStringSerializer_Tests.cs
+++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/LocalizableStringSerializer_Tests.cs
@@ -59,7 +59,7 @@ public class LocalizableStringSerializer_Tests : AbpIntegratedTest();
- localizableString.ResourceType.ShouldBe(typeof(LocalizationTestResource));
+ localizableString.ResourceName.ShouldBe("Test");
localizableString.Name.ShouldBe("Car");
Assert.Throws(() =>
diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/LocalizationTestCountryNamesResource.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/LocalizationTestCountryNamesResource.cs
deleted file mode 100644
index a70436cbc7..0000000000
--- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/LocalizationTestCountryNamesResource.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Volo.Abp.Localization.TestResources.Base.CountryNames;
-
-public sealed class LocalizationTestCountryNamesResource
-{
-
-}
diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs
index bc32e98bcc..95a1bbd32f 100644
--- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs
+++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs
@@ -1,11 +1,9 @@
-using Volo.Abp.Localization.TestResources.Base.CountryNames;
-using Volo.Abp.Localization.TestResources.Base.Validation;
+using Volo.Abp.Localization.TestResources.Base.Validation;
namespace Volo.Abp.Localization.TestResources.Source;
[InheritResource(
- typeof(LocalizationTestValidationResource),
- typeof(LocalizationTestCountryNamesResource)
+ typeof(LocalizationTestValidationResource)
)]
[LocalizationResourceName("Test")]
public sealed class LocalizationTestResource
diff --git a/modules/account/Volo.Abp.Account.abpmdl.json b/modules/account/Volo.Abp.Account.abpmdl.json
index 6762d81f33..dabb3f2ff9 100644
--- a/modules/account/Volo.Abp.Account.abpmdl.json
+++ b/modules/account/Volo.Abp.Account.abpmdl.json
@@ -38,8 +38,8 @@
"path": "src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.abppkg.json",
"folder": "src"
},
- "Volo.Abp.Account.Installer": {
- "path": "src/Volo.Abp.Account.Installer/Volo.Abp.Account.Installer.abppkg.json",
+ "Volo.Abp.Account.Web.OpenIddict": {
+ "path": "src/Volo.Abp.Account.Web.OpenIddict/Volo.Abp.Account.Web.OpenIddict.abppkg.json",
"folder": "src"
}
}
diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.abppkg.analyze.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.abppkg.analyze.json
index 4edb52319f..8cb409b1fc 100644
--- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.abppkg.analyze.json
+++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.abppkg.analyze.json
@@ -1,6 +1,6 @@
{
"name": "Volo.Abp.Account.Application.Contracts",
- "hash": "eda5b3412f7e1dd8dc07761a04f3a064",
+ "hash": "63eee204db33a361b137b2cff508e53f",
"contents": [
{
"namespace": "Volo.Abp.Account",
diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg.analyze.json b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg.analyze.json
index db23b687a0..6f591b2e46 100644
--- a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg.analyze.json
+++ b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg.analyze.json
@@ -1,6 +1,6 @@
{
"name": "Volo.Abp.Account.Application",
- "hash": "7fb9c3f35b18f2e9a0577d571fccf9df",
+ "hash": "9607ce81498fab564ea4a3681c15f72d",
"contents": [
{
"namespace": "Volo.Abp.Account",
@@ -24,26 +24,6 @@
],
"contentType": "applicationService",
"name": "ProfileAppService"
- },
- {
- "defaultValue": "true",
- "displayName": "Is self-registration enabled",
- "description": "Whether a user can register the account by him or herself.",
- "isVisibleToClient": true,
- "isInherited": true,
- "isEncrypted": false,
- "contentType": "setting",
- "name": "Abp.Account.IsSelfRegistrationEnabled"
- },
- {
- "defaultValue": "true",
- "displayName": "Authenticate with a local account",
- "description": "Indicates if the server will allow users to authenticate with a local account.",
- "isVisibleToClient": true,
- "isInherited": true,
- "isEncrypted": false,
- "contentType": "setting",
- "name": "Abp.Account.EnableLocalLogin"
}
]
}
\ No newline at end of file
diff --git a/modules/account/src/Volo.Abp.Account.HttpApi.Client/Volo.Abp.Account.HttpApi.Client.abppkg.analyze.json b/modules/account/src/Volo.Abp.Account.HttpApi.Client/Volo.Abp.Account.HttpApi.Client.abppkg.analyze.json
index 5fec7571fa..57bf3ed42d 100644
--- a/modules/account/src/Volo.Abp.Account.HttpApi.Client/Volo.Abp.Account.HttpApi.Client.abppkg.analyze.json
+++ b/modules/account/src/Volo.Abp.Account.HttpApi.Client/Volo.Abp.Account.HttpApi.Client.abppkg.analyze.json
@@ -1,6 +1,6 @@
{
"name": "Volo.Abp.Account.HttpApi.Client",
- "hash": "ca67872e6cd8c25508461f7d171a8f04",
+ "hash": "62165afd5965b8041a9ac1f1356ad2e8",
"contents": [
{
"namespace": "Volo.Abp.Account",
diff --git a/modules/account/src/Volo.Abp.Account.HttpApi/Volo.Abp.Account.HttpApi.abppkg.analyze.json b/modules/account/src/Volo.Abp.Account.HttpApi/Volo.Abp.Account.HttpApi.abppkg.analyze.json
index 04d48dcab0..597cca045f 100644
--- a/modules/account/src/Volo.Abp.Account.HttpApi/Volo.Abp.Account.HttpApi.abppkg.analyze.json
+++ b/modules/account/src/Volo.Abp.Account.HttpApi/Volo.Abp.Account.HttpApi.abppkg.analyze.json
@@ -1,6 +1,6 @@
{
"name": "Volo.Abp.Account.HttpApi",
- "hash": "b178895fe2a7f470f36c4fd121b7c07a",
+ "hash": "c789b459a83b26349ca6f0fcab82a244",
"contents": [
{
"namespace": "Volo.Abp.Account",
diff --git a/modules/account/src/Volo.Abp.Account.Installer/Volo.Abp.Account.Installer.csproj b/modules/account/src/Volo.Abp.Account.Installer/Volo.Abp.Account.Installer.csproj
index 73df3c03d9..8ad72ac461 100644
--- a/modules/account/src/Volo.Abp.Account.Installer/Volo.Abp.Account.Installer.csproj
+++ b/modules/account/src/Volo.Abp.Account.Installer/Volo.Abp.Account.Installer.csproj
@@ -4,19 +4,21 @@
- net5.0
+ net6.0true
-
+
+
+
diff --git a/modules/account/src/Volo.Abp.Account.Installer/Volo/Abp/Account/AbpAccountInstallerModule.cs b/modules/account/src/Volo.Abp.Account.Installer/Volo/Abp/Account/AbpAccountInstallerModule.cs
index 060b47ef68..01d2fa0e3f 100644
--- a/modules/account/src/Volo.Abp.Account.Installer/Volo/Abp/Account/AbpAccountInstallerModule.cs
+++ b/modules/account/src/Volo.Abp.Account.Installer/Volo/Abp/Account/AbpAccountInstallerModule.cs
@@ -1,11 +1,9 @@
using Volo.Abp.Modularity;
-using Volo.Abp.Studio;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Account;
[DependsOn(
- typeof(AbpStudioModuleInstallerModule),
typeof(AbpVirtualFileSystemModule)
)]
public class AbpAccountInstallerModule : AbpModule
diff --git a/modules/account/src/Volo.Abp.Account.Installer/Volo/Abp/Account/AccountInstallerPipelineBuilder.cs b/modules/account/src/Volo.Abp.Account.Installer/Volo/Abp/Account/AccountInstallerPipelineBuilder.cs
deleted file mode 100644
index 9fb955c9a8..0000000000
--- a/modules/account/src/Volo.Abp.Account.Installer/Volo/Abp/Account/AccountInstallerPipelineBuilder.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Threading.Tasks;
-using Microsoft.Extensions.DependencyInjection;
-using Volo.Abp.DependencyInjection;
-using Volo.Abp.Studio.ModuleInstalling;
-
-namespace Volo.Abp.Account;
-
-[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
-[ExposeServices(typeof(IModuleInstallingPipelineBuilder))]
-public class AccountInstallerPipelineBuilder : ModuleInstallingPipelineBuilderBase, IModuleInstallingPipelineBuilder, ITransientDependency
-{
- public async Task BuildAsync(ModuleInstallingContext context)
- {
- return GetBasePipeline(context);
- }
-}
diff --git a/modules/account/src/Volo.Abp.Account.Installer/Volo.Abp.Account.Installer.abppkg.json b/modules/account/src/Volo.Abp.Account.Web.OpenIddict/Volo.Abp.Account.Web.OpenIddict.abppkg.json
similarity index 100%
rename from modules/account/src/Volo.Abp.Account.Installer/Volo.Abp.Account.Installer.abppkg.json
rename to modules/account/src/Volo.Abp.Account.Web.OpenIddict/Volo.Abp.Account.Web.OpenIddict.abppkg.json
diff --git a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.abppkg.analyze.json b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.abppkg.analyze.json
index a5848927a4..02531fd742 100644
--- a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.abppkg.analyze.json
+++ b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.abppkg.analyze.json
@@ -1,6 +1,6 @@
{
"name": "Volo.Abp.Account.Web",
- "hash": "5fb2aa05261d4febe7ea7083d80fea74",
+ "hash": "56ee8c43448399a621216b5e54008a54",
"contents": [
{
"namespace": "Volo.Abp.Account.Web",
diff --git a/modules/audit-logging/Volo.Abp.AuditLogging.abpmdl.json b/modules/audit-logging/Volo.Abp.AuditLogging.abpmdl.json
index 9f425bc44e..5b2cc3f54b 100644
--- a/modules/audit-logging/Volo.Abp.AuditLogging.abpmdl.json
+++ b/modules/audit-logging/Volo.Abp.AuditLogging.abpmdl.json
@@ -37,10 +37,6 @@
"Volo.Abp.AuditLogging.MongoDB.Tests": {
"path": "test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo.Abp.AuditLogging.MongoDB.Tests.abppkg.json",
"folder": "test"
- },
- "Volo.Abp.AuditLogging.Installer": {
- "path": "src/Volo.Abp.AuditLogging.Installer/Volo.Abp.AuditLogging.Installer.abppkg.json",
- "folder": "src"
}
}
}
\ No newline at end of file
diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Installer/Volo.Abp.AuditLogging.Installer.csproj b/modules/audit-logging/src/Volo.Abp.AuditLogging.Installer/Volo.Abp.AuditLogging.Installer.csproj
index 27ab25e369..5a04e797a5 100644
--- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Installer/Volo.Abp.AuditLogging.Installer.csproj
+++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Installer/Volo.Abp.AuditLogging.Installer.csproj
@@ -4,13 +4,13 @@
-