Browse Source

Merge branch 'dev' into Dynamic-Features

pull/13881/head
maliming 4 years ago
parent
commit
c9785f9dad
No known key found for this signature in database GPG Key ID: 96224957E51C89E
  1. 4
      .gitignore
  2. 79
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json
  3. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json
  4. 29
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json
  5. 3
      build/common.ps1
  6. 35
      docs/en/Background-Jobs-Hangfire.md
  7. 4
      docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md
  8. 19
      docs/en/Distributed-Locking.md
  9. 15
      docs/en/Entities.md
  10. 1
      docs/en/Multi-Tenancy.md
  11. 155
      docs/en/Themes/LeptonXLite/AspNetCore.md
  12. 125
      docs/en/UI/Blazor/Layout-Hooks.md
  13. 8
      docs/en/docs-nav.json
  14. BIN
      docs/en/images/bookstore-banner-component.png
  15. BIN
      docs/en/images/leptonxlite-brand-component.png
  16. BIN
      docs/en/images/leptonxlite-breadcrumb-component.png
  17. BIN
      docs/en/images/leptonxlite-language-switch-component.png
  18. BIN
      docs/en/images/leptonxlite-mobile-language-switch-component.png
  19. BIN
      docs/en/images/leptonxlite-mobile-user-menu-component.png
  20. BIN
      docs/en/images/leptonxlite-page-alerts-component.png
  21. BIN
      docs/en/images/leptonxlite-sidebar-menu-component.png
  22. BIN
      docs/en/images/leptonxlite-user-menu-component.png
  23. 35
      docs/zh-Hans/Background-Jobs-Hangfire.md
  24. 19
      docs/zh-Hans/Distributed-Locking.md
  25. 16
      docs/zh-Hans/Entities.md
  26. 1
      docs/zh-Hans/Multi-Tenancy.md
  27. 7
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor
  28. 39
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs
  29. 6
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs
  30. 27
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs
  31. 1
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs
  32. 3
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs
  33. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs
  34. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.Generated.cs
  35. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.cs
  36. 99
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json
  37. 87
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteExternalLocalizationStore.cs
  38. 126
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs
  39. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs
  40. 34
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs
  41. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs
  42. 15
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs
  43. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs
  44. 11
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationRequestDto.cs
  45. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs
  46. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs
  47. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs
  48. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationLocalizationAppService.cs
  49. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs
  50. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml
  51. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs
  52. 45
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  53. 5
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs
  54. 12
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs
  55. 91
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs
  56. 23
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationController.cs
  57. 4
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
  58. 69
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpApplicationLocalizationScriptController.cs
  59. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs
  60. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs
  61. 4
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs
  62. 6
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/EntityPropertyChangeInfo.cs
  63. 26
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs
  64. 4
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
  65. 13
      framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs
  66. 121
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
  67. 1
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs
  68. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
  69. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  70. 61
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
  71. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveDependencyFromPackageJsonFileStep.cs
  72. 63
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
  73. 56
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
  74. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
  75. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs
  76. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ICmdHelper.cs
  77. 2
      framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs
  78. 42
      framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs
  79. 14
      framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/AbpDistributedLockOptions.cs
  80. 19
      framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/DistributedLockKeyNormalizer.cs
  81. 7
      framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/IDistributedLockKeyNormalizer.cs
  82. 11
      framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs
  83. 8
      framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/DaprAbpDistributedLock.cs
  84. 11
      framework/src/Volo.Abp.DistributedLocking/Volo/Abp/DistributedLocking/MedallionAbpDistributedLock.cs
  85. 11
      framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpClientProxyExtensions.cs
  86. 3
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs
  87. 2
      framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs
  88. 2
      framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs
  89. 60
      framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/AbpStringLocalizerFactoryExtensions.cs
  90. 16
      framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactory.cs
  91. 9
      framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactoryWithDefaultResourceSupport.cs
  92. 9
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/IAsyncLocalizableString.cs
  93. 2
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/ILocalizableString.cs
  94. 97
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs
  95. 19
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableStringExtensions.cs
  96. 2
      framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj
  97. 161
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs
  98. 4
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs
  99. 71
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerExtensions.cs
  100. 194
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs

4
.gitignore

@ -323,3 +323,7 @@ deploy/npm-auth-token.txt
deploy/ssh-password.txt
deploy/github-api-key.txt
deploy/_run_all_log.txt
# No commit yarn.lock files in the subfolders of templates directory
templates/**/yarn.lock

79
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json

@ -316,14 +316,12 @@
"TrialLicenseStatusFilter": "地位",
"TrialLicenseStartDateFilter": "开始日期",
"TrialLicenseEndDateFilter": "结束日期",
"FirsName": "名",
"FirstName": "名字",
"LastName": "姓",
"StartDate": "开始日期",
"EndDate": "结束日期",
"PurchasedDate": "购买日期",
"OrganizationDetail": "组织详情",
"SendActivationMail": "发送激活邮件",
"ActivationMailSentSuccessfully": "激活邮件发送成功!",
"TrialLicenseStatus": "试用许可证状态",
"TrialLicenseDetail": "试用许可证详情",
"AcceptsMarketingCommunications": "营销传播",
@ -337,17 +335,68 @@
"Expired": "已到期",
"TrialLicenseDeletionWarningMessage": "您确定要删除试用许可证吗?试用许可证、组织、支持帐户将被删除!",
"LicenseCategoryFilter": "执照类别",
"Volo.AbpIo.Commercial:030000": "您已经使用了试用期。",
"Volo.AbpIo.Commercial:030001": "此组织名称已存在。",
"Volo.AbpIo.Commercial:030002": "一旦激活,试用许可证不能设置为请求!",
"Volo.AbpIo.Commercial:030003": "没有这种状态!",
"Volo.AbpIo.Commercial:030004": "由于意外错误,无法更改状态!",
"Volo.AbpIo.Commercial:030005": "当试用许可证处于 -activated- 状态时,可以更新开始和结束日期!",
"Volo.AbpIo.Commercial:030006": "结束日期必须始终大于开始日期!",
"Volo.AbpIo.Commercial:030007": "此试用许可证已激活一次!",
"Volo.AbpIo.Commercial:030008": "购买日期只能在状态为已购买时设置!",
"Volo.AbpIo.Commercial:030009": "未找到用户!",
"Volo.AbpIo.Commercial:030010": "要购买试用许可证,首先您需要激活您的试用许可证!",
"Volo.AbpIo.Commercial:030011": "购买后,您无法删除试用许可证!"
"Permission:SendWelcomeEmail": "发送欢迎邮件",
"SendWelcomeEmail": "发送欢迎邮件",
"SendWelcomeEmailWarningMessage": "你确定要发送欢迎邮件给组织成员吗?",
"SendWelcomeEmailSuccessMessage": "欢迎邮件发送成功!",
"Activate": "激活",
"ActivateTrialLicenseWarningMessage": "激活试用版权限后,将发送欢迎邮件给用户。你确定要激活吗?",
"ActivateTrialLicenseSuccessMessage": "激活成功,欢迎邮件已发送给组织成员。",
"PaymentRequestId": "付款请求编号",
"AdditionalDeveloperCount": "额外开发者数量",
"LicensePrice": "版权价格",
"PurchaseDate": "购买日期",
"IsAbpBookDownloaded": "ABP书籍已下载",
"IsMasteringAbpBookDownloadEnabled": "ABP书籍下载已启用",
"Permission:Accounting:CustomPaymentLinkGenerator": "自定义付款链接",
"CustomPaymentLink": "自定义付款链接",
"Menu:CustomPaymentLink": "自定义付款链接",
"Amount": "金额",
"GenerateCustomPaymentLink": "生成自定义付款链接",
"GeneratedPaymentLink": "生成的付款链接",
"CopyText": "复制文本",
"Permission:CommunityEvents": "活动",
"Menu:Events": "活动",
"Events": "活动",
"EventType": "活动类型",
"Number": "数量",
"RegistrationURL": "报名网址",
"URL": "网址",
"EventDeletionConfirmationMessage": "你确定要删除这个活动吗?",
"Enum:EventType:0": "社区讲话",
"CreateAnEvent": "创建一个活动",
"Permission:CommunitySpeakers": "演讲者",
"CreateASpeaker": "创建一个演讲者",
"Speakers": "演讲者",
"Image": "图片",
"GithubURL": "Github网址",
"SpeakerDeletionConfirmationMessage": "你确定要删除这个演讲者吗?",
"Menu:Speakers": "演讲者",
"ChooseSpeakerImage": "选择一个演讲者图片...",
"SpeakerImage": "演讲者图片",
"AddSpeaker": "添加演讲者",
"ShowPurchaseItemsOfOrganizations": "购买项目",
"Enum:OrganizationPurchaseState:0": "未送达",
"Enum:OrganizationPurchaseState:1": "已送达",
"PurchaseItems": "购买项目",
"SuccessfullyUpdated": "成功更新",
"SuccessfullyAdded": "成功添加",
"PurchaseState": "购买状态",
"ShowBetweenDayCount": "显示之间的天数",
"PurchaseOrder": "购买订单",
"ShowCreateInvoiceOfOrganization": "创建发票",
"ShowCreateQuotationOfOrganization": "创建报价单",
"BookDiscounts": "书籍折扣",
"Permission:BookDiscount": "书籍折扣",
"Menu:BookDiscounts": "书籍折扣",
"BookType": "书籍类型",
"PurchasePlatform": "购买平台",
"StartTime": "开始时间",
"EndTime": "结束时间",
"CreateABookDiscount": "创建一个书籍折扣",
"BookDiscountDeletionConfirmationMessage": "你确定要删除这个书籍折扣吗?",
"CustomPaymentFlexSwitchDescription": "授权",
"AllowFeatureUpgradeOnLicenseExpire": "允许在授权过期时进行功能升级",
"Deleted{0}": "[已删除 {0}]"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json

@ -164,6 +164,9 @@
"Error_Page_500_Description_1": "我们会自动跟踪这些错误,但如果问题仍然存在,请随时 <br /> 联系我们。 与此同时,尝试刷新。",
"Error_Page_500_Description_2": "通过 <a href=\"mailto:info@abp.io\" target=\"_blank\">info@abp.io</a> 与我们联系。",
"Books": "书籍",
"ABPDiscordServer": "ABP Discord 服务器",
"ABPCommunityTalks": "ABP社区讲话",
"ABPCommunityPosts": "ABP社区文章",
"BuyAndGetMonths": "购买 12 个月,<span class=\"text-info\">获得 14 个月!</span>",
"GetYourDeal": "得到你的交易",
"BuyOrRenewLicense": "立即购买或续订许可证并额外获得 2 个月!",

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

@ -137,7 +137,6 @@
"Success": "成功",
"WeWillReplyYou": "你的消息已经发送! 我们会在短时间内给你答复.",
"GoHome": "回到主页面",
"Home": "主页",
"CreateLiveDemo": "创建在线演示",
"RegisterToTheNewsletter": "注册到时事简报以获取有关ABP.IO的消息,比如新发布的内容.",
"EnterYourEmailOrLogin": "输入你的e-mail地址来创建你的演示或者使用你的已有账号<a href=\"{0}\">登录</a>.",
@ -184,6 +183,7 @@
"ChangingLicenseType": "将来更改我的许可类型吗?",
"ChangingLicenseTypeExplanation": "你始终可以在同一许可中添加新的开发人员. 参阅 \"有多少开发者可以参与ABP商业版工作?\". 你还可以通过支付计算出的价格差来升级到更高的许可. 当你升级到更高的许可计划时,可以享受新计划的好处,但是许可升级不会更改许可的到期日期.",
"LicenseExtendUpgradeDiff": "许可扩展和升级有什么区别?",
"LicenseExtendUpgradeDiffExplanation": "<strong>延长:</strong> 你通过延长/续费你的许可证,你将继续获得高级支持和主要或次要更新模块和主题。除此之外,你还将继续创建新的项目。并且你仍然可以使用ABP Suite,这将加快你的开发。当你延长你的许可证时,1年将添加到你的许可证到期日。<hr/><strong>升级:</strong> 你通过升级你的许可证,你将晋升到更高的许可证方案,这将允许你获得额外的奖励。查看<a href=\"/pricing\">许可证比较表</a>来查看许可证方案之间的差异。<strong>但是,当你升级时,你的许可证到期日不会更改!</strong>要延长你的许可证结束日期,你需要延长你的许可证。",
"LicenseRenewalCost": "一年后的许可续期费用是多少?",
"LicenseRenewalCostExplanation": "标准 Team 许可证的续订(扩展)价格为 ${0},标准 Business License 为 ${1},标准 Enterprise License 为 ${2}。 如果您已经是客户,请<a href='{3}' target='_blank'>登录您的帐户</a>查看可用的续订价格。",
"HowDoIRenewMyLicense": "如何续费我的许可证?",
@ -195,10 +195,19 @@
"IsSourceCodeIncludedExplanation4": "<p>将模块的源代码包含到解决方案中,可以最大程度地自定义该模块. 但是当新版本发布时,将无法自动升级模块.</p><p>这些许可均不包含ABP Suite源代码,该源代码是一个外部工具,可以为你生成代码并帮助你进行开发</p><p>有关许可类型之间的其它差异查看<a href=\"{0}\">定价</a>页面.</p>",
"ChangingDevelopers": "我将来可以更改我组织的注册开发人员吗?",
"ChangingDevelopersExplanation": "除了将新的开发人员添加到你的许可中之外,你还可以更改现有的开发人员(可以删除一个开发人员并将新的开发人员添加到同一位置),而无需任何额外费用.",
"WhatHappensWhenLicenseEnds": "我的许可证期间结束时,会发生什么?",
"WhatHappensWhenLicenseEndsExplanation1": "你的ABP商业许可证是一个<a href=\"{0}\" target=\"_blank\">永久许可证</a>。你的许可证到期后,你可以继续开发你的项目。并且你不需要续费你的许可证。你的许可证是一年更新和支持方案默认的。为了继续获得新的功能,性能改进,修复,支持和继续使用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": "你不能再获得<a href=\"{0}\">高级支持</a>。",
"WhatHappensWhenLicenseEndsExplanation7": "如果你想继续获得这些奖励,你可以继续续费你的许可证。如果你在许可证到期后<strong>1个月</strong>内续费,将会应用以下折扣:团队许可证{0}%折扣,商业许可证{1}%折扣,企业许可证{2}%折扣。",
"WhatHappensWhenLicenseEndsExplanation8": "您生成的 ABP 项目未存储在我们的服务器上。 因此,您有责任保留下载的源代码。 当您的许可证到期时,将无法获取您生成的 ABP 项目源代码。",
"WhenShouldIRenewMyLicense": "我什么时候应该续订我的许可?",
"WhenShouldIRenewMyLicenseExplanation": "如果您在许可证到期后 <strong>1 个月</strong>内续订许可证,将享受以下折扣:团队许可证 {0}% 折扣、商业许可证 {1}% 折扣、企业许可证 {2}% 折扣 . 如果您在许可证到期后 <strong>1 个月</strong>续订许可证,续订价格将与许可证购买价格相同,并且续订不会有折扣。",
"TrialPlan": "你们有试用计划吗?",
"TrialPlanExplanation": "ABP商业团队许可证有14天的试用期。若要了解更多信息,请访问<a href={0} target='_blank'>这里</a>。此外,我们为团队许可证提供30天的金额返还保证。你只需要在30天内请求退款。商业和企业许可证将提供60%的金额返还保证。这是因为商业和企业许可证包含了所有模块和主题的全部源代码。",
"DoYouAcceptBankWireTransfer": "你们接受银行电汇吗?",
"DoYouAcceptBankWireTransferExplanation": "是的,我们接受银行电汇。<br />在通过银行转账发送许可费后,将您的收据和所需的许可类型通过电子邮件发送至accounting@abp.io。 我们的国际银行账户信息:",
"HowToUpgrade": "可用新版本时如何升级现有应用程序?",
@ -365,7 +374,9 @@
"CompanySize": "公司规模",
"Next": "下一个",
"StartTrial": "开始我的免费试用",
"ContactUsQuestions": "如果你有任何问题,请联系我们",
"TrialActivatedWarning": "亲爱的{0},用户只能享受 1 个免费试用期。您已经使用了试用期。",
"ActivationRequirement": "你已经距离开始你的试用还有最后一步。<br>检查你的信息后,我们将激活你的许可证。一旦你的许可证激活,我们将向<b>{0}</b>发送电子邮件。请不要担心,这个过程不会太久!",
"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": "领取您的<span class='gradient-framework'>掌握ABP框架</span>电子书",
"AddMemberModal_Warning_1": "如果您尝试添加的<strong>用户名</strong>在系统中不存在,请让您的团队成员在 <a href='{0}/账户/注册'>{0}</ 上注册 a> 并与您分享他/她帐户的用户名。",
"MyOrganizations_Detail_WelcomeMessage": "欢迎加入您的组织,{0}",
@ -591,6 +604,9 @@
"TrainingDetailsHeaderInfo_TrainingHour": "{0} 小时",
"Trainings_Content": "培训内容",
"Trial_Page_StartYourFreeTrial": "开始您的<span class=\"gradient-commercial\">免费试用</span>",
"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": "<a href=\"https://commercial.abp.io\" class=\"text-primary\">commercial.abp.io</a> 和 <a href=\"https://support.abp.io\" class=\"text-primary\">support.abp.io</a>使用相同的凭据。"
"Welcome_Page_UseSameCredentialForCommercialWebsites": "<a href=\"https://commercial.abp.io\" class=\"text-primary\">commercial.abp.io</a> 和 <a href=\"https://support.abp.io\" class=\"text-primary\">support.abp.io</a>使用相同的凭据。",
"WatchCrudPagesVideo": "观看“创建 CRUD 页面的 ABP Suite”视频!",
"WatchGeneratingFromDatabaseVideo": "观看”ABP Suite:从现有数据库表生成 CRUD 页面”视频!",
"WatchTakeCloserLookVideo": "观看“详细了解ABP Suite 的代码生成”视频!",
"ConfirmedEmailAddressRequiredToStartTrial": "你应该有一个确认的电子邮件地址,以便开始试用许可证。",
"EmailVerificationMailNotSent": "电子邮件验证邮件不能发送。",
"GetConfirmationEmail": "<a href=\"javascript:void(0);\" id=\"{0}\">点击这里获取确认邮件</a> 如果你还没有收到。",
"WhichLicenseTypeYouAreInterestedIn": "你感兴趣的许可证类型是什么?"
}
}
}

3
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")

35
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<EmailSendingArgs>, 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.

4
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<AbpRabbitMqEventBusOptions>(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.
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.

19
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<AbpDistributedLockOptions>(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.

15
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<TEntity, TKey>` interface since it requires a single Id property. However, you can always use `IRepository<TEntity>`. 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

1
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.

155
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<AbpBundlingOptions>(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<AbpBundlingOptions>(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**.
![Brand component](../../images/leptonxlite-brand-component.png)
### 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**.
![Breadcrumb component](../../images/leptonxlite-breadcrumb-component.png)
### 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.
![Sidebar menu component](../../images/leptonxlite-sidebar-menu-component.png)
### 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**.
![Page alerts component](../../images/leptonxlite-page-alerts-component.png)
### 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** <u>**without trying to locate it across the website.**</u>
![Language switch component](../../images/leptonxlite-language-switch-component.png)
### 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.
![Mobil language switch component](../../images/leptonxlite-mobile-language-switch-component.png)
### 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.
![User menu component](../../images/leptonxlite-user-menu-component.png)
### 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**.
![Mobile user menu component](../../images/leptonxlite-mobile-user-menu-component.png)
### 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**.

125
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:
![bookstore-banner-component](../../images/bookstore-banner-component.png)
**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<bool> ShowAnnouncementBannerAsync()
{
var announcementLocalStorageValue = await JsRuntime.InvokeAsync<string>("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)
{
<div class="alert alert-info">
A brand new product is in sale. Click <a href="#">here</a> to learn more.
<button @onclick="async () => { await HideAnnouncementBannerAsync(); }">Hide</button>
</div>
}
```
Then, you can add this component to any of the hook points in the `ConfigureServices` of your module class:
```csharp
Configure<AbpLayoutHookOptions>(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<AbpLayoutHookOptions>(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)

8
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"
}
]
},

BIN
docs/en/images/bookstore-banner-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/en/images/leptonxlite-brand-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/images/leptonxlite-breadcrumb-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/en/images/leptonxlite-language-switch-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
docs/en/images/leptonxlite-mobile-language-switch-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/images/leptonxlite-mobile-user-menu-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/en/images/leptonxlite-page-alerts-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/en/images/leptonxlite-sidebar-menu-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/images/leptonxlite-user-menu-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

35
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<EmailSendingArgs>, 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

19
docs/zh-Hans/Distributed-Locking.md

@ -101,6 +101,25 @@ namespace AbpDemo
* `timeout` (`TimeSpan`): 等待获取锁的超时值. 默认值为`TimeSpan.Zero`, 这意味着如果锁已经被另一个应用程序拥有, 它不会等待.
* `cancellationToken`: 取消令牌可在触发后取消操作.
### 配置
#### AbpDistributedLockOptions
`AbpDistributedLockOptions` 是配置分布式锁的主要选项类.
**示例: 设置应用程序的分布式锁Key前缀**
Configure<AbpDistributedLockOptions>(options =>
{
options.KeyPrefix = "MyApp1";
});
> 在你的[模块类](Module-Development-Basics.md)中的 `ConfigureServices` 方法进行配置.
##### 可用选项
* KeyPrefix (string, 默认值: null): 指定分布式锁名称前缀.
### 使用DistributedLock库的API
ABP的`IAbpDistributedLock`服务非常有限, 主要用于ABP框架的内部使用. 对于你自己的应用程序, 可以使用DistributedLock库自己的API. 参见[文档](https://github.com/madelson/DistributedLock)详细信息.

16
docs/zh-Hans/Entities.md

@ -105,6 +105,22 @@ public class UserRole : Entity
> 需要注意,复合主键实体不可以使用 `IRepository<TEntity, TKey>` 接口,因为它需要一个唯一的Id属性. 但你可以使用 `IRepository<TEntity>`.更多信息请参见[仓储](Repositories.md)的文档.
### EntityEquals
`Entity.EntityEquals(...)` 方法用于检查两个实体对象是否相等.
示例:
```csharp
Book book1 = ...
Book book2 = ...
if (book1.EntityEquals(book2)) //Check equality
{
...
}
```
## 聚合根
"*聚合是域驱动设计中的一种模式.DDD的聚合是一组可以作为一个单元处理的域对象.例如,订单及订单系列的商品,这些是独立的对象,但将订单(连同订单系列的商品)视为一个聚合通常是很有用的*"( [查看详细介绍](http://martinfowler.com/bliki/DDD_Aggregate.html))

1
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".

7
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)
{
<DynamicComponent Type="@hook.ComponentType" />
}
}

39
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<AbpLayoutHookOptions> 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<LayoutHookInfo>();
LayoutHookViewModel = new LayoutHookViewModel(layoutHooks.ToArray(), Layout);
return Task.CompletedTask;
}
}

6
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";
}

27
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(

1
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs

@ -15,7 +15,6 @@ public class AbpAspNetCoreMultiTenancyModule : AbpModule
Configure<AbpTenantResolveOptions>(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());

3
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";

7
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>, IAbpApplicationConfigurationAppService
{
public virtual async Task<ApplicationConfigurationDto> GetAsync()
public virtual async Task<ApplicationConfigurationDto> GetAsync(ApplicationConfigurationRequestOptions options)
{
return await RequestAsync<ApplicationConfigurationDto>(nameof(GetAsync));
return await RequestAsync<ApplicationConfigurationDto>(nameof(GetAsync), new ClientProxyRequestTypeValue
{
{ typeof(ApplicationConfigurationRequestOptions), options }
});
}
}

25
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>, IAbpApplicationLocalizationAppService
{
public virtual async Task<ApplicationLocalizationDto> GetAsync(ApplicationLocalizationRequestDto input)
{
return await RequestAsync<ApplicationLocalizationDto>(nameof(GetAsync), new ClientProxyRequestTypeValue
{
{ typeof(ApplicationLocalizationRequestDto), input }
});
}
}

7
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
{
}

99
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": {

87
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<AbpLocalizationOptions> localizationOptions)
{
ConfigurationClient = configurationClient;
LocalizationOptions = localizationOptions.Value;
}
public virtual LocalizationResourceBase GetResourceOrNull(string resourceName)
{
var configurationDto = ConfigurationClient.Get();
return CreateLocalizationResourceFromConfigurationOrNull(resourceName, configurationDto);
}
public virtual async Task<LocalizationResourceBase> GetResourceOrNullAsync(string resourceName)
{
var configurationDto = await ConfigurationClient.GetAsync();
return CreateLocalizationResourceFromConfigurationOrNull(resourceName, configurationDto);
}
public virtual async Task<string[]> GetResourceNamesAsync()
{
var configurationDto = await ConfigurationClient.GetAsync();
return configurationDto
.Localization
.Resources
.Keys
.Where(x => !LocalizationOptions.Resources.ContainsKey(x))
.ToArray();
; }
public virtual async Task<LocalizationResourceBase[]> GetResourcesAsync()
{
var configurationDto = await ConfigurationClient.GetAsync();
var resources = new List<LocalizationResourceBase>();
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);
}
}

126
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<RemoteLocalizationContributor> _logger;
@ -21,50 +26,137 @@ public class RemoteLocalizationContributor : ILocalizationResourceContributor
?? NullLogger<RemoteLocalizationContributor>.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<string, LocalizedString> 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<string, LocalizedString> 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<string, LocalizedString> dictionary)
public virtual async Task FillAsync(string cultureName, Dictionary<string, LocalizedString> 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<string, LocalizedString> 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<string, string> GetResourceOrNull()
public virtual Task<IEnumerable<string>> 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<string>)Array.Empty<string>());
}
protected virtual ApplicationLocalizationResourceDto GetResourceOrNull(string resourceName)
{
var applicationConfigurationDto = _applicationConfigurationClient.Get();
return GetResourceOrNull(applicationConfigurationDto, resourceName);
}
protected virtual async Task<ApplicationLocalizationResourceDto> 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<string>()
};
}
return resource;
_logger.LogWarning($"Could not find the localization resource {resourceName} on the remote server!");
return null;
}
}

13
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<MvcCachedApplicationConfigurationClient>().InitializeAsync();
}
}

34
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<ApplicationConfigurationDto> Cache { get; }
@ -22,19 +23,16 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
IDistributedCache<ApplicationConfigurationDto> 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<ApplicationConfigurationDto> 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<ApplicationConfigurationDto> 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();

9
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
{
/// <summary>
/// Set to true to fill the Values property in <see cref="ApplicationConfigurationDto.Localization"/>.
/// </summary>
public bool IncludeLocalizationResources { get; set; } = true;
}

15
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
{
/// <summary>
/// This is not filled if <see cref="ApplicationConfigurationRequestOptions.IncludeLocalizationResources"/> is false.
/// </summary>
public Dictionary<string, Dictionary<string, string>> Values { get; set; }
/// <summary>
/// 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.
/// </summary>
public Dictionary<string, ApplicationLocalizationResourceDto> Resources { get; set; } = new();
public List<LanguageInfo> Languages { get; set; }
public CurrentCultureDto CurrentCulture { get; set; }

10
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<string, ApplicationLocalizationResourceDto> Resources { get; set; }
}

11
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; }
}

12
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<string, string> Texts { get; set; }
public string[] BaseResources { get; set; }
}

5
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; }

4
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<ApplicationConfigurationDto> GetAsync();
}
Task<ApplicationConfigurationDto> GetAsync(ApplicationConfigurationRequestOptions options);
}

9
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<ApplicationLocalizationDto> GetAsync(ApplicationLocalizationRequestDto input);
}

9
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<IViewComponentResult> 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,

3
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)

1
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;

45
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<ApplicationConfigurationDto> GetAsync()
public virtual async Task<ApplicationConfigurationDto> 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<ApplicationLocalizationConfigurationDto> GetLocalizationConfigAsync()
protected virtual async Task<ApplicationLocalizationConfigurationDto> 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<string, string>();
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<IExternalLocalizationStore>()
.GetResourceNamesAsync()
);
foreach (var resourceName in resourceNames)
{
dictionary[localizedString.Name] = localizedString.Value;
}
var dictionary = new Dictionary<string, string>();
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();

5
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<ApplicationConfigurationDto> GetAsync()
public virtual async Task<ApplicationConfigurationDto> GetAsync(
ApplicationConfigurationRequestOptions options)
{
_antiForgeryManager.SetCookie();
return await _applicationConfigurationAppService.GetAsync();
return await _applicationConfigurationAppService.GetAsync(options);
}
}

12
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<AbpAspNetCoreMvcOptions> options,
IJavascriptMinifier javascriptMinifier,
@ -41,7 +41,13 @@ public class AbpApplicationConfigurationScriptController : AbpController
[Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)]
public async Task<ActionResult> Get()
{
var script = CreateAbpExtendScript(await _configurationAppService.GetAsync());
var script = CreateAbpExtendScript(
await _configurationAppService.GetAsync(
new ApplicationConfigurationRequestOptions {
IncludeLocalizationResources = false
}
)
);
_antiForgeryManager.SetCookie();

91
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<AbpLocalizationOptions> localizationOptions)
{
ExternalLocalizationStore = externalLocalizationStore;
LocalizationOptions = localizationOptions.Value;
}
public async Task<ApplicationLocalizationDto> 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<string, ApplicationLocalizationResourceDto>(resources.Length)
};
foreach (var resource in resources)
{
var dictionary = new Dictionary<string, string>();
var localizer = await StringLocalizerFactory.CreateByResourceNameOrNullAsync(resource.ResourceName);
if (localizer != null)
{
Dictionary<string, LocalizedString> 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;
}
}
}

23
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<ApplicationLocalizationDto> GetAsync(ApplicationLocalizationRequestDto input)
{
return await _localizationAppService.GetAsync(input);
}
}

4
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
);
}

69
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<AbpAspNetCoreMvcOptions> 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<ActionResult> 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();
}
}

2
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

2
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; }

4
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<RequestLocalizationOptions>(await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()),
new OptionsWrapper<RequestLocalizationOptions>(
await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()
),
_loggerFactory
);

6
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/EntityPropertyChangeInfo.cs

@ -9,19 +9,19 @@ public class EntityPropertyChangeInfo
/// Maximum length of <see cref="PropertyName"/> property.
/// Value: 96.
/// </summary>
public const int MaxPropertyNameLength = 96;
public static int MaxPropertyNameLength = 96;
/// <summary>
/// Maximum length of <see cref="NewValue"/> and <see cref="OriginalValue"/> properties.
/// Value: 512.
/// </summary>
public const int MaxValueLength = 512;
public static int MaxValueLength = 512;
/// <summary>
/// Maximum length of <see cref="PropertyTypeFullName"/> property.
/// Value: 512.
/// </summary>
public const int MaxPropertyTypeFullNameLength = 192;
public static int MaxPropertyTypeFullNameLength = 192;
public virtual string NewValue { get; set; }

26
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<AbpBackgroundJobOptions> options)
{
Options = options.Value;
}
public virtual Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null)
{
return Task.FromResult(delay.HasValue
? BackgroundJob.Schedule<HangfireJobExecutionAdapter<TArgs>>(
adapter => adapter.ExecuteAsync(args),
adapter => adapter.ExecuteAsync(GetQueueName(typeof(TArgs)),args),
delay.Value
)
: BackgroundJob.Enqueue<HangfireJobExecutionAdapter<TArgs>>(
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<QueueAttribute>();
if (queueAttribute != null)
{
queueName = queueAttribute.Queue;
}
return queueName;
}
}

4
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<TArgs>
Options = options.Value;
}
public async Task ExecuteAsync(TArgs args)
[Queue("{0}")]
public async Task ExecuteAsync(string queue, TArgs args)
{
if (!Options.IsJobExecutionEnabled)
{

13
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()

121
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs

@ -21,26 +21,115 @@ namespace Volo.Abp.Caching;
/// </summary>
/// <typeparam name="TCacheItem">The type of cache item being cached.</typeparam>
public class DistributedCache<TCacheItem> :
DistributedCache<TCacheItem, string>,
IDistributedCache<TCacheItem>
where TCacheItem : class
{
public DistributedCache(
IOptions<AbpDistributedCacheOptions> 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<TCacheItem, string> InternalCache { get; }
public DistributedCache(IDistributedCache<TCacheItem, string> internalCache)
{
InternalCache = internalCache;
}
public TCacheItem Get(string key, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.Get(key, hideErrors, considerUow);
}
public KeyValuePair<string, TCacheItem>[] GetMany(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.GetMany(keys, hideErrors, considerUow);
}
public Task<KeyValuePair<string, TCacheItem>[]> GetManyAsync(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetManyAsync(keys, hideErrors, considerUow, token);
}
public Task<TCacheItem> 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<TCacheItem> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.GetOrAdd(key, factory, optionsFactory, hideErrors, considerUow);
}
public Task<TCacheItem> GetOrAddAsync(string key, Func<Task<TCacheItem>> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetOrAddAsync(key, factory, optionsFactory, hideErrors, considerUow, token);
}
public KeyValuePair<string, TCacheItem>[] GetOrAddMany(IEnumerable<string> keys, Func<IEnumerable<string>, List<KeyValuePair<string, TCacheItem>>> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.GetOrAddMany(keys, factory, optionsFactory, hideErrors, considerUow);
}
public Task<KeyValuePair<string, TCacheItem>[]> GetOrAddManyAsync(IEnumerable<string> keys, Func<IEnumerable<string>, Task<List<KeyValuePair<string, TCacheItem>>>> factory, Func<DistributedCacheEntryOptions> 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<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false)
{
InternalCache.SetMany(items, options, hideErrors, considerUow);
}
public Task SetManyAsync(IEnumerable<KeyValuePair<string, TCacheItem>> 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<string> keys, bool? hideErrors = null)
{
InternalCache.RefreshMany(keys, hideErrors);
}
public Task RefreshManyAsync(IEnumerable<string> 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<string> keys, bool? hideErrors = null, bool considerUow = false)
{
InternalCache.RemoveMany(keys, hideErrors, considerUow);
}
public Task RemoveManyAsync(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false,
CancellationToken token = default)
{
return InternalCache.RemoveManyAsync(keys, hideErrors, considerUow, token);
}
}

1
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs

@ -14,6 +14,7 @@ namespace Volo.Abp.Caching;
public interface IDistributedCache<TCacheItem> : IDistributedCache<TCacheItem, string>
where TCacheItem : class
{
IDistributedCache<TCacheItem, string> InternalCache { get; }
}
/// <summary>

4
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)

4
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;
}
}

61
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<string, string>
{
@ -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"

3
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;

63
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<ProjectBuildPipelineStep> steps, bool isProTemplate)
private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List<ProjectBuildPipelineStep> 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<string>
{
"/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<string>
{
"/MyCompanyName.MyProjectName.Blazor.Server/package.json",
"/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json",
"/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json"
};
var angularPackageJsonFilePaths = new List<string>
{
"/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<string>
{
"/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<string>
{
"/angular/package.json"
};
foreach (var angularPackageJsonFilePath in angularPackageJsonFilePaths)
{
steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, ngUiPackageName));
steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, "bootstrap-icons"));
}
}
}

56
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<ProjectBuildPipelineStep> steps)
private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List<ProjectBuildPipelineStep> steps, UiFramework uiFramework)
{
var mvcUiPackageName = "@volo/abp.aspnetcore.mvc.ui.theme.leptonx";
var packageJsonFilePaths = new List<string>
{
"/MyCompanyName.MyProjectName.AuthServer/package.json",
"/MyCompanyName.MyProjectName.Web/package.json"
};
var blazorServerPackageJsonFilePaths = new List<string>
{
"/MyCompanyName.MyProjectName.Blazor.Server/package.json"
};
var angularPackageJsonFilePaths = new List<string>
{
"/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<string>
{
"/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<string>
{
"/angular/package.json"
};
foreach (var angularPackageJsonFilePath in angularPackageJsonFilePaths)
{
steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, ngUiPackageName));
steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, "bootstrap-icons"));
}
}
}

2
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;

10
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);
}
}

2
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);

2
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
/// <typeparam name="T">Type to be casted</typeparam>
/// <param name="obj">Object to cast</param>
/// <returns>Casted object</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T As<T>(this object obj)
where T : class
{

42
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;
}
}

14
framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/AbpDistributedLockOptions.cs

@ -0,0 +1,14 @@
namespace Volo.Abp.DistributedLocking;
public class AbpDistributedLockOptions
{
/// <summary>
/// DistributedLock key prefix.
/// </summary>
public string KeyPrefix { get; set; }
public AbpDistributedLockOptions()
{
KeyPrefix = "";
}
}

19
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<AbpDistributedLockOptions> options)
{
Options = options.Value;
}
public virtual string NormalizeKey(string name)
{
return $"{Options.KeyPrefix}{name}";
}
}

7
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);
}

11
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<string, SemaphoreSlim> _localSyncObjects = new();
protected IDistributedLockKeyNormalizer DistributedLockKeyNormalizer { get; }
public LocalAbpDistributedLock(IDistributedLockKeyNormalizer distributedLockKeyNormalizer)
{
DistributedLockKeyNormalizer = distributedLockKeyNormalizer;
}
public async Task<IAbpDistributedLockHandle> 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))
{

8
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<AbpDistributedLockDaprOptions> distributedLockDaprOptions,
IOptions<AbpDaprOptions> daprOptions)
IOptions<AbpDaprOptions> 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);

11
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<IAbpDistributedLockHandle> 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)
);

11
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)

3
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs

@ -12,11 +12,14 @@ public class AbpHttpClientBuilderOptions
internal HashSet<string> ConfiguredProxyClients { get; }
public List<Action<string, IServiceProvider, HttpClient>> ProxyClientActions { get; }
public List<Action<string, IServiceProvider, HttpClientHandler>> ProxyClientHandlerActions { get; }
public AbpHttpClientBuilderOptions()
{
ProxyClientBuildActions = new List<Action<string, IHttpClientBuilder>>();
ConfiguredProxyClients = new HashSet<string>();
ProxyClientActions = new List<Action<string, IServiceProvider, HttpClient>>();
ProxyClientHandlerActions = new List<Action<string, IServiceProvider, HttpClientHandler>>();
}
}

2
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<IConsumer<string, byte[]>>(() =>
{
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

2
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<IProducer<string, byte[]>>(() =>
{
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<string, byte[]>(producerConfig).Build();

60
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<IStringLocalizer> 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<IStringLocalizer> 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;
}
}

16
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<IStringLocalizer> CreateByResourceNameOrNullAsync([NotNull] string resourceName);
}

9
framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactoryWithDefaultResourceSupport.cs

@ -1,9 +0,0 @@
using JetBrains.Annotations;
namespace Microsoft.Extensions.Localization;
public interface IAbpStringLocalizerFactoryWithDefaultResourceSupport
{
[CanBeNull]
IStringLocalizer CreateDefaultOrNull();
}

9
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<LocalizedString> LocalizeAsync(IStringLocalizerFactory stringLocalizerFactory);
}

2
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);
}
}

97
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<LocalizedString> 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<IStringLocalizer> 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<TResource>([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);
}
}

19
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<LocalizedString> LocalizeAsync(
this ILocalizableString localizableString,
IStringLocalizerFactory stringLocalizerFactory)
{
if (localizableString is IAsyncLocalizableString asyncLocalizableString)
{
return await asyncLocalizableString.LocalizeAsync(stringLocalizerFactory);
}
return localizableString.Localize(stringLocalizerFactory);
}
}

2
framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj

@ -22,6 +22,8 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.Settings\Volo.Abp.Settings.csproj" />
<ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" />
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
</ItemGroup>
</Project>

161
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<IStringLocalizer> 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<IStringLocalizer> baseLocalizers, AbpLocalizationOptions abpLocalizationOptions)
public AbpDictionaryBasedStringLocalizer(
LocalizationResourceBase resource,
List<IStringLocalizer> baseLocalizers,
AbpLocalizationOptions abpLocalizationOptions)
{
Resource = resource;
BaseLocalizers = baseLocalizers;
@ -34,16 +38,46 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
includeParentCultures
);
}
public async Task<IEnumerable<LocalizedString>> GetAllStringsAsync(bool includeParentCultures)
{
return await GetAllStringsAsync(
CultureInfo.CurrentUICulture.Name,
includeParentCultures
);
}
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers)
public IEnumerable<LocalizedString> GetAllStrings(
bool includeParentCultures,
bool includeBaseLocalizers,
bool includeDynamicContributors)
{
return GetAllStrings(
CultureInfo.CurrentUICulture.Name,
includeParentCultures,
includeBaseLocalizers
includeBaseLocalizers,
includeDynamicContributors
);
}
public async Task<IEnumerable<LocalizedString>> GetAllStringsAsync(
bool includeParentCultures,
bool includeBaseLocalizers,
bool includeDynamicContributors)
{
return await GetAllStringsAsync(
CultureInfo.CurrentUICulture.Name,
includeParentCultures,
includeBaseLocalizers,
includeDynamicContributors
);
}
public Task<IEnumerable<string>> 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<LocalizedString> 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<string, LocalizedString>();
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<IReadOnlyList<LocalizedString>> 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<LocalizedString> GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers)
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers, bool includeDynamicContributors)
{
return _innerLocalizer.GetAllStrings(_cultureName, includeParentCultures, includeBaseLocalizers, includeDynamicContributors);
}
public Task<IEnumerable<LocalizedString>> GetAllStringsAsync(bool includeParentCultures)
{
return _innerLocalizer.GetAllStringsAsync(includeParentCultures);
}
public Task<IEnumerable<LocalizedString>> GetAllStringsAsync(bool includeParentCultures, bool includeBaseLocalizers, bool includeDynamicContributors)
{
return _innerLocalizer.GetAllStringsAsync(
includeParentCultures,
includeBaseLocalizers,
includeDynamicContributors
);
}
public Task<IEnumerable<string>> GetSupportedCulturesAsync()
{
return _innerLocalizer.GetAllStrings(_cultureName, includeParentCultures, includeBaseLocalizers);
return _innerLocalizer.GetSupportedCulturesAsync();
}
}
}

4
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
{

71
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<LocalizedString> 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<IEnumerable<LocalizedString>> 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<IEnumerable<string>> GetSupportedCulturesAsync(this IStringLocalizer localizer)
{
var internalLocalizer = ((IStringLocalizer)ProxyHelper.UnProxy(localizer)).GetInternalLocalizer();
if (internalLocalizer is IAbpStringLocalizer abpStringLocalizer)
{
return await abpStringLocalizer.GetSupportedCulturesAsync();
}
return Array.Empty<string>();
}
public static Task<IEnumerable<LocalizedString>> GetAllStringsAsync(
this IStringLocalizer localizer)
{
return localizer.GetAllStringsAsync(includeParentCultures: true);
}
public static Task<IEnumerable<LocalizedString>> 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)
);
}
}

194
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<Type, StringLocalizerCacheItem> LocalizerCache { get; }
protected IExternalLocalizationStore ExternalLocalizationStore { get; }
protected ConcurrentDictionary<string, StringLocalizerCacheItem> 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> abpLocalizationOptions,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
IExternalLocalizationStore externalLocalizationStore)
{
InnerFactory = innerFactory;
ServiceProvider = serviceProvider;
ExternalLocalizationStore = externalLocalizationStore;
AbpLocalizationOptions = abpLocalizationOptions.Value;
LocalizerCache = new ConcurrentDictionary<Type, StringLocalizerCacheItem>();
LocalizerCache = new ConcurrentDictionary<string, StringLocalizerCacheItem>();
}
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<IStringLocalizer> CreateByResourceNameOrNullAsync(string resourceName)
{
return CreateByResourceNameOrNullInternalAsync(resourceName, lockCache: true);
}
private async Task<IStringLocalizer> 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<IStringLocalizer> CreateInternalAsync(
string resourceName,
LocalizationResourceBase resource,
bool lockCache)
{
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem))
{
return cacheItem.Localizer;
}
private StringLocalizerCacheItem CreateStringLocalizerCacheItem(LocalizationResource resource)
async Task<IStringLocalizer> 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<ILocalizationResourceContributor>()
);
}
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<StringLocalizerCacheItem> CreateStringLocalizerCacheItemAsync(LocalizationResourceBase resource)
{
foreach (var globalContributorType in AbpLocalizationOptions.GlobalContributors)
{
resource.Contributors.Add(
Activator
.CreateInstance(globalContributorType)
.As<ILocalizationResourceContributor>()
);
}
var context = new LocalizationResourceInitializationContext(resource, ServiceProvider);
foreach (var contributor in resource.Contributors)
{
contributor.Initialize(context);
}
var baseLocalizers = new List<IStringLocalizer>();
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);
}
}
}

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

Loading…
Cancel
Save