From a15ee011a4c61db782b1da1e26331aac02b75932 Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Wed, 22 Apr 2020 07:59:03 +0200 Subject: [PATCH 01/52] Wrong polish culture code in translation file name --- .../Localization/MyProjectName/{pl.json => pl-PL.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/{pl.json => pl-PL.json} (100%) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json similarity index 100% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json From 5dadd2cd2fc3cbe635fc3e6d54caa8bdfef1cb61 Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Wed, 22 Apr 2020 08:07:42 +0200 Subject: [PATCH 02/52] Wrong polish culture code in json file --- .../Localization/MyProjectName/pl-PL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json index 815bbcc83f..33412f307c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:Home": "Home", "Welcome": "Witaj", From 49cf38b02bdb21ba6aac7fd19beb0c85ed0808ef Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Wed, 22 Apr 2020 08:29:31 +0200 Subject: [PATCH 03/52] Rename and change culture in all polish file for proper one pl-PL. Without tests folder. --- .../UI/MultiTenancy/Localization/{pl.json => pl-PL.json} | 2 +- .../Localization/Resource/{pl.json => pl-PL.json} | 2 +- .../Localization/Resources/AbpUi/{pl.json => pl-PL.json} | 2 +- .../Abp/Validation/Localization/{pl.json => pl-PL.json} | 2 +- .../Account/Localization/Resources/{pl.json => pl-PL.json} | 2 +- .../Blogging/ApplicationContracts/{pl.json => pl-PL.json} | 2 +- .../Blogging/Localization/Resources/{pl.json => pl-PL.json} | 2 +- .../Resources/VoloDocs/Web/{pl.json => pl-PL.json} | 2 +- .../Docs/ApplicationContracts/{pl.json => pl-PL.json} | 2 +- .../Volo/Docs/Localization/Domain/{pl.json => pl-PL.json} | 2 +- .../Localization/Domain/{pl.json => pl-PL.json} | 2 +- .../Volo/Abp/Identity/Localization/{pl.json => pl-PL.json} | 2 +- .../Localization/Domain/{pl.json => pl-PL.json} | 2 +- .../Resources/AbpSettingManagement/{pl.json => pl-PL.json} | 2 +- .../Localization/Resources/{pl.json => pl-PL.json} | 2 +- .../ApplicationContracts/{pl.json => pl-PL.json} | 2 +- .../ProductManagement/Localization/Domain/pl-PL.json | 6 ++++++ .../ProductManagement/Localization/Domain/pl.json | 6 ------ .../Resources/ProductManagement/{pl.json => pl-PL.json} | 2 +- .../Localization/MyProjectName/pl-PL.json | 6 ++++++ .../Localization/MyProjectName/pl.json | 6 ------ 21 files changed, 29 insertions(+), 29 deletions(-) rename framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/{pl.json => pl-PL.json} (92%) rename framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/{pl.json => pl-PL.json} (72%) rename framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/{pl.json => pl-PL.json} (98%) rename framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/{pl.json => pl-PL.json} (98%) rename modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/{pl.json => pl-PL.json} (98%) rename modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/{pl.json => pl-PL.json} (93%) rename modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/{pl.json => pl-PL.json} (98%) rename modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/{pl.json => pl-PL.json} (91%) rename modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/{pl.json => pl-PL.json} (98%) rename modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/{pl.json => pl-PL.json} (95%) rename modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/{pl.json => pl-PL.json} (66%) rename modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/{pl.json => pl-PL.json} (99%) rename modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/{pl.json => pl-PL.json} (91%) rename modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/{pl.json => pl-PL.json} (67%) rename modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/{pl.json => pl-PL.json} (97%) rename samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/{pl.json => pl-PL.json} (90%) create mode 100644 samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json delete mode 100644 samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json rename samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/{pl.json => pl-PL.json} (95%) create mode 100644 templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json delete mode 100644 templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl-PL.json similarity index 92% rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl.json rename to framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl-PL.json index 8509b7d02b..438d770a7c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "GivenTenantIsNotAvailable": "Podany tenant jest niedostępny: {0}", "Tenant": "Tenant", diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl.json b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl-PL.json similarity index 72% rename from framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl.json rename to framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl-PL.json index 557d01eb31..d8edcb1438 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl.json +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:Administration": "Administracja" } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json similarity index 98% rename from framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json rename to framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json index cdaf556168..1be12a51cf 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "InternalServerErrorMessage": "Błąd wewnętrzny serwera podczas przetwarzania żądania!", "ValidationErrorMessage": "Twoje żądanie jest niepoprawnie!", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json similarity index 98% rename from framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl.json rename to framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json index eef572eb13..93145f0a3a 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "'{0}' and '{1}' do not match.": "'{0}' i '{1}' nie są takie same.", "The {0} field is not a valid credit card number.": "Pole {0} nie jest poprawnym numerem karty kredytowej.", diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl-PL.json similarity index 98% rename from modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl.json rename to modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl-PL.json index b661af8050..032ac41cfa 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "UserName": "Nazwa użytkownika", "EmailAddress": "Adres email", diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json similarity index 93% rename from modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl.json rename to modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json index 8806854643..05ba8451c6 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl.json +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permission:Blogging": "Blog", "Permission:Blogs": "Blogi", diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json similarity index 98% rename from modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl.json rename to modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json index 15e0c7eb0d..3795ab93d3 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:Blogs": "Blogi", "Menu:BlogManagement": "Zarządzanie blogiem", diff --git a/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl.json b/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl-PL.json similarity index 91% rename from modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl.json rename to modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl-PL.json index f131f9e139..ca06e49ef5 100644 --- a/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl.json +++ b/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "WelcomeVoloDocs": "Witaj w VoloDocs!", "NoProjectWarning": "Nie ma jeszcze zdefiniowanego projektu!", diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl-PL.json similarity index 98% rename from modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl.json rename to modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl-PL.json index be91b22800..80922afc71 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl.json +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permission:DocumentManagement": "Zarządzanie dokumentacją", "Permission:Projects": "Projekty", diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl-PL.json similarity index 95% rename from modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl.json rename to modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl-PL.json index 5ba3dd6c10..44bd398458 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Documents": "Dokumenty", "BackToWebsite": "Powrót do strony", diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json similarity index 66% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl.json rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json index d714a0d499..86d5f08258 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Features": "Funkcje" } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json similarity index 99% rename from modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl.json rename to modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json index 3a0896147c..54d137988d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:IdentityManagement": "Zarządzanie tożsamością", "Users": "Użytkownicy", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json similarity index 91% rename from modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl.json rename to modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index e1043feea7..b552012c1b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permissions": "Uprawnienia", "OnlyProviderPermissons": "Tylko ten dostawca", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json similarity index 67% rename from modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl.json rename to modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json index 4aa474d82b..6c29a15453 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Settings": "Ustawienia" } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json similarity index 97% rename from modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl.json rename to modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json index 18891c3633..be3af86d22 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:TenantManagement": "Zarządzanie tenantami", "Tenants": "Tenanty", diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl-PL.json similarity index 90% rename from samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl.json rename to samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl-PL.json index 9181e35186..e8629599a2 100644 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl.json +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permission:ProductManagement": "Zarządzanie produktami", "Permission:Products": "Produkty", diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json new file mode 100644 index 0000000000..3ea7b190ee --- /dev/null +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json @@ -0,0 +1,6 @@ +{ + "culture": "pl-PL", + "texts": { + + } +} \ No newline at end of file diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json deleted file mode 100644 index 2ea227cbf3..0000000000 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pl", - "texts": { - - } -} \ No newline at end of file diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl-PL.json similarity index 95% rename from samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl.json rename to samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl-PL.json index 5828dd8942..929b543df8 100644 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl.json +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:ProductManagement": "Zarządzanie produktami", "Menu:Products": "Produkty", diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json new file mode 100644 index 0000000000..3ea7b190ee --- /dev/null +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json @@ -0,0 +1,6 @@ +{ + "culture": "pl-PL", + "texts": { + + } +} \ No newline at end of file diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json deleted file mode 100644 index 2ea227cbf3..0000000000 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pl", - "texts": { - - } -} \ No newline at end of file From 54268c2834592d9d243eacb6fedd818fe6bcef05 Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Wed, 22 Apr 2020 08:36:15 +0200 Subject: [PATCH 04/52] change pl-PL for tests projects. --- .../Mvc/Localization/Resource/{pl.json => pl-PL.json} | 2 +- .../Volo/Abp/Emailing/Localization/{pl.json => pl-PL.json} | 2 +- .../TestResources/Base/CountryNames/{pl.json => pl-PL.json} | 2 +- .../TestResources/Base/Validation/{pl.json => pl-PL.json} | 2 +- .../Localization/TestResources/Source/{pl.json => pl-PL.json} | 2 +- .../TestResources/SourceExt/{pl.json => pl-PL.json} | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/{pl.json => pl-PL.json} (79%) rename framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/{pl.json => pl-PL.json} (63%) rename framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/{pl.json => pl-PL.json} (79%) rename framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/{pl.json => pl-PL.json} (86%) rename framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/{pl.json => pl-PL.json} (92%) rename framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/{pl.json => pl-PL.json} (69%) diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl-PL.json similarity index 79% rename from framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl.json rename to framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl-PL.json index c794c9c123..5370ccdf57 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "BirthDate": "Data urodzenia", "Value1": "Wartość jeden" diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl-PL.json similarity index 63% rename from framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json rename to framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl-PL.json index 6dd6654aa1..3b4ee39a1e 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "hello": "witaj" } diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl-PL.json similarity index 79% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl-PL.json index 5e343cf2f9..a25b79d6d8 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "USA": "Stany Zjednoczone Ameryki", "Brazil": "Brazylia" diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl-PL.json similarity index 86% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl-PL.json index 1d0bfdf66a..cce4cfd722 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "ThisFieldIsRequired": "To pole jest wymagane", "MaxLenghtErrorMessage": "To pole może mieć maksymalnie '{0}' znaków" diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl-PL.json similarity index 92% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl-PL.json index d014bc7b30..e208fb12b3 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Hello {0}.": "Witaj {0}.", "Car": "Samochód", diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl-PL.json similarity index 69% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl-PL.json index 2935790010..5b754a7ae1 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "SeeYou": "Do zobaczenia" } From ca7a084c512b06ca19269ba7b1c26670851eaac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 18:36:57 +0300 Subject: [PATCH 05/52] Created Volo.Abp.TextTemplating package and copied initial files. --- framework/Volo.Abp.sln | 9 ++++- .../Volo.Abp.TextTemplating/FodyWeavers.xml | 3 ++ .../Volo.Abp.TextTemplating/FodyWeavers.xsd | 30 ++++++++++++++++ .../Volo.Abp.TextTemplating.csproj | 21 +++++++++++ .../TextTemplating/AbpTextTemplatingModule.cs | 9 +++++ .../EmailTemplateContributorList.cs | 22 ++++++++++++ .../TextTemplating/EmailTemplateDefinition.cs | 36 +++++++++++++++++++ .../EmailTemplateInitializationContext.cs | 18 ++++++++++ .../IEmailTemplateContributor.cs | 9 +++++ nupkg/common.ps1 | 1 + 10 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml create mode 100644 framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 3db0998855..97a831752c 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -277,7 +277,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.Identi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending", "src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj", "{D1815C77-16D6-4F99-8814-69065CD89FB3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ObjectExtending.Tests", "test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj", "{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending.Tests", "test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj", "{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating", "src\Volo.Abp.TextTemplating\Volo.Abp.TextTemplating.csproj", "{9E53F91F-EACD-4191-A487-E727741F1311}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -829,6 +831,10 @@ Global {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Debug|Any CPU.Build.0 = Debug|Any CPU {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.ActiveCfg = Release|Any CPU {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.Build.0 = Release|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -970,6 +976,7 @@ Global {E1963439-2BE5-4DB5-8438-2A9A792A1ADA} = {447C8A77-E5F0-4538-8687-7383196D04EA} {D1815C77-16D6-4F99-8814-69065CD89FB3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {9E53F91F-EACD-4191-A487-E727741F1311} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj new file mode 100644 index 0000000000..cbc6ba05ec --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + Volo.Abp.TextTemplating + Volo.Abp.TextTemplating + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs new file mode 100644 index 0000000000..6b0ad6fb11 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs new file mode 100644 index 0000000000..44a91212ea --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateContributorList : List + { + public string GetOrNull(string cultureName) + { + foreach (var contributor in this.AsQueryable().Reverse()) + { + var templateString = contributor.GetOrNull(cultureName); + if (templateString != null) + { + return templateString; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs new file mode 100644 index 0000000000..19f0eb2fa7 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs @@ -0,0 +1,36 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateDefinition + { + public const string DefaultLayoutPlaceHolder = "_"; + + public string Name { get; } + + public bool IsLayout { get; } + + public string Layout { get; set; } + + public Type LocalizationResource { get; set; } + + public EmailTemplateContributorList Contributors { get; } + + public string DefaultCultureName { get; } + + public bool SingleTemplateFile { get; } + + public EmailTemplateDefinition([NotNull] string name, Type localizationResource = null, bool isLayout = false, + string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null, bool singleTemplateFile = false) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + LocalizationResource = localizationResource; + Contributors = new EmailTemplateContributorList(); + IsLayout = isLayout; + Layout = layout; + DefaultCultureName = defaultCultureName; + SingleTemplateFile = singleTemplateFile; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs new file mode 100644 index 0000000000..8cd4b95bbd --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs @@ -0,0 +1,18 @@ +using System; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateInitializationContext + { + public EmailTemplateDefinition EmailTemplateDefinition { get; } + + public IServiceProvider ServiceProvider { get; } + + public EmailTemplateInitializationContext(EmailTemplateDefinition emailTemplateDefinition, + IServiceProvider serviceProvider) + { + EmailTemplateDefinition = emailTemplateDefinition; + ServiceProvider = serviceProvider; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs new file mode 100644 index 0000000000..d2c2775845 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Emailing.Templates +{ + public interface IEmailTemplateContributor + { + void Initialize(EmailTemplateInitializationContext context); + + string GetOrNull(string cultureName); + } +} \ No newline at end of file diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index f9e1263908..9c77b05eef 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -101,6 +101,7 @@ $projects = ( "framework/src/Volo.Abp.Sms", "framework/src/Volo.Abp.Specifications", "framework/src/Volo.Abp.TestBase", + "framework/src/Volo.Abp.TextTemplating", "framework/src/Volo.Abp.Threading", "framework/src/Volo.Abp.Timing", "framework/src/Volo.Abp.UI", From af77ec2aee9f5a53d7f52ed11471bcc8693e48d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 18:53:20 +0300 Subject: [PATCH 06/52] Refactored and added test project --- framework/Volo.Abp.sln | 7 +++ ...pleVirtualFilesEmailTemplateContributor.cs | 2 +- .../TextTemplating/EmailTemplateDefinition.cs | 36 --------------- .../EmailTemplateInitializationContext.cs | 18 -------- .../IEmailTemplateContributor.cs | 9 ---- .../TextTemplating/ITemplateContributor.cs | 9 ++++ ...emplateContributorInitializationContext.cs | 22 ++++++++++ ...utorList.cs => TemplateContributorList.cs} | 4 +- .../Abp/TextTemplating/TemplateDefinition.cs | 44 +++++++++++++++++++ .../Volo.Abp.TextTemplating.Tests.csproj | 16 +++++++ .../AbpTextTemplatingTestModule.cs | 9 ++++ 11 files changed, 110 insertions(+), 66 deletions(-) delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/{EmailTemplateContributorList.cs => TemplateContributorList.cs} (79%) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 97a831752c..5159f0a86e 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -281,6 +281,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending.Te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating", "src\Volo.Abp.TextTemplating\Volo.Abp.TextTemplating.csproj", "{9E53F91F-EACD-4191-A487-E727741F1311}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating.Tests", "test\Volo.Abp.TextTemplating.Tests\Volo.Abp.TextTemplating.Tests.csproj", "{251C7FD3-D313-4BCE-8068-352EC7EEA275}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -835,6 +837,10 @@ Global {9E53F91F-EACD-4191-A487-E727741F1311}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.Build.0 = Release|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Debug|Any CPU.Build.0 = Debug|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Release|Any CPU.ActiveCfg = Release|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -977,6 +983,7 @@ Global {D1815C77-16D6-4F99-8814-69065CD89FB3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5} = {447C8A77-E5F0-4538-8687-7383196D04EA} {9E53F91F-EACD-4191-A487-E727741F1311} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {251C7FD3-D313-4BCE-8068-352EC7EEA275} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs index 2ae5f88cc5..39af512350 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs @@ -46,7 +46,7 @@ namespace Volo.Abp.Emailing.Templates.VirtualFiles { return dictionaries; } - + _templateDictionary = new Dictionary(); foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs deleted file mode 100644 index 19f0eb2fa7..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinition - { - public const string DefaultLayoutPlaceHolder = "_"; - - public string Name { get; } - - public bool IsLayout { get; } - - public string Layout { get; set; } - - public Type LocalizationResource { get; set; } - - public EmailTemplateContributorList Contributors { get; } - - public string DefaultCultureName { get; } - - public bool SingleTemplateFile { get; } - - public EmailTemplateDefinition([NotNull] string name, Type localizationResource = null, bool isLayout = false, - string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null, bool singleTemplateFile = false) - { - Name = Check.NotNullOrWhiteSpace(name, nameof(name)); - LocalizationResource = localizationResource; - Contributors = new EmailTemplateContributorList(); - IsLayout = isLayout; - Layout = layout; - DefaultCultureName = defaultCultureName; - SingleTemplateFile = singleTemplateFile; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs deleted file mode 100644 index 8cd4b95bbd..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateInitializationContext - { - public EmailTemplateDefinition EmailTemplateDefinition { get; } - - public IServiceProvider ServiceProvider { get; } - - public EmailTemplateInitializationContext(EmailTemplateDefinition emailTemplateDefinition, - IServiceProvider serviceProvider) - { - EmailTemplateDefinition = emailTemplateDefinition; - ServiceProvider = serviceProvider; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs deleted file mode 100644 index d2c2775845..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateContributor - { - void Initialize(EmailTemplateInitializationContext context); - - string GetOrNull(string cultureName); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs new file mode 100644 index 0000000000..c1a7eb95cd --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateContributor + { + void Initialize(TemplateContributorInitializationContext context); + + string GetOrNull(string cultureName); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs new file mode 100644 index 0000000000..e8a7332e83 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs @@ -0,0 +1,22 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateContributorInitializationContext + { + [NotNull] + public TemplateDefinition TemplateDefinition { get; } + + [NotNull] + public IServiceProvider ServiceProvider { get; } + + public TemplateContributorInitializationContext( + TemplateDefinition templateDefinition, + IServiceProvider serviceProvider) + { + TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); + ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs similarity index 79% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs index 44a91212ea..50fdb684df 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using System.Linq; -namespace Volo.Abp.Emailing.Templates +namespace Volo.Abp.TextTemplating { - public class EmailTemplateContributorList : List + public class TemplateContributorList : List { public string GetOrNull(string cultureName) { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs new file mode 100644 index 0000000000..b9b7762405 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -0,0 +1,44 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinition + { + public const string DefaultLayoutPlaceHolder = "_"; + + [NotNull] + public string Name { get; } + + public bool IsLayout { get; } + + [CanBeNull] + public string Layout { get; set; } + + public Type LocalizationResource { get; set; } //TODO: ??? + + public TemplateContributorList Contributors { get; } + + [CanBeNull] + public string DefaultCultureName { get; } + + public bool SingleTemplateFile { get; } //TODO: ??? + + public TemplateDefinition( + [NotNull] string name, + Type localizationResource = null, + bool isLayout = false, + string layout = DefaultLayoutPlaceHolder, + string defaultCultureName = null, + bool singleTemplateFile = false) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + LocalizationResource = localizationResource; + Contributors = new TemplateContributorList(); + IsLayout = isLayout; + Layout = layout; + DefaultCultureName = defaultCultureName; + SingleTemplateFile = singleTemplateFile; + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj new file mode 100644 index 0000000000..56e93ae650 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -0,0 +1,16 @@ + + + + + + netcoreapp3.1 + + + + + + + + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs new file mode 100644 index 0000000000..280059a40e --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingTestModule : AbpModule + { + + } +} From 095bb128a0502e35a69ba9aa8750565fb0ea305b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 19:30:27 +0300 Subject: [PATCH 07/52] Added template definition system and tests. --- .../Volo/Abp/Emailing/AbpEmailingModule.cs | 1 - .../TextTemplating/AbpTextTemplatingModule.cs | 26 ++++++- .../AbpTextTemplatingOptions.cs | 14 ++++ .../ITemplateDefinitionContext.cs | 9 +++ .../ITemplateDefinitionManager.cs | 15 ++++ .../ITemplateDefinitionProvider.cs | 7 ++ .../TemplateDefinitionContext.cs | 38 ++++++++++ .../TemplateDefinitionManager.cs | 74 +++++++++++++++++++ .../TemplateDefinitionProvider.cs | 9 +++ .../Volo.Abp.TextTemplating.Tests.csproj | 3 +- .../AbpTextTemplatingOptions_Tests.cs | 24 ++++++ .../AbpTextTemplatingTestBase.cs | 12 +++ .../AbpTextTemplatingTestModule.cs | 8 +- .../TextTemplating/TemplateDefinitionTests.cs | 22 ++++++ .../TestTemplateDefinitionProvider.cs | 14 ++++ .../Volo/Abp/TextTemplating/TestTemplates.cs | 7 ++ 16 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs index c2e29a7ff7..9641f7c14c 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs @@ -50,7 +50,6 @@ namespace Volo.Abp.Emailing services.OnRegistred(context => { - if (typeof(IEmailTemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) { definitionProviders.Add(context.ImplementationType); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index 6b0ad6fb11..28b71da4aa 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -1,9 +1,33 @@ -using Volo.Abp.Modularity; +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; namespace Volo.Abp.TextTemplating { public class AbpTextTemplatingModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + AutoAddDefinitionProviders(context.Services); + } + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(ITemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs new file mode 100644 index 0000000000..a79b66e255 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Collections; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingOptions + { + public ITypeList DefinitionProviders { get; } + + public AbpTextTemplatingOptions() + { + DefinitionProviders = new TypeList(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs new file mode 100644 index 0000000000..18402f8f9d --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateDefinitionContext + { + TemplateDefinition GetOrNull(string name); + + void Add(params TemplateDefinition[] definitions); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs new file mode 100644 index 0000000000..feadd3442f --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateDefinitionManager + { + [NotNull] + TemplateDefinition Get([NotNull] string name); + + IReadOnlyList GetAll(); + + TemplateDefinition GetOrNull(string name); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs new file mode 100644 index 0000000000..a9e5155d0c --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateDefinitionProvider + { + void Define(ITemplateDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs new file mode 100644 index 0000000000..39da2f5e88 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinitionContext : ITemplateDefinitionContext + { + protected Dictionary EmailTemplates { get; } + + public TemplateDefinitionContext(Dictionary emailTemplates) + { + EmailTemplates = emailTemplates; + } + + public virtual TemplateDefinition GetOrNull(string name) + { + return EmailTemplates.GetOrDefault(name); + } + + public virtual IReadOnlyList GetAll() + { + return EmailTemplates.Values.ToImmutableList(); + } + + public virtual void Add(params TemplateDefinition[] definitions) + { + if (definitions.IsNullOrEmpty()) + { + return; + } + + foreach (var definition in definitions) + { + EmailTemplates[definition.Name] = definition; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs new file mode 100644 index 0000000000..534010dbe2 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinitionManager : ITemplateDefinitionManager, ISingletonDependency + { + protected Lazy> EmailTemplateDefinitions { get; } + + protected AbpTextTemplatingOptions Options { get; } + + protected IServiceProvider ServiceProvider { get; } + + public TemplateDefinitionManager( + IOptions options, + IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + Options = options.Value; + + EmailTemplateDefinitions = + new Lazy>(CreateEmailTemplateDefinitions, true); + } + + public virtual TemplateDefinition Get(string name) + { + Check.NotNull(name, nameof(name)); + + var template = GetOrNull(name); + + if (template == null) + { + throw new AbpException("Undefined template: " + name); + } + + return template; + } + + public virtual IReadOnlyList GetAll() + { + return EmailTemplateDefinitions.Value.Values.ToImmutableList(); + } + + public virtual TemplateDefinition GetOrNull(string name) + { + return EmailTemplateDefinitions.Value.GetOrDefault(name); + } + + protected virtual IDictionary CreateEmailTemplateDefinitions() + { + var templates = new Dictionary(); + + using (var scope = ServiceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider.Define(new TemplateDefinitionContext(templates)); + } + } + + return templates; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs new file mode 100644 index 0000000000..9afc22a8b8 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs @@ -0,0 +1,9 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating +{ + public abstract class TemplateDefinitionProvider : ITemplateDefinitionProvider, ITransientDependency + { + public abstract void Define(ITemplateDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj index 56e93ae650..bc5a0fc679 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -10,6 +10,7 @@ + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs new file mode 100644 index 0000000000..294596c693 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingOptions_Tests : AbpTextTemplatingTestBase + { + private readonly AbpTextTemplatingOptions _options; + + public AbpTextTemplatingOptions_Tests() + { + _options = GetRequiredService>().Value; + } + + [Fact] + public void Should_Auto_Add_TemplateDefinitionProviders_To_Options() + { + _options + .DefinitionProviders + .ShouldContain(typeof(TestTemplateDefinitionProvider)); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs new file mode 100644 index 0000000000..ca5dc20445 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.TextTemplating +{ + public abstract class AbpTextTemplatingTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs index 280059a40e..057accfc23 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -1,7 +1,13 @@ -using Volo.Abp.Modularity; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; namespace Volo.Abp.TextTemplating { + [DependsOn( + typeof(AbpTextTemplatingModule), + typeof(AbpTestBaseModule), + typeof(AbpAutofacModule) + )] public class AbpTextTemplatingTestModule : AbpModule { diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs new file mode 100644 index 0000000000..2202c098b1 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -0,0 +1,22 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinitionTests : AbpTextTemplatingTestBase + { + private readonly ITemplateDefinitionManager _templateDefinitionManager; + + public TemplateDefinitionTests() + { + _templateDefinitionManager = GetRequiredService(); + } + + [Fact] + public void Should_Retrieve_Template_Definition() + { + var definition = _templateDefinitionManager.Get(TestTemplates.TestTemplate1); + definition.Name.ShouldBe(TestTemplates.TestTemplate1); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs new file mode 100644 index 0000000000..1ff8be9bab --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.TextTemplating +{ + public class TestTemplateDefinitionProvider : TemplateDefinitionProvider + { + public override void Define(ITemplateDefinitionContext context) + { + context + .Add(new TemplateDefinition( + TestTemplates.TestTemplate1 + ) + ); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs new file mode 100644 index 0000000000..8e6e21ea85 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.TextTemplating +{ + public static class TestTemplates + { + public const string TestTemplate1 = "TestTemplate1"; + } +} \ No newline at end of file From 42047737bd002b1fbed40841b95429170a75808b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 22:23:09 +0300 Subject: [PATCH 08/52] Added more tests for template definitions. --- common.DotSettings | 6 ++++++ .../TextTemplating/ITemplateDefinitionManager.cs | 2 ++ .../TextTemplating/TemplateDefinitionTests.cs | 16 +++++++++++++++- .../TestTemplateDefinitionProvider.cs | 5 ++++- .../Volo/Abp/TextTemplating/TestTemplates.cs | 1 + 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/common.DotSettings b/common.DotSettings index 0eb4875d49..6f40d029a7 100644 --- a/common.DotSettings +++ b/common.DotSettings @@ -20,5 +20,11 @@ False False SQL + False + False + False + False + False + False True \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs index feadd3442f..cbd2d15463 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs @@ -8,8 +8,10 @@ namespace Volo.Abp.TextTemplating [NotNull] TemplateDefinition Get([NotNull] string name); + [NotNull] IReadOnlyList GetAll(); + [CanBeNull] TemplateDefinition GetOrNull(string name); } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs index 2202c098b1..bc0e8b94c4 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -13,10 +13,24 @@ namespace Volo.Abp.TextTemplating } [Fact] - public void Should_Retrieve_Template_Definition() + public void Should_Retrieve_Template_Definition_By_Name() { var definition = _templateDefinitionManager.Get(TestTemplates.TestTemplate1); definition.Name.ShouldBe(TestTemplates.TestTemplate1); } + + [Fact] + public void Should_Get_Null_If_Template_Not_Found() + { + var definition = _templateDefinitionManager.GetOrNull("undefined-template"); + definition.ShouldBeNull(); + } + + [Fact] + public void Should_Retrieve_All_Template_Definitions() + { + var definitions = _templateDefinitionManager.GetAll(); + definitions.Count.ShouldBeGreaterThan(1); + } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 1ff8be9bab..134ebdc74a 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -5,8 +5,11 @@ public override void Define(ITemplateDefinitionContext context) { context - .Add(new TemplateDefinition( + .Add( + new TemplateDefinition( TestTemplates.TestTemplate1 + ), new TemplateDefinition( + TestTemplates.TestTemplateLayout1 ) ); } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs index 8e6e21ea85..e5b7bcc9ae 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs @@ -3,5 +3,6 @@ public static class TestTemplates { public const string TestTemplate1 = "TestTemplate1"; + public const string TestTemplateLayout1 = "TestTemplateLayout1"; } } \ No newline at end of file From 07aaaa046951f2b9614d9eb8d60a0e798d5feb90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 23:08:57 +0300 Subject: [PATCH 09/52] Implemented VirtualFileTemplateContributor and added tests. --- .../Volo.Abp.TextTemplating.csproj | 4 +- .../TextTemplating/AbpTextTemplatingModule.cs | 4 + ...emplateContributorInitializationContext.cs | 4 +- .../Abp/TextTemplating/TemplateDefinition.cs | 12 +-- .../VirtualFileTemplateContributor.cs | 84 +++++++++++++++++++ .../Volo.Abp.TextTemplating.Tests.csproj | 5 ++ .../AbpTextTemplatingTestModule.cs | 9 +- .../SampleTemplates/ForgotPasswordEmail.tpl | 1 + .../SampleTemplates/WelcomeEmail/en.tpl | 1 + .../SampleTemplates/WelcomeEmail/tr.tpl | 1 + .../TestTemplateDefinitionProvider.cs | 23 +++-- .../VirtualFileTemplateContributor_Tests.cs | 50 +++++++++++ 12 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj index cbc6ba05ec..ba12b60ca7 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -1,4 +1,4 @@ - + @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index 28b71da4aa..3ef30f5fc2 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -2,9 +2,13 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating { + [DependsOn( + typeof(AbpVirtualFileSystemModule) + )] public class AbpTextTemplatingModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs index e8a7332e83..ffe97a3484 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs @@ -12,8 +12,8 @@ namespace Volo.Abp.TextTemplating public IServiceProvider ServiceProvider { get; } public TemplateContributorInitializationContext( - TemplateDefinition templateDefinition, - IServiceProvider serviceProvider) + [NotNull] TemplateDefinition templateDefinition, + [NotNull] IServiceProvider serviceProvider) { TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index b9b7762405..548ac87c15 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -22,15 +22,12 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public string DefaultCultureName { get; } - public bool SingleTemplateFile { get; } //TODO: ??? - public TemplateDefinition( [NotNull] string name, Type localizationResource = null, bool isLayout = false, string layout = DefaultLayoutPlaceHolder, - string defaultCultureName = null, - bool singleTemplateFile = false) + string defaultCultureName = null) { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); LocalizationResource = localizationResource; @@ -38,7 +35,12 @@ namespace Volo.Abp.TextTemplating IsLayout = isLayout; Layout = layout; DefaultCultureName = defaultCultureName; - SingleTemplateFile = singleTemplateFile; + } + + public virtual TemplateDefinition AddContributor(ITemplateContributor contributor) + { + Contributors.Add(contributor); + return this; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs new file mode 100644 index 0000000000..f7a320ab16 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class VirtualFileTemplateContributor : ITemplateContributor + { + private readonly string _virtualPath; + private IVirtualFileProvider _virtualFileProvider; + private volatile Dictionary _templateDictionary; + private readonly object _syncObj = new object(); + + public VirtualFileTemplateContributor(string virtualPath) + { + _virtualPath = virtualPath; + } + + public void Initialize(TemplateContributorInitializationContext context) + { + _virtualFileProvider = context.ServiceProvider.GetRequiredService(); + } + + public string GetOrNull([CanBeNull] string cultureName = null) + { + var dictionary = GetTemplateDictionary(); + + if (cultureName == null) + { + return dictionary.GetOrDefault("__default"); + } + else + { + return dictionary.GetOrDefault(cultureName) ?? + dictionary.GetOrDefault("__default"); + } + } + + private Dictionary GetTemplateDictionary() + { + if (_templateDictionary != null) + { + return _templateDictionary; + } + + lock (_syncObj) + { + if (_templateDictionary != null) + { + return _templateDictionary; + } + + var dictionary = new Dictionary(); + + var fileInfo = _virtualFileProvider.GetFileInfo(_virtualPath); + if (!fileInfo.IsDirectory) + { + //TODO: __default to consts + dictionary.Add("__default", fileInfo.ReadAsString()); + } + else + { + foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) + { + if (file.IsDirectory) + { + continue; + } + + // TODO: How to normalize file names? + dictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); + } + } + + _templateDictionary = dictionary; + return dictionary; + } + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj index bc5a0fc679..916495b116 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -7,6 +7,11 @@ + + + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs index 057accfc23..0457e0136d 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -1,5 +1,6 @@ using Volo.Abp.Autofac; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating { @@ -10,6 +11,12 @@ namespace Volo.Abp.TextTemplating )] public class AbpTextTemplatingTestModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded("Volo.Abp.TextTemplating"); + }); + } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl new file mode 100644 index 0000000000..674b734c8a --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl @@ -0,0 +1 @@ +Please click to the following link to get an email to reset your password! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl new file mode 100644 index 0000000000..7dbe5e8cb0 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl @@ -0,0 +1 @@ +Welcome to the abp.io! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl new file mode 100644 index 0000000000..6c70e0afb1 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl @@ -0,0 +1 @@ +abp.io'ya hoşgeldiniz! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 134ebdc74a..57244c61c6 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -1,17 +1,22 @@ -namespace Volo.Abp.TextTemplating +using Volo.Abp.TextTemplating.VirtualFiles; + +namespace Volo.Abp.TextTemplating { public class TestTemplateDefinitionProvider : TemplateDefinitionProvider { public override void Define(ITemplateDefinitionContext context) { - context - .Add( - new TemplateDefinition( - TestTemplates.TestTemplate1 - ), new TemplateDefinition( - TestTemplates.TestTemplateLayout1 - ) - ); + context.Add( + new TemplateDefinition( + TestTemplates.TestTemplate1 + ).AddContributor( + new VirtualFileTemplateContributor("/SampleTemplates/WelcomeEmail") + ) + ); + + context.Add(new TemplateDefinition( + TestTemplates.TestTemplateLayout1 + )); } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs new file mode 100644 index 0000000000..d884a5e73b --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -0,0 +1,50 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase + { + [Fact] + public void Should_Get_Localized_Content_By_Culture() + { + var contributor = new VirtualFileTemplateContributor( + "/SampleTemplates/WelcomeEmail" + ); + + contributor.Initialize( + new TemplateContributorInitializationContext( + new TemplateDefinition("Test"), + ServiceProvider + ) + ); + + contributor + .GetOrNull("en") + .ShouldBe("Welcome to the abp.io!"); + + contributor + .GetOrNull("tr") + .ShouldBe("abp.io'ya hoşgeldiniz!"); + } + + [Fact] + public void Should_Get_Non_Localized_Template_Content() + { + var contributor = new VirtualFileTemplateContributor( + "/SampleTemplates/ForgotPasswordEmail.tpl" + ); + + contributor.Initialize( + new TemplateContributorInitializationContext( + new TemplateDefinition("Test"), + ServiceProvider + ) + ); + + contributor + .GetOrNull() + .ShouldBe("Please click to the following link to get an email to reset your password!"); + } + } +} From 9060b695ea6c06682a8d11c390463cd84b40423b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 23:18:12 +0300 Subject: [PATCH 10/52] Added TemplateDefinitionExtensions. --- .../TemplateDefinitionExtensions.cs | 19 +++++++++++++++++++ .../VirtualFileTemplateContributor.cs | 6 +++--- .../TestTemplateDefinitionProvider.cs | 8 ++------ 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs new file mode 100644 index 0000000000..6508295da7 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using Volo.Abp.TextTemplating.VirtualFiles; + +namespace Volo.Abp.TextTemplating +{ + public static class TemplateDefinitionExtensions + { + public static TemplateDefinition AddVirtualFiles( + [NotNull] this TemplateDefinition templateDefinition, + [NotNull] string virtualPath) + { + Check.NotNull(templateDefinition, nameof(templateDefinition)); + + return templateDefinition.AddContributor( + new VirtualFileTemplateContributor(virtualPath) + ); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs index f7a320ab16..869a9cb7b1 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -15,9 +14,10 @@ namespace Volo.Abp.TextTemplating.VirtualFiles private volatile Dictionary _templateDictionary; private readonly object _syncObj = new object(); - public VirtualFileTemplateContributor(string virtualPath) + public VirtualFileTemplateContributor( + [NotNull] string virtualPath) { - _virtualPath = virtualPath; + _virtualPath = Check.NotNullOrWhiteSpace(virtualPath, nameof(virtualPath)); } public void Initialize(TemplateContributorInitializationContext context) diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 57244c61c6..f47d31ac4e 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -1,6 +1,4 @@ -using Volo.Abp.TextTemplating.VirtualFiles; - -namespace Volo.Abp.TextTemplating +namespace Volo.Abp.TextTemplating { public class TestTemplateDefinitionProvider : TemplateDefinitionProvider { @@ -9,9 +7,7 @@ namespace Volo.Abp.TextTemplating context.Add( new TemplateDefinition( TestTemplates.TestTemplate1 - ).AddContributor( - new VirtualFileTemplateContributor("/SampleTemplates/WelcomeEmail") - ) + ).AddVirtualFiles("/SampleTemplates/WelcomeEmail") ); context.Add(new TemplateDefinition( From 67dbc9b453cdaf5564e24ff44117d8d991648851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 15:06:20 +0300 Subject: [PATCH 11/52] Added template content provider and template renderer. --- .../TextTemplating/AbpTextTemplatingModule.cs | 23 ++++++++++++ .../ITemplateContentProvider.cs | 9 +++++ .../TextTemplating/ITemplateContributor.cs | 6 ++-- .../Abp/TextTemplating/ITemplateRenderer.cs | 14 ++++++++ .../TextTemplating/TemplateContentProvider.cs | 35 +++++++++++++++++++ .../TextTemplating/TemplateContributorList.cs | 15 +------- .../Abp/TextTemplating/TemplateRenderer.cs | 30 ++++++++++++++++ .../TextTemplating/TemplateDefinitionTests.cs | 4 +-- .../TextTemplating/TemplateRenderer_Tests.cs | 23 ++++++++++++ .../TestTemplateDefinitionProvider.cs | 8 ++++- .../Volo/Abp/TextTemplating/TestTemplates.cs | 3 +- 11 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index 3ef30f5fc2..e49f271c1c 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -33,5 +33,28 @@ namespace Volo.Abp.TextTemplating options.DefinitionProviders.AddIfNotContains(definitionProviders); }); } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + //TODO: Consider to move to the TemplateContentProvider and invoke lazy (with making it singleton) + using (var scope = context.ServiceProvider.CreateScope()) + { + var templateDefinitionManager = scope.ServiceProvider + .GetRequiredService(); + + foreach (var templateDefinition in templateDefinitionManager.GetAll()) + { + var contributorInitializationContext = new TemplateContributorInitializationContext( + templateDefinition, + scope.ServiceProvider + ); + + foreach (var contributor in templateDefinition.Contributors) + { + contributor.Initialize(contributorInitializationContext); + } + } + } + } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs new file mode 100644 index 0000000000..d4dc5615fb --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateContentProvider + { + Task GetContentOrNullAsync(string templateName, string cultureName); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs index c1a7eb95cd..e8e1f97769 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs @@ -1,9 +1,11 @@ -namespace Volo.Abp.TextTemplating +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating { public interface ITemplateContributor { void Initialize(TemplateContributorInitializationContext context); - string GetOrNull(string cultureName); + string GetOrNull([CanBeNull] string cultureName); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs new file mode 100644 index 0000000000..d65f77653a --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs @@ -0,0 +1,14 @@ +using System.Linq; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateRenderer + { + Task RenderAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs new file mode 100644 index 0000000000..ab81d8b9f7 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateContentProvider : ITemplateContentProvider, ITransientDependency + { + private readonly ITemplateDefinitionManager _templateDefinitionManager; + + public TemplateContentProvider( + ITemplateDefinitionManager templateDefinitionManager + ) + { + _templateDefinitionManager = templateDefinitionManager; + } + + public async Task GetContentOrNullAsync(string templateName, string cultureName) + { + var template = _templateDefinitionManager.Get(templateName); + + foreach (var contributor in template.Contributors) + { + var templateString = contributor.GetOrNull(cultureName); + if (templateString != null) + { + return templateString; + } + } + + throw new AbpException( + $"None of the template contributors could get the content for the template '{templateName}'" + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs index 50fdb684df..e737897dae 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs @@ -1,22 +1,9 @@ using System.Collections.Generic; -using System.Linq; namespace Volo.Abp.TextTemplating { public class TemplateContributorList : List { - public string GetOrNull(string cultureName) - { - foreach (var contributor in this.AsQueryable().Reverse()) - { - var templateString = contributor.GetOrNull(cultureName); - if (templateString != null) - { - return templateString; - } - } - - return null; - } + } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs new file mode 100644 index 0000000000..dcc36081fd --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateRenderer : ITemplateRenderer, ITransientDependency + { + private readonly ITemplateContentProvider _templateContentProvider; + + public TemplateRenderer( + ITemplateContentProvider templateContentProvider + ) + { + _templateContentProvider = templateContentProvider; + } + + public virtual async Task RenderAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null) + { + Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); + + return await _templateContentProvider.GetContentOrNullAsync( + templateName, + cultureName + ); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs index bc0e8b94c4..b711860089 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -15,8 +15,8 @@ namespace Volo.Abp.TextTemplating [Fact] public void Should_Retrieve_Template_Definition_By_Name() { - var definition = _templateDefinitionManager.Get(TestTemplates.TestTemplate1); - definition.Name.ShouldBe(TestTemplates.TestTemplate1); + var definition = _templateDefinitionManager.Get(TestTemplates.WelcomeEmail); + definition.Name.ShouldBe(TestTemplates.WelcomeEmail); } [Fact] diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs new file mode 100644 index 0000000000..e4c5481413 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateRenderer_Tests : AbpTextTemplatingTestBase + { + private readonly ITemplateRenderer _templateRenderer; + + public TemplateRenderer_Tests() + { + _templateRenderer = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_Rendered_Non_Localized_Template_Content() + { + var content = await _templateRenderer.RenderAsync(TestTemplates.ForgotPasswordEmail); + content.ShouldBe("Please click to the following link to get an email to reset your password!"); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index f47d31ac4e..d400964684 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -6,10 +6,16 @@ { context.Add( new TemplateDefinition( - TestTemplates.TestTemplate1 + TestTemplates.WelcomeEmail ).AddVirtualFiles("/SampleTemplates/WelcomeEmail") ); + context.Add( + new TemplateDefinition( + TestTemplates.ForgotPasswordEmail + ).AddVirtualFiles("/SampleTemplates/ForgotPasswordEmail.tpl") + ); + context.Add(new TemplateDefinition( TestTemplates.TestTemplateLayout1 )); diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs index e5b7bcc9ae..a2b605c213 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs @@ -2,7 +2,8 @@ { public static class TestTemplates { - public const string TestTemplate1 = "TestTemplate1"; + public const string WelcomeEmail = "WelcomeEmail"; + public const string ForgotPasswordEmail = "ForgotPasswordEmail"; public const string TestTemplateLayout1 = "TestTemplateLayout1"; } } \ No newline at end of file From 24365e49797a109921dee0eed4938c57e5baa9a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 15:11:19 +0300 Subject: [PATCH 12/52] Use Scriban on TemplateRenderer --- .../Volo.Abp.TextTemplating.csproj | 4 ++++ .../Volo/Abp/TextTemplating/ITemplateRenderer.cs | 1 + .../Volo/Abp/TextTemplating/TemplateRenderer.cs | 11 ++++++++--- .../Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs | 4 +++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj index ba12b60ca7..404259ae17 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -14,6 +14,10 @@ + + + + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs index d65f77653a..92a1c0782c 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs @@ -8,6 +8,7 @@ namespace Volo.Abp.TextTemplating { Task RenderAsync( [NotNull] string templateName, + [CanBeNull] object model = null, [CanBeNull] string cultureName = null ); } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index dcc36081fd..127e7e9872 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; +using Scriban; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating @@ -9,22 +10,26 @@ namespace Volo.Abp.TextTemplating private readonly ITemplateContentProvider _templateContentProvider; public TemplateRenderer( - ITemplateContentProvider templateContentProvider - ) + ITemplateContentProvider templateContentProvider) { _templateContentProvider = templateContentProvider; } public virtual async Task RenderAsync( [NotNull] string templateName, + [CanBeNull] object model = null, [CanBeNull] string cultureName = null) { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); - return await _templateContentProvider.GetContentOrNullAsync( + var content = await _templateContentProvider.GetContentOrNullAsync( templateName, cultureName ); + + var parsedTemplate = Template.Parse(content); + + return await parsedTemplate.RenderAsync(model); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs index e4c5481413..70e257ae9a 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -16,7 +16,9 @@ namespace Volo.Abp.TextTemplating [Fact] public async Task Should_Get_Rendered_Non_Localized_Template_Content() { - var content = await _templateRenderer.RenderAsync(TestTemplates.ForgotPasswordEmail); + var content = await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail + ); content.ShouldBe("Please click to the following link to get an email to reset your password!"); } } From 61d2512311f832e7eb8c1be12c6915a2e7b3b9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 15:43:04 +0300 Subject: [PATCH 13/52] Implemented template renderer. --- .../Volo/Abp/Localization/CultureHelper.cs | 7 ++ .../AbpDictionaryBasedStringLocalizer.cs | 11 +-- .../ITemplateContentProvider.cs | 6 +- .../TextTemplating/TemplateContentProvider.cs | 5 +- .../VirtualFileTemplateContributor.cs | 39 +++++++-- .../SampleTemplates/WelcomeEmail/en.tpl | 2 +- .../SampleTemplates/WelcomeEmail/tr.tpl | 2 +- .../TextTemplating/TemplateRenderer_Tests.cs | 81 ++++++++++++++++++- .../TestTemplateDefinitionProvider.cs | 3 +- .../VirtualFileTemplateContributor_Tests.cs | 4 +- 10 files changed, 139 insertions(+), 21 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs index 92320f737c..8f663e084e 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs @@ -29,5 +29,12 @@ namespace Volo.Abp.Localization CultureInfo.CurrentUICulture = currentUiCulture; }); } + + public static string GetBaseCultureName(string cultureName) + { + return cultureName.Contains("-") + ? cultureName.Left(cultureName.IndexOf("-", StringComparison.Ordinal)) + : cultureName; + } } } diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs index 79c164eb5f..53a2d7714e 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs @@ -104,7 +104,7 @@ namespace Volo.Abp.Localization //Try to get from same language dictionary (without country code) if (cultureName.Contains("-")) //Example: "tr-TR" (length=5) { - var strLang = Resource.Contributors.GetOrNull(GetBaseCultureName(cultureName), name); + var strLang = Resource.Contributors.GetOrNull(CultureHelper.GetBaseCultureName(cultureName), name); if (strLang != null) { return strLang; @@ -168,7 +168,7 @@ namespace Volo.Abp.Localization //Overwrite all strings from the language based on country culture if (cultureName.Contains("-")) { - Resource.Contributors.Fill(GetBaseCultureName(cultureName), allStrings); + Resource.Contributors.Fill(CultureHelper.GetBaseCultureName(cultureName), allStrings); } } @@ -178,12 +178,7 @@ namespace Volo.Abp.Localization return allStrings.Values.ToImmutableList(); } - protected virtual string GetBaseCultureName(string cultureName) - { - return cultureName.Contains("-") - ? cultureName.Left(cultureName.IndexOf("-", StringComparison.Ordinal)) - : cultureName; - } + public class CultureWrapperStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs index d4dc5615fb..526da6a381 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -1,9 +1,13 @@ using System.Threading.Tasks; +using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { public interface ITemplateContentProvider { - Task GetContentOrNullAsync(string templateName, string cultureName); + Task GetContentOrNullAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null + ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index ab81d8b9f7..1334b5b1dd 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using JetBrains.Annotations; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating @@ -14,7 +15,9 @@ namespace Volo.Abp.TextTemplating _templateDefinitionManager = templateDefinitionManager; } - public async Task GetContentOrNullAsync(string templateName, string cultureName) + public async Task GetContentOrNullAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null) { var template = _templateDefinitionManager.Get(templateName); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs index 869a9cb7b1..f1a4700da7 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -1,14 +1,18 @@ using System; using System.Collections.Generic; +using System.Globalization; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Volo.Abp.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { public class VirtualFileTemplateContributor : ITemplateContributor { + public TemplateDefinition TemplateDefinition { get; private set; } + private readonly string _virtualPath; private IVirtualFileProvider _virtualFileProvider; private volatile Dictionary _templateDictionary; @@ -23,21 +27,46 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public void Initialize(TemplateContributorInitializationContext context) { _virtualFileProvider = context.ServiceProvider.GetRequiredService(); + TemplateDefinition = context.TemplateDefinition; } public string GetOrNull([CanBeNull] string cultureName = null) { - var dictionary = GetTemplateDictionary(); + //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents if (cultureName == null) { - return dictionary.GetOrDefault("__default"); + cultureName = CultureInfo.CurrentUICulture.Name; + } + + var dictionary = GetTemplateDictionary(); + + var content = dictionary.GetOrDefault(cultureName); + if (content != null) + { + return content; + } + + if (cultureName.Contains("-")) + { + var baseCultureName = CultureHelper.GetBaseCultureName(cultureName); + content = dictionary.GetOrDefault(baseCultureName); + if (content != null) + { + return content; + } } - else + + if (TemplateDefinition.DefaultCultureName != null) { - return dictionary.GetOrDefault(cultureName) ?? - dictionary.GetOrDefault("__default"); + content = dictionary.GetOrDefault(TemplateDefinition.DefaultCultureName); + if (content != null) + { + return content; + } } + + return dictionary.GetOrDefault("__default"); } private Dictionary GetTemplateDictionary() diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl index 7dbe5e8cb0..1088362628 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl @@ -1 +1 @@ -Welcome to the abp.io! \ No newline at end of file +Welcome {{name}} to the abp.io! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl index 6c70e0afb1..b457611f1e 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl @@ -1 +1 @@ -abp.io'ya hoşgeldiniz! \ No newline at end of file +Merhaba {{name}}, abp.io'ya hoşgeldiniz! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs index 70e257ae9a..9e76d22ab8 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Shouldly; using Xunit; @@ -19,7 +20,85 @@ namespace Volo.Abp.TextTemplating var content = await _templateRenderer.RenderAsync( TestTemplates.ForgotPasswordEmail ); + content.ShouldBe("Please click to the following link to get an email to reset your password!"); } + + [Fact] + public async Task Should_Get_Rendered_Localized_Template_Content_With_Different_Cultures() + { + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + name = "John" + }, + cultureName: "en" + )).ShouldBe("Welcome John to the abp.io!"); + + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + name = "John" + }, + cultureName: "tr" + )).ShouldBe("Merhaba John, abp.io'ya hoşgeldiniz!"); + + //"en-US" fallbacks to "en" since "en-US" doesn't exists and "en" is the fallback culture + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + name = "John" + }, + cultureName: "en-US" + )).ShouldBe("Welcome John to the abp.io!"); + + //"fr" fallbacks to "en" since "fr" doesn't exists and "en" is the default culture + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + Name = "John" //Intentionally written as PascalCase since Scriban supports it + }, + cultureName: "fr" + )).ShouldBe("Welcome John to the abp.io!"); + } + + [Fact] + public async Task Should_Get_Rendered_Localized_Template_Content_With_Stronly_Typed_Model() + { + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new WelcomeEmailModel("John"), + cultureName: "en" + )).ShouldBe("Welcome John to the abp.io!"); + } + + [Fact] + public async Task Should_Get_Rendered_Localized_Template_Content_With_Dictionary_Model() + { + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new Dictionary() { { "name", "John" } }, + cultureName: "en" + )).ShouldBe("Welcome John to the abp.io!"); + } + + private class WelcomeEmailModel + { + public string Name { get; set; } + + public WelcomeEmailModel() + { + + } + + public WelcomeEmailModel(string name) + { + Name = name; + } + } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index d400964684..7a1ff4121b 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -6,7 +6,8 @@ { context.Add( new TemplateDefinition( - TestTemplates.WelcomeEmail + TestTemplates.WelcomeEmail, + defaultCultureName: "en" ).AddVirtualFiles("/SampleTemplates/WelcomeEmail") ); diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index d884a5e73b..1c636d7813 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -21,11 +21,11 @@ namespace Volo.Abp.TextTemplating.VirtualFiles contributor .GetOrNull("en") - .ShouldBe("Welcome to the abp.io!"); + .ShouldBe("Welcome {{name}} to the abp.io!"); contributor .GetOrNull("tr") - .ShouldBe("abp.io'ya hoşgeldiniz!"); + .ShouldBe("Merhaba {{name}}, abp.io'ya hoşgeldiniz!"); } [Fact] From 8fb41d9e430bbcd81bdb15c2a52aeca0cc22b6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 17:32:47 +0300 Subject: [PATCH 14/52] Delete email template system and use the text template system. --- .../Volo.Abp.Emailing.csproj | 5 +- .../Volo/Abp/Emailing/AbpEmailingModule.cs | 50 +------- .../Templates/AbpEmailTemplateOptions.cs | 18 --- .../Templates/DefaultEmailTemplateProvider.cs | 24 +++- .../DefaultEmailTemplates/Layout/en.tpl | 2 +- .../Abp/Emailing/Templates/EmailTemplate.cs | 42 ------ .../Templates/EmailTemplateContributorList.cs | 22 ---- .../Templates/EmailTemplateDefinition.cs | 36 ------ .../EmailTemplateDefinitionContext.cs | 38 ------ .../EmailTemplateDefinitionDictionary.cs | 22 ---- .../EmailTemplateDefinitionManager.cs | 74 ----------- .../EmailTemplateDefinitionProvider.cs | 9 -- .../EmailTemplateInitializationContext.cs | 18 --- .../Templates/EmailTemplateProvider.cs | 121 ------------------ .../Templates/IEmailTemplateContributor.cs | 9 -- .../IEmailTemplateDefinitionContext.cs | 9 -- .../IEmailTemplateDefinitionManager.cs | 15 --- .../IEmailTemplateDefinitionProvider.cs | 7 - .../Templates/IEmailTemplateProvider.cs | 11 -- .../Abp/Emailing/Templates/ITemplateRender.cs | 9 -- .../Abp/Emailing/Templates/TemplateRender.cs | 15 --- .../EmailTemplateDefinitionExtensions.cs | 19 --- ...pleVirtualFilesEmailTemplateContributor.cs | 68 ---------- ...ngleVirtualFileEmailTemplateContributor.cs | 34 ----- .../Abp/Localization/TemplateLocalizer.cs | 18 --- .../ITemplateContentProvider.cs | 5 + .../TextTemplating/TemplateContentProvider.cs | 16 ++- .../Abp/TextTemplating/TemplateDefinition.cs | 5 +- .../Abp/TextTemplating/TemplateRenderer.cs | 47 ++++++- .../Volo.Abp.Emailing.Tests.csproj | 7 - .../Abp/Emailing/AbpEmailingTestModule.cs | 10 -- .../Abp/Emailing/EmailTemplateRender_Tests.cs | 65 ---------- .../Abp/Emailing/EmailTemplateStore_Tests.cs | 54 -------- .../Localization/AbpEmailingTestResource.cs | 10 -- .../Volo/Abp/Emailing/Localization/cs.json | 6 - .../Volo/Abp/Emailing/Localization/en.json | 6 - .../Volo/Abp/Emailing/Localization/pl.json | 6 - .../Volo/Abp/Emailing/Localization/pt-BR.json | 6 - .../Volo/Abp/Emailing/Localization/tr.json | 6 - .../Volo/Abp/Emailing/Localization/vi.json | 6 - .../Abp/Emailing/Localization/zh-Hant.json | 6 - .../Abp/Emailing/TestEmailTemplateProvider.cs | 24 ---- .../Emailing/TestTemplates/Template1/en.tpl | 4 - .../Emailing/TestTemplates/Template1/tr.tpl | 4 - .../Emailing/TestTemplates/Template2/en.tpl | 4 - .../TestTemplates/Template3/Template.tpl | 1 - .../Localization/TemplateLocalizer_Tests.cs | 63 --------- 47 files changed, 85 insertions(+), 971 deletions(-) delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs delete mode 100644 framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl delete mode 100644 framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs diff --git a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj index be953a1f26..783d840cf0 100644 --- a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj +++ b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj @@ -24,14 +24,11 @@ - - - - + diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs index 9641f7c14c..afcbaf6aef 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.BackgroundJobs; +using Volo.Abp.BackgroundJobs; using Volo.Abp.Emailing.Localization; -using Volo.Abp.Emailing.Templates; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.Settings; +using Volo.Abp.TextTemplating; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Emailing @@ -15,15 +12,11 @@ namespace Volo.Abp.Emailing typeof(AbpSettingsModule), typeof(AbpVirtualFileSystemModule), typeof(AbpBackgroundJobsAbstractionsModule), - typeof(AbpLocalizationModule) + typeof(AbpLocalizationModule), + typeof(AbpTextTemplatingModule) )] public class AbpEmailingModule : AbpModule { - public override void PreConfigureServices(ServiceConfigurationContext context) - { - AutoAddDefinitionProviders(context.Services); - } - public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => @@ -43,40 +36,5 @@ namespace Volo.Abp.Emailing options.AddJob(); }); } - - private static void AutoAddDefinitionProviders(IServiceCollection services) - { - var definitionProviders = new List(); - - services.OnRegistred(context => - { - if (typeof(IEmailTemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) - { - definitionProviders.Add(context.ImplementationType); - } - }); - - services.Configure(options => - { - options.DefinitionProviders.AddIfNotContains(definitionProviders); - }); - } - - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - using (var scope = context.ServiceProvider.CreateScope()) - { - var emailTemplateDefinitionManager = - scope.ServiceProvider.GetRequiredService(); - - foreach (var templateDefinition in emailTemplateDefinitionManager.GetAll()) - { - foreach (var contributor in templateDefinition.Contributors) - { - contributor.Initialize(new EmailTemplateInitializationContext(templateDefinition, scope.ServiceProvider)); - } - } - } - } } } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs deleted file mode 100644 index cb5a9d370b..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Volo.Abp.Collections; - -namespace Volo.Abp.Emailing.Templates -{ - public class AbpEmailTemplateOptions - { - public string DefaultLayout { get; set; } - - public ITypeList DefinitionProviders { get; } - - public AbpEmailTemplateOptions() - { - DefaultLayout = StandardEmailTemplates.DefaultLayout; - - DefinitionProviders = new TypeList(); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs index c15012f0bf..28d1ac94c1 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs @@ -1,16 +1,26 @@ -using Volo.Abp.Emailing.Templates.VirtualFiles; +using Volo.Abp.TextTemplating; namespace Volo.Abp.Emailing.Templates { - public class DefaultEmailTemplateProvider : EmailTemplateDefinitionProvider + public class DefaultEmailTemplateProvider : TemplateDefinitionProvider { - public override void Define(IEmailTemplateDefinitionContext context) + public override void Define(ITemplateDefinitionContext context) { - context.Add(new EmailTemplateDefinition(StandardEmailTemplates.DefaultLayout, defaultCultureName: "en", isLayout: true, layout: null) - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout")); + context.Add( + new TemplateDefinition( + StandardEmailTemplates.DefaultLayout, + defaultCultureName: "en", + isLayout: true, + layout: null + ).AddVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout") + ); - context.Add(new EmailTemplateDefinition(StandardEmailTemplates.SimpleMessage, defaultCultureName: "en") - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message")); + context.Add( + new TemplateDefinition( + StandardEmailTemplates.SimpleMessage, + defaultCultureName: "en" + ).AddVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message") + ); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl index 107fbb5230..57453a027f 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl @@ -4,6 +4,6 @@ - {{#content}} + {{content}} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs deleted file mode 100644 index ad6f8c4839..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Text; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplate - { - public EmailTemplateDefinition Definition { get; } - - public string Content => ContentBuilder.ToString(); - - protected StringBuilder ContentBuilder { get; set; } - - public EmailTemplate(string content, EmailTemplateDefinition definition) - { - ContentBuilder = new StringBuilder(content); - Definition = definition; - } - - public virtual void SetLayout(EmailTemplate layoutTemplate) - { - if (!layoutTemplate.Definition.IsLayout) - { - throw new AbpException($"Given template is not a layout template: {layoutTemplate.Definition.Name}"); - } - - var newStrBuilder = new StringBuilder(layoutTemplate.Content); - newStrBuilder.Replace("{{#content}}", ContentBuilder.ToString()); - - ContentBuilder = newStrBuilder; - } - - public virtual void SetContent(string content) - { - ContentBuilder = new StringBuilder(content); - } - - public virtual void Replace(string name, string value) - { - ContentBuilder.Replace("{{" + name + "}}", value); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs deleted file mode 100644 index 44a91212ea..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateContributorList : List - { - public string GetOrNull(string cultureName) - { - foreach (var contributor in this.AsQueryable().Reverse()) - { - var templateString = contributor.GetOrNull(cultureName); - if (templateString != null) - { - return templateString; - } - } - - return null; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs deleted file mode 100644 index 19f0eb2fa7..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinition - { - public const string DefaultLayoutPlaceHolder = "_"; - - public string Name { get; } - - public bool IsLayout { get; } - - public string Layout { get; set; } - - public Type LocalizationResource { get; set; } - - public EmailTemplateContributorList Contributors { get; } - - public string DefaultCultureName { get; } - - public bool SingleTemplateFile { get; } - - public EmailTemplateDefinition([NotNull] string name, Type localizationResource = null, bool isLayout = false, - string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null, bool singleTemplateFile = false) - { - Name = Check.NotNullOrWhiteSpace(name, nameof(name)); - LocalizationResource = localizationResource; - Contributors = new EmailTemplateContributorList(); - IsLayout = isLayout; - Layout = layout; - DefaultCultureName = defaultCultureName; - SingleTemplateFile = singleTemplateFile; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs deleted file mode 100644 index 03a6c95d8b..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinitionContext : IEmailTemplateDefinitionContext - { - protected Dictionary EmailTemplates { get; } - - public EmailTemplateDefinitionContext(Dictionary emailTemplates) - { - EmailTemplates = emailTemplates; - } - - public virtual EmailTemplateDefinition GetOrNull(string name) - { - return EmailTemplates.GetOrDefault(name); - } - - public virtual IReadOnlyList GetAll() - { - return EmailTemplates.Values.ToImmutableList(); - } - - public virtual void Add(params EmailTemplateDefinition[] definitions) - { - if (definitions.IsNullOrEmpty()) - { - return; - } - - foreach (var definition in definitions) - { - EmailTemplates[definition.Name] = definition; - } - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs deleted file mode 100644 index aa36232156..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinitionDictionary : Dictionary - { - public EmailTemplateDefinitionDictionary Add(EmailTemplateDefinition emailTemplateDefinition) - { - if (ContainsKey(emailTemplateDefinition.Name)) - { - throw new AbpException( - "There is already an email template definition with given name: " + - emailTemplateDefinition.Name - ); - } - - this[emailTemplateDefinition.Name] = emailTemplateDefinition; - - return this; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs deleted file mode 100644 index 0491dc867e..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinitionManager : IEmailTemplateDefinitionManager, ISingletonDependency - { - protected Lazy> EmailTemplateDefinitions { get; } - - protected AbpEmailTemplateOptions Options { get; } - - protected IServiceProvider ServiceProvider { get; } - - public EmailTemplateDefinitionManager( - IOptions options, - IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - Options = options.Value; - - EmailTemplateDefinitions = - new Lazy>(CreateEmailTemplateDefinitions, true); - } - - public virtual EmailTemplateDefinition Get(string name) - { - Check.NotNull(name, nameof(name)); - - var template = GetOrNull(name); - - if (template == null) - { - throw new AbpException("Undefined template: " + name); - } - - return template; - } - - public virtual IReadOnlyList GetAll() - { - return EmailTemplateDefinitions.Value.Values.ToImmutableList(); - } - - public virtual EmailTemplateDefinition GetOrNull(string name) - { - return EmailTemplateDefinitions.Value.GetOrDefault(name); - } - - protected virtual IDictionary CreateEmailTemplateDefinitions() - { - var templates = new Dictionary(); - - using (var scope = ServiceProvider.CreateScope()) - { - var providers = Options - .DefinitionProviders - .Select(p => scope.ServiceProvider.GetRequiredService(p) as IEmailTemplateDefinitionProvider) - .ToList(); - - foreach (var provider in providers) - { - provider.Define(new EmailTemplateDefinitionContext(templates)); - } - } - - return templates; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs deleted file mode 100644 index e53505fafc..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public abstract class EmailTemplateDefinitionProvider : IEmailTemplateDefinitionProvider, ITransientDependency - { - public abstract void Define(IEmailTemplateDefinitionContext context); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs deleted file mode 100644 index 8cd4b95bbd..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateInitializationContext - { - public EmailTemplateDefinition EmailTemplateDefinition { get; } - - public IServiceProvider ServiceProvider { get; } - - public EmailTemplateInitializationContext(EmailTemplateDefinition emailTemplateDefinition, - IServiceProvider serviceProvider) - { - EmailTemplateDefinition = emailTemplateDefinition; - ServiceProvider = serviceProvider; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs deleted file mode 100644 index 29fcf08664..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Globalization; -using System.Threading.Tasks; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Localization; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateProvider : IEmailTemplateProvider, ITransientDependency - { - protected IEmailTemplateDefinitionManager EmailTemplateDefinitionManager; - protected ITemplateLocalizer TemplateLocalizer { get; } - protected AbpEmailTemplateOptions Options { get; } - protected IStringLocalizerFactory StringLocalizerFactory; - - public EmailTemplateProvider(IEmailTemplateDefinitionManager emailTemplateDefinitionManager, - ITemplateLocalizer templateLocalizer, IStringLocalizerFactory stringLocalizerFactory, - IOptions options) - { - EmailTemplateDefinitionManager = emailTemplateDefinitionManager; - TemplateLocalizer = templateLocalizer; - StringLocalizerFactory = stringLocalizerFactory; - Options = options.Value; - } - - public async Task GetAsync(string name) - { - return await GetAsync(name, CultureInfo.CurrentUICulture.Name); - } - - public async Task GetAsync(string name, string cultureName) - { - return await GetInternalAsync(name, cultureName); - } - - protected virtual async Task GetInternalAsync(string name, string cultureName) - { - var emailTemplateDefinition = EmailTemplateDefinitionManager.GetOrNull(name); - if (emailTemplateDefinition == null) - { - // TODO: Localized message - throw new AbpException($"email template {name} not definition"); - } - - var emailTemplateString = emailTemplateDefinition.Contributors.GetOrNull(cultureName); - if (emailTemplateString == null && emailTemplateDefinition.DefaultCultureName != null) - { - emailTemplateString = - emailTemplateDefinition.Contributors.GetOrNull(emailTemplateDefinition.DefaultCultureName); - if (emailTemplateString != null) - { - cultureName = emailTemplateDefinition.DefaultCultureName; - } - } - - if (emailTemplateString != null) - { - var emailTemplate = new EmailTemplate(emailTemplateString, emailTemplateDefinition); - - await SetLayoutAsync(emailTemplateDefinition, emailTemplate, cultureName); - - if (emailTemplateDefinition.SingleTemplateFile) - { - await LocalizeAsync(emailTemplateDefinition, emailTemplate, cultureName); - } - - return emailTemplate; - } - - // TODO: Localized message - throw new AbpException($"{cultureName} template not exist!"); - } - - protected virtual async Task SetLayoutAsync(EmailTemplateDefinition emailTemplateDefinition, - EmailTemplate emailTemplate, string cultureName) - { - var layout = emailTemplateDefinition.Layout; - if (layout.IsNullOrWhiteSpace()) - { - return; - } - - if (layout == EmailTemplateDefinition.DefaultLayoutPlaceHolder) - { - layout = Options.DefaultLayout; - } - - var layoutTemplate = await GetInternalAsync(layout, cultureName); - - emailTemplate.SetLayout(layoutTemplate); - } - - protected virtual Task LocalizeAsync(EmailTemplateDefinition emailTemplateDefinition, - EmailTemplate emailTemplate, string cultureName) - { - if (emailTemplateDefinition.LocalizationResource == null) - { - return Task.CompletedTask; - } - - var localizer = StringLocalizerFactory.Create(emailTemplateDefinition.LocalizationResource); - if (cultureName != null) - { - using (CultureHelper.Use(new CultureInfo(cultureName))) - { - emailTemplate.SetContent(TemplateLocalizer.Localize(localizer, emailTemplate.Content)); - } - } - else - { - emailTemplate.SetContent( - TemplateLocalizer.Localize(localizer, emailTemplate.Content) - ); - } - - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs deleted file mode 100644 index d2c2775845..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateContributor - { - void Initialize(EmailTemplateInitializationContext context); - - string GetOrNull(string cultureName); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs deleted file mode 100644 index 1641562ccf..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateDefinitionContext - { - EmailTemplateDefinition GetOrNull(string name); - - void Add(params EmailTemplateDefinition[] definitions); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs deleted file mode 100644 index 0936a2fe93..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; - -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateDefinitionManager - { - [NotNull] - EmailTemplateDefinition Get([NotNull] string name); - - IReadOnlyList GetAll(); - - EmailTemplateDefinition GetOrNull(string name); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs deleted file mode 100644 index 691d3874d6..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateDefinitionProvider - { - void Define(IEmailTemplateDefinitionContext context); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs deleted file mode 100644 index ab68dbe2ca..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateProvider - { - Task GetAsync(string name); - - Task GetAsync(string name, string cultureName); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs deleted file mode 100644 index 35ac14c8fd..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.Emailing.Templates -{ - public interface ITemplateRender - { - Task RenderAsync(string template, object model = null); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs deleted file mode 100644 index 8c4e24017c..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Threading.Tasks; -using Scriban; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public class TemplateRender : ITemplateRender, ITransientDependency - { - public async Task RenderAsync(string template, object model = null) - { - var scribanTemplate = Template.Parse(template); - return await scribanTemplate.RenderAsync(model); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs deleted file mode 100644 index 7bee611d28..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Volo.Abp.Emailing.Templates.VirtualFiles -{ - public static class EmailTemplateDefinitionExtensions - { - public static EmailTemplateDefinition AddTemplateVirtualFile( - this EmailTemplateDefinition emailTemplateDefinition, string path) - { - emailTemplateDefinition.Contributors.Add(new SingleVirtualFileEmailTemplateContributor(path)); - return emailTemplateDefinition; - } - - public static EmailTemplateDefinition AddTemplateVirtualFiles( - this EmailTemplateDefinition emailTemplateDefinition, string path) - { - emailTemplateDefinition.Contributors.Add(new MultipleVirtualFilesEmailTemplateContributor(path)); - return emailTemplateDefinition; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs deleted file mode 100644 index 39af512350..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Volo.Abp.VirtualFileSystem; - -namespace Volo.Abp.Emailing.Templates.VirtualFiles -{ - public class MultipleVirtualFilesEmailTemplateContributor : IEmailTemplateContributor - { - private readonly string _virtualPath; - - private IVirtualFileProvider _virtualFileProvider; - - private Dictionary _templateDictionary; - - private readonly object _syncObj = new object(); - - public MultipleVirtualFilesEmailTemplateContributor(string virtualPath) - { - _virtualPath = virtualPath; - } - - public void Initialize(EmailTemplateInitializationContext context) - { - _virtualFileProvider = context.ServiceProvider.GetRequiredService(); - } - - public string GetOrNull(string cultureName) - { - return GetTemplateDictionary().GetOrDefault(cultureName); - } - - private Dictionary GetTemplateDictionary() - { - var dictionaries = _templateDictionary; - if (dictionaries != null) - { - return dictionaries; - } - - lock (_syncObj) - { - dictionaries = _templateDictionary; - if (dictionaries != null) - { - return dictionaries; - } - - _templateDictionary = new Dictionary(); - foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) - { - if (file.IsDirectory) - { - continue; - } - - // TODO: How to normalize file names? - _templateDictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); - } - - dictionaries = _templateDictionary; - } - - return dictionaries; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs deleted file mode 100644 index d72d18e99a..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Volo.Abp.VirtualFileSystem; - -namespace Volo.Abp.Emailing.Templates.VirtualFiles -{ - public class SingleVirtualFileEmailTemplateContributor : IEmailTemplateContributor - { - private readonly string _virtualPath; - - private IVirtualFileProvider _virtualFileProvider; - - public SingleVirtualFileEmailTemplateContributor(string virtualPath) - { - _virtualPath = virtualPath; - } - - public void Initialize(EmailTemplateInitializationContext context) - { - _virtualFileProvider = context.ServiceProvider.GetRequiredService(); - } - - public string GetOrNull(string cultureName) - { - var file = _virtualFileProvider.GetFileInfo(_virtualPath); - if (file == null || !file.Exists || file.IsDirectory) - { - return null; - } - - return file.ReadAsString(); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs deleted file mode 100644 index 5c4220c45f..0000000000 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.RegularExpressions; -using Microsoft.Extensions.Localization; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Localization -{ - public class TemplateLocalizer : ITemplateLocalizer, ITransientDependency - { - public string Localize(IStringLocalizer localizer, string text) - { - return new Regex("\\{\\{#L:.+?\\}\\}") - .Replace( - text, - match => localizer[match.Value.Substring(5, match.Length - 7)] - ); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs index 526da6a381..12c8f8f8d8 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -9,5 +9,10 @@ namespace Volo.Abp.TextTemplating [NotNull] string templateName, [CanBeNull] string cultureName = null ); + + Task GetContentOrNullAsync( + [NotNull] TemplateDefinition templateDefinition, + [CanBeNull] string cultureName = null + ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 1334b5b1dd..4049a3861e 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -15,15 +15,23 @@ namespace Volo.Abp.TextTemplating _templateDefinitionManager = templateDefinitionManager; } - public async Task GetContentOrNullAsync( + public Task GetContentOrNullAsync( [NotNull] string templateName, [CanBeNull] string cultureName = null) { var template = _templateDefinitionManager.Get(templateName); + return GetContentOrNullAsync(template, cultureName); + } + + public async Task GetContentOrNullAsync( + [NotNull] TemplateDefinition templateDefinition, + [CanBeNull] string cultureName = null) + { + Check.NotNull(templateDefinition, nameof(templateDefinition)); - foreach (var contributor in template.Contributors) + foreach (var contributor in templateDefinition.Contributors) { - var templateString = contributor.GetOrNull(cultureName); + var templateString = contributor.GetOrNull(cultureName); //TODO: GetOrNull should be async! if (templateString != null) { return templateString; @@ -31,7 +39,7 @@ namespace Volo.Abp.TextTemplating } throw new AbpException( - $"None of the template contributors could get the content for the template '{templateName}'" + $"None of the template contributors could get the content for the template '{templateDefinition.Name}'" ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 548ac87c15..ea4ba03e27 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -15,7 +15,8 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public string Layout { get; set; } - public Type LocalizationResource { get; set; } //TODO: ??? + [CanBeNull] + public Type LocalizationResource { get; set; } public TemplateContributorList Contributors { get; } @@ -24,7 +25,7 @@ namespace Volo.Abp.TextTemplating public TemplateDefinition( [NotNull] string name, - Type localizationResource = null, + [CanBeNull] Type localizationResource = null, bool isLayout = false, string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 127e7e9872..c3e3f2b70d 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -1,5 +1,7 @@ -using System.Threading.Tasks; +using System.Text.RegularExpressions; +using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Localization; using Scriban; using Volo.Abp.DependencyInjection; @@ -8,11 +10,17 @@ namespace Volo.Abp.TextTemplating public class TemplateRenderer : ITemplateRenderer, ITransientDependency { private readonly ITemplateContentProvider _templateContentProvider; + private readonly ITemplateDefinitionManager _templateDefinitionManager; + private readonly IStringLocalizerFactory _stringLocalizerFactory; public TemplateRenderer( - ITemplateContentProvider templateContentProvider) + ITemplateContentProvider templateContentProvider, + ITemplateDefinitionManager templateDefinitionManager, + IStringLocalizerFactory stringLocalizerFactory) { _templateContentProvider = templateContentProvider; + _templateDefinitionManager = templateDefinitionManager; + _stringLocalizerFactory = stringLocalizerFactory; } public virtual async Task RenderAsync( @@ -22,14 +30,43 @@ namespace Volo.Abp.TextTemplating { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); + var templateDefinition = _templateDefinitionManager.Get(templateName); + var content = await _templateContentProvider.GetContentOrNullAsync( - templateName, + templateDefinition, cultureName ); - var parsedTemplate = Template.Parse(content); + if (templateDefinition.LocalizationResource != null) + { + var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); + content = Localize(localizer, content); + } + + var renderedContent = await Template.Parse(content).RenderAsync(model); + + if (templateDefinition.Layout != null) + { + renderedContent = await RenderAsync( + templateDefinition.Layout, + new + { + content = renderedContent + }, + cultureName: cultureName + ); + } - return await parsedTemplate.RenderAsync(model); + return renderedContent; + } + + public string Localize(IStringLocalizer localizer, string text) + { + return new Regex("\\{\\{#L:.+?\\}\\}") + .Replace( + text, + match => localizer[match.Value.Substring(5, match.Length - 7)] + ); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj b/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj index a86ea9f051..030eddb65a 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj @@ -7,13 +7,6 @@ - - - - - - - diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs index e4cdfe0556..7f1175bcbb 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs @@ -1,6 +1,4 @@ using Volo.Abp.Autofac; -using Volo.Abp.Emailing.Localization; -using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -18,14 +16,6 @@ namespace Volo.Abp.Emailing { options.FileSets.AddEmbedded(); }); - - Configure(options => - { - options.Resources - .Add() - .AddVirtualJson("/Volo/Abp/Emailing/Localization"); - }); - } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs deleted file mode 100644 index 24d7298f6c..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.Emailing -{ - public class EmailTemplateRender_Tests : AbpIntegratedTest - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - private readonly ITemplateRender _templateRender; - - public EmailTemplateRender_Tests() - { - _templateRender = GetRequiredService(); - } - - [Fact] - public async Task RenderAsync() - { - var template = "Hello {{email}} {{ for order in orders }}{{ order.id }}:{{ order.name }},{{ end }}"; - - var model = new ModelClass - { - Email = "john@abp.io", - Orders = new List - { - new ModelClass.Order - { - Id = "1", - Name = "iphone" - }, - new ModelClass.Order - { - Id = "2", - Name = "ipad" - } - } - }; - - var result = await _templateRender.RenderAsync(template, model); - result.ShouldBe("Hello john@abp.io 1:iphone,2:ipad,"); - } - - public class ModelClass - { - public string Email { get; set; } - - public List Orders { get; set; } - - public class Order - { - public string Id { get; set; } - - public string Name { get; set; } - } - } - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs deleted file mode 100644 index 1e4ea8c090..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.Emailing -{ - public class EmailTemplateStore_Tests : AbpIntegratedTest - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - private readonly IEmailTemplateProvider _emailTemplateProvider; - - public EmailTemplateStore_Tests() - { - _emailTemplateProvider = GetRequiredService(); - } - - [Fact] - public async Task Should_Get_Registered_Template() - { - var template = await _emailTemplateProvider.GetAsync("template1", "tr"); - template.Content.ShouldContain("Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın."); - } - - [Fact] - public async Task Should_Get_Default_Culture_Template() - { - var template = await _emailTemplateProvider.GetAsync("template1", "zh-Hans"); - template.Content.ShouldContain("Please confirm your email address by clicking the link below."); - } - - [Fact] - public async Task Should_Get_Registered_Template_With_Layout() - { - var template = await _emailTemplateProvider.GetAsync("template2", "en"); - - template.Content.ShouldContain($"{Environment.NewLine} " + "Please confirm your email address by clicking the link below."); - } - - - [Fact] - public async Task Should_Get_Registered_Template_With_Localize() - { - var template = await _emailTemplateProvider.GetAsync("template3", "tr"); - template.Content.ShouldContain("Merhaba Abp"); - } - } -} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs deleted file mode 100644 index bdc75f9f2f..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Volo.Abp.Emailing.Localization -{ - public class AbpEmailingTestResource - { - } -} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json deleted file mode 100644 index 5f6e35488e..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "cs", - "texts": { - "hello": "ahoj" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json deleted file mode 100644 index 38f7cf7e5b..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "en", - "texts": { - "hello": "hello" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json deleted file mode 100644 index 6dd6654aa1..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pl", - "texts": { - "hello": "witaj" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json deleted file mode 100644 index 0e33dee138..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pt-BR", - "texts": { - "hello": "Ol" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json deleted file mode 100644 index 6c3c94cfdf..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "tr", - "texts": { - "hello": "Merhaba" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json deleted file mode 100644 index 8261599d78..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "vi", - "texts": { - "hello": "xin chào" - } -} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json deleted file mode 100644 index dd12964f70..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "zh-Hant", - "texts": { - "hello": "哈囉" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs deleted file mode 100644 index 60c9a2ccf3..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Volo.Abp.Emailing.Localization; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Emailing.Templates.VirtualFiles; - -namespace Volo.Abp.Emailing -{ - public class TestEmailTemplateProvider : EmailTemplateDefinitionProvider - { - public override void Define(IEmailTemplateDefinitionContext context) - { - var template1 = new EmailTemplateDefinition("template1", defaultCultureName: "en", layout: null) - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template1"); - context.Add(template1); - - var template2 = new EmailTemplateDefinition("template2", layout: StandardEmailTemplates.DefaultLayout) - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template2"); - context.Add(template2); - - var template3 = new EmailTemplateDefinition("template3", layout: null, singleTemplateFile: true, localizationResource: typeof(AbpEmailingTestResource)) - .AddTemplateVirtualFile("/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl"); - context.Add(template3); - } - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl deleted file mode 100644 index 49a951e8c0..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl +++ /dev/null @@ -1,4 +0,0 @@ -Please confirm your email address by clicking the link below. -We may need to send you critical information about our service and it is important that we have an accurate email address. - -Confirm email address \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl deleted file mode 100644 index 3e572aedbb..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl +++ /dev/null @@ -1,4 +0,0 @@ -Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın. -Size hizmetimizle ilgili kritik bilgileri göndermemiz gerekebilir ve doğru bir e-posta adresimizin olması önemlidir. - -E-posta adresini onayla \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl deleted file mode 100644 index 49a951e8c0..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl +++ /dev/null @@ -1,4 +0,0 @@ -Please confirm your email address by clicking the link below. -We may need to send you critical information about our service and it is important that we have an accurate email address. - -Confirm email address \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl deleted file mode 100644 index f30f512848..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl +++ /dev/null @@ -1 +0,0 @@ -{{#L:hello}} Abp \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs deleted file mode 100644 index 4b31554637..0000000000 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; -using Shouldly; -using Volo.Abp.Localization.TestResources.Source; -using Volo.Abp.Modularity; -using Volo.Abp.Testing; -using Volo.Abp.VirtualFileSystem; -using Xunit; - -namespace Volo.Abp.Localization -{ - public class TemplateLocalizer_Tests : AbpIntegratedTest - { - private readonly ITemplateLocalizer _templateLocalizer; - private readonly IStringLocalizer _testResource; - - public TemplateLocalizer_Tests() - { - _testResource = GetRequiredService>(); - _templateLocalizer = GetRequiredService(); - } - - [Fact] - public void Should_Localize() - { - using (CultureHelper.Use("en")) - { - _templateLocalizer.Localize(_testResource, "

{{#L:CarPlural}} {{#L:Universe}}

") - .ShouldBe("

Cars Universe

"); - } - } - - [Fact] - public void Should_Work_Even_If_No_Text_To_Localize() - { - using (CultureHelper.Use("en")) - { - _templateLocalizer.Localize(_testResource, "

test

") - .ShouldBe("

test

"); - } - } - - [DependsOn(typeof(AbpTestBaseModule))] - [DependsOn(typeof(AbpLocalizationModule))] - public class TestModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - - Configure(options => - { - options.Resources - .Add("en") - .AddVirtualJson("/Volo/Abp/Localization/TestResources/Source"); - }); - } - } - } -} From 0311092ae1b43212ddee6c0c82ed2eafc6c013c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 23:03:37 +0300 Subject: [PATCH 15/52] Refactored text templating and added more tests. --- .../Volo/Abp/Localization/CultureHelper.cs | 7 +- .../Abp/TextTemplating/TemplateDefinition.cs | 4 +- .../Abp/TextTemplating/TemplateRenderer.cs | 120 ++++++++++++++---- .../Volo.Abp.TextTemplating.Tests.csproj | 3 + .../AbpTextTemplatingTestModule.cs | 12 +- .../Localization/TestLocalizationSource.cs | 6 + .../Abp/TextTemplating/Localization/en.json | 6 + .../Abp/TextTemplating/Localization/tr.json | 6 + .../SampleTemplates/ForgotPasswordEmail.tpl | 2 +- .../SampleTemplates/TestTemplateLayout1.tpl | 1 + .../SampleTemplates/WelcomeEmail/en.tpl | 2 +- .../SampleTemplates/WelcomeEmail/tr.tpl | 2 +- .../TextTemplating/TemplateRenderer_Tests.cs | 24 ++-- .../TestTemplateDefinitionProvider.cs | 17 ++- .../VirtualFileTemplateContributor_Tests.cs | 6 +- 15 files changed, 168 insertions(+), 50 deletions(-) create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs index 8f663e084e..d4d9cfb1c0 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs @@ -10,7 +10,12 @@ namespace Volo.Abp.Localization { Check.NotNull(culture, nameof(culture)); - return Use(new CultureInfo(culture), uiCulture == null ? null : new CultureInfo(uiCulture)); + return Use( + new CultureInfo(culture), + uiCulture == null + ? null + : new CultureInfo(uiCulture) + ); } public static IDisposable Use([NotNull] CultureInfo culture, CultureInfo uiCulture = null) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index ea4ba03e27..8e113829ea 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -5,8 +5,6 @@ namespace Volo.Abp.TextTemplating { public class TemplateDefinition { - public const string DefaultLayoutPlaceHolder = "_"; - [NotNull] public string Name { get; } @@ -27,7 +25,7 @@ namespace Volo.Abp.TextTemplating [NotNull] string name, [CanBeNull] Type localizationResource = null, bool isLayout = false, - string layout = DefaultLayoutPlaceHolder, + string layout = null, string defaultCultureName = null) { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index c3e3f2b70d..4ff2beb9ba 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -1,9 +1,13 @@ -using System.Text.RegularExpressions; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Localization; using Scriban; +using Scriban.Runtime; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.TextTemplating { @@ -14,8 +18,8 @@ namespace Volo.Abp.TextTemplating private readonly IStringLocalizerFactory _stringLocalizerFactory; public TemplateRenderer( - ITemplateContentProvider templateContentProvider, - ITemplateDefinitionManager templateDefinitionManager, + ITemplateContentProvider templateContentProvider, + ITemplateDefinitionManager templateDefinitionManager, IStringLocalizerFactory stringLocalizerFactory) { _templateContentProvider = templateContentProvider; @@ -30,43 +34,111 @@ namespace Volo.Abp.TextTemplating { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); - var templateDefinition = _templateDefinitionManager.Get(templateName); - - var content = await _templateContentProvider.GetContentOrNullAsync( - templateDefinition, - cultureName - ); + if (cultureName == null) + { + cultureName = CultureInfo.CurrentUICulture.Name; + } - if (templateDefinition.LocalizationResource != null) + using (CultureHelper.Use(cultureName)) { - var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); - content = Localize(localizer, content); + return await RenderInternalAsync( + templateName, + new Dictionary(), + model + ); } + } - var renderedContent = await Template.Parse(content).RenderAsync(model); + private async Task RenderInternalAsync( + string templateName, + Dictionary globalContext, + object model = null) + { + var templateDefinition = _templateDefinitionManager.Get(templateName); + + var renderedContent = await RenderSingleTemplateAsync( + templateDefinition, + globalContext, + model + ); if (templateDefinition.Layout != null) { - renderedContent = await RenderAsync( + globalContext["content"] = renderedContent; + renderedContent = await RenderInternalAsync( templateDefinition.Layout, - new - { - content = renderedContent - }, - cultureName: cultureName + globalContext ); } return renderedContent; } - public string Localize(IStringLocalizer localizer, string text) + private async Task RenderSingleTemplateAsync( + TemplateDefinition templateDefinition, + Dictionary globalContext, + object model = null) + { + var rawTemplateContent = await _templateContentProvider + .GetContentOrNullAsync( + templateDefinition + ); + + return await RenderTemplateContentWithScribanAsync( + templateDefinition, + rawTemplateContent, + globalContext, + model + ); + } + + protected virtual async Task RenderTemplateContentWithScribanAsync( + TemplateDefinition templateDefinition, + string templateContent, + Dictionary globalContext, + object model = null) + { + var context = CreateScribanTemplateContext( + templateDefinition, + globalContext, + model + ); + + return await Template + .Parse(templateContent) + .RenderAsync(context); + } + + protected virtual TemplateContext CreateScribanTemplateContext( + TemplateDefinition templateDefinition, + Dictionary globalContext, + object model = null) { - return new Regex("\\{\\{#L:.+?\\}\\}") - .Replace( - text, - match => localizer[match.Value.Substring(5, match.Length - 7)] + var context = new TemplateContext(); + + var scriptObject = new ScriptObject(); + + scriptObject.Import(globalContext); + + if (model != null) + { + scriptObject["model"] = model; + } + + if (templateDefinition.LocalizationResource != null) + { + var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); + scriptObject.Import( + "l", + new Func( + name => localizer[name] + ) ); + } + + context.PushGlobal(scriptObject); + + return context; } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj index 916495b116..0d230552e8 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -8,11 +8,14 @@ + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs index 0457e0136d..8e8b69e116 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -1,5 +1,7 @@ using Volo.Abp.Autofac; +using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.TextTemplating.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating @@ -7,7 +9,8 @@ namespace Volo.Abp.TextTemplating [DependsOn( typeof(AbpTextTemplatingModule), typeof(AbpTestBaseModule), - typeof(AbpAutofacModule) + typeof(AbpAutofacModule), + typeof(AbpLocalizationModule) )] public class AbpTextTemplatingTestModule : AbpModule { @@ -17,6 +20,13 @@ namespace Volo.Abp.TextTemplating { options.FileSets.AddEmbedded("Volo.Abp.TextTemplating"); }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Localization"); + }); } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs new file mode 100644 index 0000000000..a4e53e3861 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.TextTemplating.Localization +{ + public class TestLocalizationSource + { + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json new file mode 100644 index 0000000000..a0dff7e930 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "HelloText": "Hello" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json new file mode 100644 index 0000000000..d09f02cd8f --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json @@ -0,0 +1,6 @@ +{ + "culture": "tr", + "texts": { + "HelloText": "Merhaba" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl index 674b734c8a..7d844c6f7c 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl @@ -1 +1 @@ -Please click to the following link to get an email to reset your password! \ No newline at end of file +{{l "HelloText"}}. Please click to the following link to get an email to reset your password! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl new file mode 100644 index 0000000000..a780e210b0 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl @@ -0,0 +1 @@ +*BEGIN*{{content}}*END* \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl index 1088362628..1746eed52b 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl @@ -1 +1 @@ -Welcome {{name}} to the abp.io! \ No newline at end of file +Welcome {{model.name}} to the abp.io! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl index b457611f1e..581016bc4d 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl @@ -1 +1 @@ -Merhaba {{name}}, abp.io'ya hoşgeldiniz! \ No newline at end of file +Merhaba {{model.name}}, abp.io'ya hoşgeldiniz! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs index 9e76d22ab8..14b3df3486 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -14,16 +14,6 @@ namespace Volo.Abp.TextTemplating _templateRenderer = GetRequiredService(); } - [Fact] - public async Task Should_Get_Rendered_Non_Localized_Template_Content() - { - var content = await _templateRenderer.RenderAsync( - TestTemplates.ForgotPasswordEmail - ); - - content.ShouldBe("Please click to the following link to get an email to reset your password!"); - } - [Fact] public async Task Should_Get_Rendered_Localized_Template_Content_With_Different_Cultures() { @@ -86,6 +76,20 @@ namespace Volo.Abp.TextTemplating )).ShouldBe("Welcome John to the abp.io!"); } + [Fact] + public async Task Should_Get_Rendered_Inline_Localized_Template() + { + (await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail, + cultureName: "en" + )).ShouldBe("*BEGIN*Hello. Please click to the following link to get an email to reset your password!*END*"); + + (await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail, + cultureName: "tr" + )).ShouldBe("*BEGIN*Merhaba. Please click to the following link to get an email to reset your password!*END*"); + } + private class WelcomeEmailModel { public string Name { get; set; } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 7a1ff4121b..8fe7c98e38 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -1,4 +1,6 @@ -namespace Volo.Abp.TextTemplating +using Volo.Abp.TextTemplating.Localization; + +namespace Volo.Abp.TextTemplating { public class TestTemplateDefinitionProvider : TemplateDefinitionProvider { @@ -13,13 +15,18 @@ context.Add( new TemplateDefinition( - TestTemplates.ForgotPasswordEmail + TestTemplates.ForgotPasswordEmail, + localizationResource: typeof(TestLocalizationSource), + layout: TestTemplates.TestTemplateLayout1 ).AddVirtualFiles("/SampleTemplates/ForgotPasswordEmail.tpl") ); - context.Add(new TemplateDefinition( - TestTemplates.TestTemplateLayout1 - )); + context.Add( + new TemplateDefinition( + TestTemplates.TestTemplateLayout1, + isLayout: true + ).AddVirtualFiles("/SampleTemplates/TestTemplateLayout1.tpl") + ); } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 1c636d7813..2c104b632d 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -21,11 +21,11 @@ namespace Volo.Abp.TextTemplating.VirtualFiles contributor .GetOrNull("en") - .ShouldBe("Welcome {{name}} to the abp.io!"); + .ShouldBe("Welcome {{model.name}} to the abp.io!"); contributor .GetOrNull("tr") - .ShouldBe("Merhaba {{name}}, abp.io'ya hoşgeldiniz!"); + .ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); } [Fact] @@ -44,7 +44,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles contributor .GetOrNull() - .ShouldBe("Please click to the following link to get an email to reset your password!"); + .ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); } } } From 7bd91c1d01e49bfd7850c6a10fbc15771fcd0fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 23:07:32 +0300 Subject: [PATCH 16/52] Get a globalContext from the TemplateRenderer --- .../Volo/Abp/TextTemplating/ITemplateRenderer.cs | 14 ++++++++++++-- .../Volo/Abp/TextTemplating/TemplateRenderer.cs | 5 +++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs index 92a1c0782c..b153a5130b 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using JetBrains.Annotations; @@ -6,10 +7,19 @@ namespace Volo.Abp.TextTemplating { public interface ITemplateRenderer { + /// + /// Renders a text template. + /// + /// The template name + /// An optional model object that is used in the template + /// Culture name. Uses the if not specified + /// A dictionary which can be used to import global objects to the template + /// Task RenderAsync( [NotNull] string templateName, [CanBeNull] object model = null, - [CanBeNull] string cultureName = null + [CanBeNull] string cultureName = null, + [CanBeNull] Dictionary globalContext = null ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 4ff2beb9ba..2824c6e463 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -30,7 +30,8 @@ namespace Volo.Abp.TextTemplating public virtual async Task RenderAsync( [NotNull] string templateName, [CanBeNull] object model = null, - [CanBeNull] string cultureName = null) + [CanBeNull] string cultureName = null, + [CanBeNull] Dictionary globalContext = null) { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); @@ -43,7 +44,7 @@ namespace Volo.Abp.TextTemplating { return await RenderInternalAsync( templateName, - new Dictionary(), + globalContext ?? new Dictionary(), model ); } From 693e4ad5bf0e814622d6de058ded7bde4b450ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 23:09:25 +0300 Subject: [PATCH 17/52] Update TemplateRenderer.cs --- .../Volo/Abp/TextTemplating/TemplateRenderer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 2824c6e463..558104e3a4 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -50,7 +50,7 @@ namespace Volo.Abp.TextTemplating } } - private async Task RenderInternalAsync( + protected virtual async Task RenderInternalAsync( string templateName, Dictionary globalContext, object model = null) @@ -75,7 +75,7 @@ namespace Volo.Abp.TextTemplating return renderedContent; } - private async Task RenderSingleTemplateAsync( + protected virtual async Task RenderSingleTemplateAsync( TemplateDefinition templateDefinition, Dictionary globalContext, object model = null) From f51c67f11a8427d8152302e0c57710023a6e1260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 23:23:10 +0300 Subject: [PATCH 18/52] Re-Organize standard email templates and renamed AddVirtualFiles to WithVirtualFilePath --- .../src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj | 8 ++++---- .../Templates/DefaultEmailTemplateProvider.cs | 14 +++++++------- .../{DefaultEmailTemplates => }/Layout/en.tpl | 0 .../{DefaultEmailTemplates => }/Message/en.tpl | 0 .../Emailing/Templates/StandardEmailTemplates.cs | 4 ++-- .../Volo/Abp/TextTemplating/TemplateDefinition.cs | 2 +- .../TextTemplating/TemplateDefinitionExtensions.cs | 10 ++++++++-- .../TestTemplateDefinitionProvider.cs | 6 +++--- 8 files changed, 25 insertions(+), 19 deletions(-) rename framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/{DefaultEmailTemplates => }/Layout/en.tpl (100%) rename framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/{DefaultEmailTemplates => }/Message/en.tpl (100%) diff --git a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj index 783d840cf0..a2f83399a0 100644 --- a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj +++ b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj @@ -15,13 +15,13 @@ - - + + - - + + diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs index 28d1ac94c1..385e0ef178 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs @@ -8,18 +8,18 @@ namespace Volo.Abp.Emailing.Templates { context.Add( new TemplateDefinition( - StandardEmailTemplates.DefaultLayout, + StandardEmailTemplates.Layout, defaultCultureName: "en", - isLayout: true, - layout: null - ).AddVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout") + isLayout: true + ).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Layout") ); context.Add( new TemplateDefinition( - StandardEmailTemplates.SimpleMessage, - defaultCultureName: "en" - ).AddVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message") + StandardEmailTemplates.Message, + defaultCultureName: "en", + layout: StandardEmailTemplates.Layout + ).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Message") ); } } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Layout/en.tpl similarity index 100% rename from framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl rename to framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Layout/en.tpl diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message/en.tpl b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Message/en.tpl similarity index 100% rename from framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message/en.tpl rename to framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Message/en.tpl diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs index 7e8cbedc68..6a60219a5e 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs @@ -2,7 +2,7 @@ { public static class StandardEmailTemplates { - public const string DefaultLayout = "Abp.DefaultLayout"; - public const string SimpleMessage = "Abp.SimpleMessage"; + public const string Layout = "Abp.StandardEmailTemplates.Layout"; + public const string Message = "Abp.StandardEmailTemplates.Message"; } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 8e113829ea..09de7bf458 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -36,7 +36,7 @@ namespace Volo.Abp.TextTemplating DefaultCultureName = defaultCultureName; } - public virtual TemplateDefinition AddContributor(ITemplateContributor contributor) + public virtual TemplateDefinition WithContributor(ITemplateContributor contributor) { Contributors.Add(contributor); return this; diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs index 6508295da7..9bd932c947 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -5,13 +5,19 @@ namespace Volo.Abp.TextTemplating { public static class TemplateDefinitionExtensions { - public static TemplateDefinition AddVirtualFiles( + /// + /// + /// + /// + /// + /// + public static TemplateDefinition WithVirtualFilePath( [NotNull] this TemplateDefinition templateDefinition, [NotNull] string virtualPath) { Check.NotNull(templateDefinition, nameof(templateDefinition)); - return templateDefinition.AddContributor( + return templateDefinition.WithContributor( new VirtualFileTemplateContributor(virtualPath) ); } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 8fe7c98e38..05df1378b2 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.TextTemplating new TemplateDefinition( TestTemplates.WelcomeEmail, defaultCultureName: "en" - ).AddVirtualFiles("/SampleTemplates/WelcomeEmail") + ).WithVirtualFilePath("/SampleTemplates/WelcomeEmail") ); context.Add( @@ -18,14 +18,14 @@ namespace Volo.Abp.TextTemplating TestTemplates.ForgotPasswordEmail, localizationResource: typeof(TestLocalizationSource), layout: TestTemplates.TestTemplateLayout1 - ).AddVirtualFiles("/SampleTemplates/ForgotPasswordEmail.tpl") + ).WithVirtualFilePath("/SampleTemplates/ForgotPasswordEmail.tpl") ); context.Add( new TemplateDefinition( TestTemplates.TestTemplateLayout1, isLayout: true - ).AddVirtualFiles("/SampleTemplates/TestTemplateLayout1.tpl") + ).WithVirtualFilePath("/SampleTemplates/TestTemplateLayout1.tpl") ); } } From 60e4a8e8c33388f67d555d779e932a648bc85acf Mon Sep 17 00:00:00 2001 From: Ahmet Date: Thu, 23 Apr 2020 23:48:29 +0300 Subject: [PATCH 19/52] ITemplateContributor GetOrNull converted to Async --- .../TextTemplating/ITemplateContributor.cs | 5 +++-- .../TextTemplating/TemplateContentProvider.cs | 2 +- .../VirtualFileTemplateContributor.cs | 3 ++- .../VirtualFileTemplateContributor_Tests.cs | 22 +++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs index e8e1f97769..2174a65bcd 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Threading.Tasks; +using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { @@ -6,6 +7,6 @@ namespace Volo.Abp.TextTemplating { void Initialize(TemplateContributorInitializationContext context); - string GetOrNull([CanBeNull] string cultureName); + Task GetOrNullAsync([CanBeNull] string cultureName); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 4049a3861e..b983c75f11 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -31,7 +31,7 @@ namespace Volo.Abp.TextTemplating foreach (var contributor in templateDefinition.Contributors) { - var templateString = contributor.GetOrNull(cultureName); //TODO: GetOrNull should be async! + var templateString = await contributor.GetOrNullAsync(cultureName); if (templateString != null) { return templateString; diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs index f1a4700da7..ae4b8c92f0 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -30,7 +31,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles TemplateDefinition = context.TemplateDefinition; } - public string GetOrNull([CanBeNull] string cultureName = null) + public async Task GetOrNullAsync([CanBeNull] string cultureName = null) { //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 2c104b632d..1ff2596342 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -1,4 +1,5 @@ -using Shouldly; +using System.Threading.Tasks; +using Shouldly; using Xunit; namespace Volo.Abp.TextTemplating.VirtualFiles @@ -6,7 +7,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase { [Fact] - public void Should_Get_Localized_Content_By_Culture() + public async Task Should_Get_Localized_Content_By_Culture() { var contributor = new VirtualFileTemplateContributor( "/SampleTemplates/WelcomeEmail" @@ -19,17 +20,15 @@ namespace Volo.Abp.TextTemplating.VirtualFiles ) ); - contributor - .GetOrNull("en") - .ShouldBe("Welcome {{model.name}} to the abp.io!"); + (await contributor + .GetOrNullAsync("en")).ShouldBe("Welcome {{model.name}} to the abp.io!"); - contributor - .GetOrNull("tr") - .ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); + (await contributor + .GetOrNullAsync("tr")).ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); } [Fact] - public void Should_Get_Non_Localized_Template_Content() + public async Task Should_Get_Non_Localized_Template_Content() { var contributor = new VirtualFileTemplateContributor( "/SampleTemplates/ForgotPasswordEmail.tpl" @@ -42,9 +41,8 @@ namespace Volo.Abp.TextTemplating.VirtualFiles ) ); - contributor - .GetOrNull() - .ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); + (await contributor + .GetOrNullAsync()).ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); } } } From 1950a8dcc5f1f7e86b6b3434bf5ad77939e2ac09 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Thu, 23 Apr 2020 23:50:11 +0300 Subject: [PATCH 20/52] TemplateRenderer null checking updated --- .../Volo/Abp/TextTemplating/TemplateRenderer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 558104e3a4..8b67a380f0 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -35,10 +35,7 @@ namespace Volo.Abp.TextTemplating { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); - if (cultureName == null) - { - cultureName = CultureInfo.CurrentUICulture.Name; - } + cultureName ??= CultureInfo.CurrentUICulture.Name; using (CultureHelper.Use(cultureName)) { From 0e519b765742e269e0dfff7d5c946bfc2775b412 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Fri, 24 Apr 2020 00:13:43 +0300 Subject: [PATCH 21/52] null checking updated --- .../VirtualFiles/VirtualFileTemplateContributor.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs index ae4b8c92f0..eded060a2e 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -35,10 +35,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles { //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents - if (cultureName == null) - { - cultureName = CultureInfo.CurrentUICulture.Name; - } + cultureName ??= CultureInfo.CurrentUICulture.Name; var dictionary = GetTemplateDictionary(); From 4437bf7ec0e467097312addea99997e726ceb9ac Mon Sep 17 00:00:00 2001 From: Ahmet Date: Fri, 24 Apr 2020 01:01:06 +0300 Subject: [PATCH 22/52] renamed some properties. --- .../Abp/TextTemplating/TemplateDefinitionContext.cs | 12 ++++++------ .../Abp/TextTemplating/TemplateDefinitionManager.cs | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs index 39da2f5e88..59fe7fced1 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs @@ -5,21 +5,21 @@ namespace Volo.Abp.TextTemplating { public class TemplateDefinitionContext : ITemplateDefinitionContext { - protected Dictionary EmailTemplates { get; } + protected Dictionary TextTemplates { get; } - public TemplateDefinitionContext(Dictionary emailTemplates) + public TemplateDefinitionContext(Dictionary textTemplates) { - EmailTemplates = emailTemplates; + TextTemplates = textTemplates; } public virtual TemplateDefinition GetOrNull(string name) { - return EmailTemplates.GetOrDefault(name); + return TextTemplates.GetOrDefault(name); } public virtual IReadOnlyList GetAll() { - return EmailTemplates.Values.ToImmutableList(); + return TextTemplates.Values.ToImmutableList(); } public virtual void Add(params TemplateDefinition[] definitions) @@ -31,7 +31,7 @@ namespace Volo.Abp.TextTemplating foreach (var definition in definitions) { - EmailTemplates[definition.Name] = definition; + TextTemplates[definition.Name] = definition; } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index 534010dbe2..657e227e9c 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.TextTemplating { public class TemplateDefinitionManager : ITemplateDefinitionManager, ISingletonDependency { - protected Lazy> EmailTemplateDefinitions { get; } + protected Lazy> TemplateDefinitions { get; } protected AbpTextTemplatingOptions Options { get; } @@ -23,7 +23,7 @@ namespace Volo.Abp.TextTemplating ServiceProvider = serviceProvider; Options = options.Value; - EmailTemplateDefinitions = + TemplateDefinitions = new Lazy>(CreateEmailTemplateDefinitions, true); } @@ -43,12 +43,12 @@ namespace Volo.Abp.TextTemplating public virtual IReadOnlyList GetAll() { - return EmailTemplateDefinitions.Value.Values.ToImmutableList(); + return TemplateDefinitions.Value.Values.ToImmutableList(); } public virtual TemplateDefinition GetOrNull(string name) { - return EmailTemplateDefinitions.Value.GetOrDefault(name); + return TemplateDefinitions.Value.GetOrDefault(name); } protected virtual IDictionary CreateEmailTemplateDefinitions() From e1990d1a3f2fb6d6e38b01fc73ec997fa5df5cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 16:57:45 +0300 Subject: [PATCH 23/52] rename template contributor to template content contributor --- .../Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs | 4 ++-- ...plateContributor.cs => ITemplateContentContributor.cs} | 4 ++-- ...=> TemplateContentContributorInitializationContext.cs} | 4 ++-- ...ntributorList.cs => TemplateContentContributorList.cs} | 2 +- .../Volo/Abp/TextTemplating/TemplateContentProvider.cs | 2 +- .../Volo/Abp/TextTemplating/TemplateDefinition.cs | 8 ++++---- .../Abp/TextTemplating/TemplateDefinitionExtensions.cs | 2 +- ...ibutor.cs => VirtualFileTemplateContentContributor.cs} | 6 +++--- .../VirtualFiles/VirtualFileTemplateContributor_Tests.cs | 8 ++++---- 9 files changed, 20 insertions(+), 20 deletions(-) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/{ITemplateContributor.cs => ITemplateContentContributor.cs} (57%) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/{TemplateContributorInitializationContext.cs => TemplateContentContributorInitializationContext.cs} (81%) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/{TemplateContributorList.cs => TemplateContentContributorList.cs} (52%) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/{VirtualFileTemplateContributor.cs => VirtualFileTemplateContentContributor.cs} (93%) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index e49f271c1c..831de9c1c4 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -44,12 +44,12 @@ namespace Volo.Abp.TextTemplating foreach (var templateDefinition in templateDefinitionManager.GetAll()) { - var contributorInitializationContext = new TemplateContributorInitializationContext( + var contributorInitializationContext = new TemplateContentContributorInitializationContext( templateDefinition, scope.ServiceProvider ); - foreach (var contributor in templateDefinition.Contributors) + foreach (var contributor in templateDefinition.ContentContributors) { contributor.Initialize(contributorInitializationContext); } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs similarity index 57% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs index 2174a65bcd..26aef67e94 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs @@ -3,9 +3,9 @@ using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { - public interface ITemplateContributor + public interface ITemplateContentContributor { - void Initialize(TemplateContributorInitializationContext context); + void Initialize(TemplateContentContributorInitializationContext context); Task GetOrNullAsync([CanBeNull] string cultureName); } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs similarity index 81% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs index ffe97a3484..523f7a6edf 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs @@ -3,7 +3,7 @@ using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { - public class TemplateContributorInitializationContext + public class TemplateContentContributorInitializationContext { [NotNull] public TemplateDefinition TemplateDefinition { get; } @@ -11,7 +11,7 @@ namespace Volo.Abp.TextTemplating [NotNull] public IServiceProvider ServiceProvider { get; } - public TemplateContributorInitializationContext( + public TemplateContentContributorInitializationContext( [NotNull] TemplateDefinition templateDefinition, [NotNull] IServiceProvider serviceProvider) { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs similarity index 52% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs index e737897dae..3c67a03dcc 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.TextTemplating { - public class TemplateContributorList : List + public class TemplateContentContributorList : List { } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index b983c75f11..7c7031c980 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -29,7 +29,7 @@ namespace Volo.Abp.TextTemplating { Check.NotNull(templateDefinition, nameof(templateDefinition)); - foreach (var contributor in templateDefinition.Contributors) + foreach (var contributor in templateDefinition.ContentContributors) { var templateString = await contributor.GetOrNullAsync(cultureName); if (templateString != null) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 09de7bf458..4c4703d5e3 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -16,7 +16,7 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public Type LocalizationResource { get; set; } - public TemplateContributorList Contributors { get; } + public TemplateContentContributorList ContentContributors { get; } [CanBeNull] public string DefaultCultureName { get; } @@ -30,15 +30,15 @@ namespace Volo.Abp.TextTemplating { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); LocalizationResource = localizationResource; - Contributors = new TemplateContributorList(); + ContentContributors = new TemplateContentContributorList(); IsLayout = isLayout; Layout = layout; DefaultCultureName = defaultCultureName; } - public virtual TemplateDefinition WithContributor(ITemplateContributor contributor) + public virtual TemplateDefinition WithContributor(ITemplateContentContributor contentContributor) { - Contributors.Add(contributor); + ContentContributors.Add(contentContributor); return this; } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs index 9bd932c947..c9a644fedc 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -18,7 +18,7 @@ namespace Volo.Abp.TextTemplating Check.NotNull(templateDefinition, nameof(templateDefinition)); return templateDefinition.WithContributor( - new VirtualFileTemplateContributor(virtualPath) + new VirtualFileTemplateContentContributor(virtualPath) ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs similarity index 93% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index eded060a2e..5a6958e29b 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -10,7 +10,7 @@ using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class VirtualFileTemplateContributor : ITemplateContributor + public class VirtualFileTemplateContentContributor : ITemplateContentContributor { public TemplateDefinition TemplateDefinition { get; private set; } @@ -19,13 +19,13 @@ namespace Volo.Abp.TextTemplating.VirtualFiles private volatile Dictionary _templateDictionary; private readonly object _syncObj = new object(); - public VirtualFileTemplateContributor( + public VirtualFileTemplateContentContributor( [NotNull] string virtualPath) { _virtualPath = Check.NotNullOrWhiteSpace(virtualPath, nameof(virtualPath)); } - public void Initialize(TemplateContributorInitializationContext context) + public void Initialize(TemplateContentContributorInitializationContext context) { _virtualFileProvider = context.ServiceProvider.GetRequiredService(); TemplateDefinition = context.TemplateDefinition; diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 1ff2596342..0bf54a5bee 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -9,12 +9,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles [Fact] public async Task Should_Get_Localized_Content_By_Culture() { - var contributor = new VirtualFileTemplateContributor( + var contributor = new VirtualFileTemplateContentContributor( "/SampleTemplates/WelcomeEmail" ); contributor.Initialize( - new TemplateContributorInitializationContext( + new TemplateContentContributorInitializationContext( new TemplateDefinition("Test"), ServiceProvider ) @@ -30,12 +30,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles [Fact] public async Task Should_Get_Non_Localized_Template_Content() { - var contributor = new VirtualFileTemplateContributor( + var contributor = new VirtualFileTemplateContentContributor( "/SampleTemplates/ForgotPasswordEmail.tpl" ); contributor.Initialize( - new TemplateContributorInitializationContext( + new TemplateContentContributorInitializationContext( new TemplateDefinition("Test"), ServiceProvider ) From f3d82a3807d0e834c4298cdf5cc3fd33f971e834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 17:24:18 +0300 Subject: [PATCH 24/52] Refactor text template contribution system. --- .../TextTemplating/AbpTextTemplatingModule.cs | 34 ++------ .../AbpTextTemplatingOptions.cs | 2 + .../ITemplateContentContributor.cs | 5 +- .../TemplateContentContributorContext.cs | 27 ++++++ ...ContentContributorInitializationContext.cs | 13 --- .../TextTemplating/TemplateContentProvider.cs | 45 ++++++++-- .../Abp/TextTemplating/TemplateDefinition.cs | 34 ++++++-- .../TemplateDefinitionExtensions.cs | 5 +- .../VirtualFileTemplateContentContributor.cs | 84 ++++++++----------- .../VirtualFileTemplateContributor_Tests.cs | 71 ++++++++-------- 10 files changed, 176 insertions(+), 144 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index 831de9c1c4..aca0ac8f53 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -13,12 +13,13 @@ namespace Volo.Abp.TextTemplating { public override void PreConfigureServices(ServiceConfigurationContext context) { - AutoAddDefinitionProviders(context.Services); + AutoAddProvidersAndContributors(context.Services); } - private static void AutoAddDefinitionProviders(IServiceCollection services) + private static void AutoAddProvidersAndContributors(IServiceCollection services) { var definitionProviders = new List(); + var contentContributors = new List(); services.OnRegistred(context => { @@ -26,35 +27,18 @@ namespace Volo.Abp.TextTemplating { definitionProviders.Add(context.ImplementationType); } + + if (typeof(ITemplateContentContributor).IsAssignableFrom(context.ImplementationType)) + { + contentContributors.Add(context.ImplementationType); + } }); services.Configure(options => { options.DefinitionProviders.AddIfNotContains(definitionProviders); + options.ContentContributors.AddIfNotContains(contentContributors); }); } - - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - //TODO: Consider to move to the TemplateContentProvider and invoke lazy (with making it singleton) - using (var scope = context.ServiceProvider.CreateScope()) - { - var templateDefinitionManager = scope.ServiceProvider - .GetRequiredService(); - - foreach (var templateDefinition in templateDefinitionManager.GetAll()) - { - var contributorInitializationContext = new TemplateContentContributorInitializationContext( - templateDefinition, - scope.ServiceProvider - ); - - foreach (var contributor in templateDefinition.ContentContributors) - { - contributor.Initialize(contributorInitializationContext); - } - } - } - } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs index a79b66e255..b217094974 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs @@ -5,10 +5,12 @@ namespace Volo.Abp.TextTemplating public class AbpTextTemplatingOptions { public ITypeList DefinitionProviders { get; } + public ITypeList ContentContributors { get; } public AbpTextTemplatingOptions() { DefinitionProviders = new TypeList(); + ContentContributors = new TypeList(); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs index 26aef67e94..746b29a2f0 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs @@ -1,12 +1,9 @@ using System.Threading.Tasks; -using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { public interface ITemplateContentContributor { - void Initialize(TemplateContentContributorInitializationContext context); - - Task GetOrNullAsync([CanBeNull] string cultureName); + Task GetOrNullAsync(TemplateContentContributorContext context); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs new file mode 100644 index 0000000000..773bf1a0a4 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs @@ -0,0 +1,27 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateContentContributorContext + { + [NotNull] + public TemplateDefinition TemplateDefinition { get; } + + [NotNull] + public IServiceProvider ServiceProvider { get; } + + [CanBeNull] + public string Culture { get; } + + public TemplateContentContributorContext( + [NotNull] TemplateDefinition templateDefinition, + [NotNull] IServiceProvider serviceProvider, + [CanBeNull] string culture) + { + TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); + ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); + Culture = culture; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs index 523f7a6edf..bf6ea65909 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs @@ -5,18 +5,5 @@ namespace Volo.Abp.TextTemplating { public class TemplateContentContributorInitializationContext { - [NotNull] - public TemplateDefinition TemplateDefinition { get; } - - [NotNull] - public IServiceProvider ServiceProvider { get; } - - public TemplateContentContributorInitializationContext( - [NotNull] TemplateDefinition templateDefinition, - [NotNull] IServiceProvider serviceProvider) - { - TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); - ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); - } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 7c7031c980..d7d7982f16 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -1,17 +1,25 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating { public class TemplateContentProvider : ITemplateContentProvider, ITransientDependency { + public IHybridServiceScopeFactory ServiceScopeFactory { get; } + public AbpTextTemplatingOptions Options { get; } private readonly ITemplateDefinitionManager _templateDefinitionManager; public TemplateContentProvider( - ITemplateDefinitionManager templateDefinitionManager - ) + ITemplateDefinitionManager templateDefinitionManager, + IHybridServiceScopeFactory serviceScopeFactory, + IOptions options) { + ServiceScopeFactory = serviceScopeFactory; + Options = options.Value; _templateDefinitionManager = templateDefinitionManager; } @@ -29,17 +37,36 @@ namespace Volo.Abp.TextTemplating { Check.NotNull(templateDefinition, nameof(templateDefinition)); - foreach (var contributor in templateDefinition.ContentContributors) + if (!Options.ContentContributors.Any()) { - var templateString = await contributor.GetOrNullAsync(cultureName); - if (templateString != null) + throw new AbpException( + $"No template content contributor was registered. Use {nameof(AbpTextTemplatingOptions)} to register contributors!" + ); + } + + using (var scope = ServiceScopeFactory.CreateScope()) + { + var context = new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + cultureName + ); + + foreach (var contentContributorType in Options.ContentContributors) { - return templateString; + var contributor = (ITemplateContentContributor) scope.ServiceProvider + .GetRequiredService(contentContributorType); + + var templateString = await contributor.GetOrNullAsync(context); + if (templateString != null) + { + return templateString; + } } } - + throw new AbpException( - $"None of the template contributors could get the content for the template '{templateDefinition.Name}'" + $"None of the template content contributors could get the content for the template '{templateDefinition.Name}'" ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 4c4703d5e3..d165eb4d31 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using JetBrains.Annotations; namespace Volo.Abp.TextTemplating @@ -16,11 +17,30 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public Type LocalizationResource { get; set; } - public TemplateContentContributorList ContentContributors { get; } - [CanBeNull] public string DefaultCultureName { get; } + /// + /// Gets/sets a key-value on the . + /// + /// Name of the property + /// + /// Returns the value in the dictionary by given . + /// Returns null if given is not present in the dictionary. + /// + [CanBeNull] + public object this[string name] + { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } + + /// + /// Can be used to get/set custom properties for this feature. + /// + [NotNull] + public Dictionary Properties { get; } + public TemplateDefinition( [NotNull] string name, [CanBeNull] Type localizationResource = null, @@ -30,15 +50,19 @@ namespace Volo.Abp.TextTemplating { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); LocalizationResource = localizationResource; - ContentContributors = new TemplateContentContributorList(); IsLayout = isLayout; Layout = layout; DefaultCultureName = defaultCultureName; + Properties = new Dictionary(); } - public virtual TemplateDefinition WithContributor(ITemplateContentContributor contentContributor) + /// + /// Sets a property in the dictionary. + /// This is a shortcut for nested calls on this object. + /// + public virtual TemplateDefinition WithProperty(string key, object value) { - ContentContributors.Add(contentContributor); + Properties[key] = value; return this; } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs index c9a644fedc..ef4a0ac9a3 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -17,8 +17,9 @@ namespace Volo.Abp.TextTemplating { Check.NotNull(templateDefinition, nameof(templateDefinition)); - return templateDefinition.WithContributor( - new VirtualFileTemplateContentContributor(virtualPath) + return templateDefinition.WithProperty( + VirtualFileTemplateContentContributor.VirtualPathPropertyName, + virtualPath ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index 5a6958e29b..6d373dfec1 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -2,42 +2,38 @@ using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Volo.Abp.DependencyInjection; using Volo.Abp.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class VirtualFileTemplateContentContributor : ITemplateContentContributor + public class VirtualFileTemplateContentContributor : ITemplateContentContributor, ITransientDependency { - public TemplateDefinition TemplateDefinition { get; private set; } + public const string VirtualPathPropertyName = "VirtualPath"; - private readonly string _virtualPath; - private IVirtualFileProvider _virtualFileProvider; - private volatile Dictionary _templateDictionary; - private readonly object _syncObj = new object(); + private readonly IVirtualFileProvider _virtualFileProvider; - public VirtualFileTemplateContentContributor( - [NotNull] string virtualPath) + public VirtualFileTemplateContentContributor(IVirtualFileProvider virtualFileProvider) { - _virtualPath = Check.NotNullOrWhiteSpace(virtualPath, nameof(virtualPath)); + _virtualFileProvider = virtualFileProvider; } - public void Initialize(TemplateContentContributorInitializationContext context) + public async Task GetOrNullAsync(TemplateContentContributorContext context) { - _virtualFileProvider = context.ServiceProvider.GetRequiredService(); - TemplateDefinition = context.TemplateDefinition; - } + var virtualPath = context.TemplateDefinition.Properties.GetOrDefault(VirtualPathPropertyName) as string; + if (virtualPath == null) + { + return null; + } - public async Task GetOrNullAsync([CanBeNull] string cultureName = null) - { //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents - cultureName ??= CultureInfo.CurrentUICulture.Name; + var cultureName = context.Culture ?? + CultureInfo.CurrentUICulture.Name; - var dictionary = GetTemplateDictionary(); + var dictionary = GetTemplateDictionary(virtualPath); var content = dictionary.GetOrDefault(cultureName); if (content != null) @@ -55,9 +51,9 @@ namespace Volo.Abp.TextTemplating.VirtualFiles } } - if (TemplateDefinition.DefaultCultureName != null) + if (context.TemplateDefinition.DefaultCultureName != null) { - content = dictionary.GetOrDefault(TemplateDefinition.DefaultCultureName); + content = dictionary.GetOrDefault(context.TemplateDefinition.DefaultCultureName); if (content != null) { return content; @@ -67,45 +63,31 @@ namespace Volo.Abp.TextTemplating.VirtualFiles return dictionary.GetOrDefault("__default"); } - private Dictionary GetTemplateDictionary() + private Dictionary GetTemplateDictionary(string virtualPath) { - if (_templateDictionary != null) + var dictionary = new Dictionary(); + + var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.IsDirectory) { - return _templateDictionary; + //TODO: __default to consts + dictionary.Add("__default", fileInfo.ReadAsString()); } - - lock (_syncObj) + else { - if (_templateDictionary != null) - { - return _templateDictionary; - } - - var dictionary = new Dictionary(); - - var fileInfo = _virtualFileProvider.GetFileInfo(_virtualPath); - if (!fileInfo.IsDirectory) - { - //TODO: __default to consts - dictionary.Add("__default", fileInfo.ReadAsString()); - } - else + foreach (var file in _virtualFileProvider.GetDirectoryContents(virtualPath)) { - foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) + if (file.IsDirectory) { - if (file.IsDirectory) - { - continue; - } - - // TODO: How to normalize file names? - dictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); + continue; } - } - _templateDictionary = dictionary; - return dictionary; + // TODO: How to normalize file names? + dictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); + } } + + return dictionary; } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 0bf54a5bee..da2d4179de 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -4,45 +4,46 @@ using Xunit; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase - { - [Fact] - public async Task Should_Get_Localized_Content_By_Culture() - { - var contributor = new VirtualFileTemplateContentContributor( - "/SampleTemplates/WelcomeEmail" - ); + //TODO: Make tests running again! + //public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase + //{ + // [Fact] + // public async Task Should_Get_Localized_Content_By_Culture() + // { + // var contributor = new VirtualFileTemplateContentContributor( + // "/SampleTemplates/WelcomeEmail" + // ); - contributor.Initialize( - new TemplateContentContributorInitializationContext( - new TemplateDefinition("Test"), - ServiceProvider - ) - ); + // contributor.Initialize( + // new TemplateContentContributorInitializationContext( + // new TemplateDefinition("Test"), + // ServiceProvider + // ) + // ); - (await contributor - .GetOrNullAsync("en")).ShouldBe("Welcome {{model.name}} to the abp.io!"); + // (await contributor + // .GetOrNullAsync("en")).ShouldBe("Welcome {{model.name}} to the abp.io!"); - (await contributor - .GetOrNullAsync("tr")).ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); - } + // (await contributor + // .GetOrNullAsync("tr")).ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); + // } - [Fact] - public async Task Should_Get_Non_Localized_Template_Content() - { - var contributor = new VirtualFileTemplateContentContributor( - "/SampleTemplates/ForgotPasswordEmail.tpl" - ); + // [Fact] + // public async Task Should_Get_Non_Localized_Template_Content() + // { + // var contributor = new VirtualFileTemplateContentContributor( + // "/SampleTemplates/ForgotPasswordEmail.tpl" + // ); - contributor.Initialize( - new TemplateContentContributorInitializationContext( - new TemplateDefinition("Test"), - ServiceProvider - ) - ); + // contributor.Initialize( + // new TemplateContentContributorInitializationContext( + // new TemplateDefinition("Test"), + // ServiceProvider + // ) + // ); - (await contributor - .GetOrNullAsync()).ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); - } - } + // (await contributor + // .GetOrNullAsync()).ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); + // } + //} } From 1dde0dbe2352bcd29795b704d5fd7c0dc0150fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 17:31:36 +0300 Subject: [PATCH 25/52] Added PreDefine and PostDefine methods for the ITemplateDefinitionProvider --- .../TextTemplating/ITemplateDefinitionContext.cs | 6 +++++- .../TextTemplating/ITemplateDefinitionProvider.cs | 4 ++++ .../TextTemplating/TemplateDefinitionContext.cs | 5 +++++ .../TextTemplating/TemplateDefinitionManager.cs | 14 +++++++++++++- .../TextTemplating/TemplateDefinitionProvider.cs | 10 ++++++++++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs index 18402f8f9d..515cee74ca 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs @@ -1,7 +1,11 @@ -namespace Volo.Abp.TextTemplating +using System.Collections.Generic; + +namespace Volo.Abp.TextTemplating { public interface ITemplateDefinitionContext { + IReadOnlyList GetAll(string name); + TemplateDefinition GetOrNull(string name); void Add(params TemplateDefinition[] definitions); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs index a9e5155d0c..aeb6a3c2da 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs @@ -2,6 +2,10 @@ { public interface ITemplateDefinitionProvider { + void PreDefine(ITemplateDefinitionContext context); + void Define(ITemplateDefinitionContext context); + + void PostDefine(ITemplateDefinitionContext context); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs index 59fe7fced1..d0911f21ac 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs @@ -12,6 +12,11 @@ namespace Volo.Abp.TextTemplating TextTemplates = textTemplates; } + public IReadOnlyList GetAll(string name) + { + return TextTemplates.Values.ToImmutableList(); + } + public virtual TemplateDefinition GetOrNull(string name) { return TextTemplates.GetOrDefault(name); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index 657e227e9c..224b220396 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -62,9 +62,21 @@ namespace Volo.Abp.TextTemplating .Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) .ToList(); + var context = new TemplateDefinitionContext(templates); + + foreach (var provider in providers) + { + provider.PreDefine(context); + } + + foreach (var provider in providers) + { + provider.Define(context); + } + foreach (var provider in providers) { - provider.Define(new TemplateDefinitionContext(templates)); + provider.PostDefine(context); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs index 9afc22a8b8..924a6dfdd8 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs @@ -4,6 +4,16 @@ namespace Volo.Abp.TextTemplating { public abstract class TemplateDefinitionProvider : ITemplateDefinitionProvider, ITransientDependency { + public virtual void PreDefine(ITemplateDefinitionContext context) + { + + } + public abstract void Define(ITemplateDefinitionContext context); + + public virtual void PostDefine(ITemplateDefinitionContext context) + { + + } } } \ No newline at end of file From 8af5a54ad2b3f74a9883e3f6d59366c5b7777a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 18:42:57 +0300 Subject: [PATCH 26/52] Refactored VirtualFileTemplateContentContributor --- .../FolderLocalizedTemplateContentReader.cs | 65 ++++++++++++++ .../ILocalizedTemplateContentReader.cs | 7 ++ .../NullLocalizedTemplateContentReader.cs | 19 +++++ ...ingleFileLocalizedTemplateContentReader.cs | 20 +++++ .../VirtualFileTemplateContentContributor.cs | 85 +++++++------------ .../FileProviders/AbpFileInfoExtensions.cs | 24 ++++++ 6 files changed, 164 insertions(+), 56 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..65bb2423a8 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.FileProviders; +using Volo.Abp.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class FolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader + { + private Dictionary _dictionary; + + public async Task ReadContentsAsync(IVirtualFileProvider virtualFileProvider, string virtualPath) + { + _dictionary = new Dictionary(); + + var directoryInfo = virtualFileProvider.GetFileInfo(virtualPath); + if (!directoryInfo.IsDirectory) + { + throw new AbpException("Given virtual path is not a folder: " + virtualPath); + } + + foreach (var file in virtualFileProvider.GetDirectoryContents(virtualPath)) + { + if (file.IsDirectory) + { + continue; + } + + _dictionary.Add(file.Name.RemovePostFix(".tpl"), await file.ReadAsStringAsync()); + } + } + + public string GetContent(string cultureName, string defaultCultureName) + { + var content = _dictionary.GetOrDefault(cultureName); + if (content != null) + { + return content; + } + + if (cultureName.Contains("-")) + { + var baseCultureName = CultureHelper.GetBaseCultureName(cultureName); + content = _dictionary.GetOrDefault(baseCultureName); + if (content != null) + { + return content; + } + } + + if (defaultCultureName != null) + { + content = _dictionary.GetOrDefault(defaultCultureName); + if (content != null) + { + return content; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..0b028138cd --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public interface ILocalizedTemplateContentReader + { + public string GetContent(string culture, string defaultCultureName = null); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..0578a716ae --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs @@ -0,0 +1,19 @@ +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class NullLocalizedTemplateContentReader : ILocalizedTemplateContentReader + { + public static NullLocalizedTemplateContentReader Instance { get; } = new NullLocalizedTemplateContentReader(); + + private NullLocalizedTemplateContentReader() + { + + } + + public string GetContent( + string culture, + string defaultCultureName = null) + { + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..23e4bfc14a --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.FileProviders; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class SingleFileLocalizedTemplateContentReader : ILocalizedTemplateContentReader + { + private string _content; + + public async Task ReadContentsAsync(IFileInfo fileInfo) + { + _content = await fileInfo.ReadAsStringAsync(); + } + + public string GetContent(string culture, string defaultCultureName) + { + return _content; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index 6d373dfec1..dea4920447 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; -using Microsoft.Extensions.FileProviders; using Volo.Abp.DependencyInjection; -using Volo.Abp.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles @@ -20,74 +17,50 @@ namespace Volo.Abp.TextTemplating.VirtualFiles _virtualFileProvider = virtualFileProvider; } - public async Task GetOrNullAsync(TemplateContentContributorContext context) + public virtual async Task GetOrNullAsync(TemplateContentContributorContext context) { - var virtualPath = context.TemplateDefinition.Properties.GetOrDefault(VirtualPathPropertyName) as string; - if (virtualPath == null) - { - return null; - } - - //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents - var cultureName = context.Culture ?? CultureInfo.CurrentUICulture.Name; - var dictionary = GetTemplateDictionary(virtualPath); + var localizedReader = await CreateLocalizedReader(context); - var content = dictionary.GetOrDefault(cultureName); - if (content != null) - { - return content; - } + return localizedReader.GetContent( + cultureName, + context.TemplateDefinition.DefaultCultureName + ); + } + + protected async Task CreateLocalizedReader( + TemplateContentContributorContext context) + { + var virtualPath = context + .TemplateDefinition + .Properties + .GetOrDefault(VirtualPathPropertyName) as string; - if (cultureName.Contains("-")) + if (virtualPath == null) { - var baseCultureName = CultureHelper.GetBaseCultureName(cultureName); - content = dictionary.GetOrDefault(baseCultureName); - if (content != null) - { - return content; - } + return NullLocalizedTemplateContentReader.Instance; } - if (context.TemplateDefinition.DefaultCultureName != null) + var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) { - content = dictionary.GetOrDefault(context.TemplateDefinition.DefaultCultureName); - if (content != null) - { - return content; - } + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); } - return dictionary.GetOrDefault("__default"); - } - - private Dictionary GetTemplateDictionary(string virtualPath) - { - var dictionary = new Dictionary(); - - var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); - if (!fileInfo.IsDirectory) + if (fileInfo.IsDirectory) { - //TODO: __default to consts - dictionary.Add("__default", fileInfo.ReadAsString()); + var folderReader = new FolderLocalizedTemplateContentReader(); + await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); + return folderReader; } - else + else //File { - foreach (var file in _virtualFileProvider.GetDirectoryContents(virtualPath)) - { - if (file.IsDirectory) - { - continue; - } - - // TODO: How to normalize file names? - dictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); - } + var singleFileReader = new SingleFileLocalizedTemplateContentReader(); + await singleFileReader.ReadContentsAsync(fileInfo); + return singleFileReader; } - - return dictionary; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs b/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs index 7d3505d616..8a6d7d57a1 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs @@ -18,6 +18,14 @@ namespace Microsoft.Extensions.FileProviders return fileInfo.ReadAsString(Encoding.UTF8); } + /// + /// Reads file content as string using encoding. + /// + public static Task ReadAsStringAsync([NotNull] this IFileInfo fileInfo) + { + return fileInfo.ReadAsStringAsync(Encoding.UTF8); + } + /// /// Reads file content as string using the given . /// @@ -34,6 +42,22 @@ namespace Microsoft.Extensions.FileProviders } } + /// + /// Reads file content as string using the given . + /// + public static async Task ReadAsStringAsync([NotNull] this IFileInfo fileInfo, Encoding encoding) + { + Check.NotNull(fileInfo, nameof(fileInfo)); + + using (var stream = fileInfo.CreateReadStream()) + { + using (var streamReader = new StreamReader(stream, encoding, true)) + { + return await streamReader.ReadToEndAsync(); + } + } + } + /// /// Reads file content as byte[]. /// From a29cdda2c53de46579e4044b8a3ee5b4c33d17e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 18:51:51 +0300 Subject: [PATCH 27/52] Extract LocalizedTemplateContentReaderFactory --- .../ILocalizedTemplateContentReaderFactory.cs | 9 ++++ .../LocalizedTemplateContentReaderFactory.cs | 54 +++++++++++++++++++ .../VirtualFileTemplateContentContributor.cs | 51 +++--------------- 3 files changed, 70 insertions(+), 44 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs new file mode 100644 index 0000000000..2d4d9ef2d5 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public interface ILocalizedTemplateContentReaderFactory + { + Task CreateAsync(TemplateDefinition templateDefinition); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs new file mode 100644 index 0000000000..41527c4e11 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class LocalizedTemplateContentReaderFactory : ILocalizedTemplateContentReaderFactory, ISingletonDependency + { + private readonly IVirtualFileProvider _virtualFileProvider; + + public LocalizedTemplateContentReaderFactory(IVirtualFileProvider virtualFileProvider) + { + _virtualFileProvider = virtualFileProvider; + } + + public Task CreateAsync(TemplateDefinition templateDefinition) + { + return CreateLocalizedReader(templateDefinition); + } + + protected async Task CreateLocalizedReader( + TemplateDefinition templateDefinition) + { + var virtualPath = templateDefinition + .Properties + .GetOrDefault(VirtualFileTemplateContentContributor.VirtualPathPropertyName) as string; + + if (virtualPath == null) + { + return NullLocalizedTemplateContentReader.Instance; + } + + var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) + { + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); + } + + if (fileInfo.IsDirectory) + { + var folderReader = new FolderLocalizedTemplateContentReader(); + await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); + return folderReader; + } + else //File + { + var singleFileReader = new SingleFileLocalizedTemplateContentReader(); + await singleFileReader.ReadContentsAsync(fileInfo); + return singleFileReader; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index dea4920447..1240352bd2 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -1,8 +1,6 @@ -using System.Collections.Generic; -using System.Globalization; +using System.Globalization; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; -using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { @@ -10,57 +8,22 @@ namespace Volo.Abp.TextTemplating.VirtualFiles { public const string VirtualPathPropertyName = "VirtualPath"; - private readonly IVirtualFileProvider _virtualFileProvider; + private readonly ILocalizedTemplateContentReaderFactory _localizedTemplateContentReaderFactory; - public VirtualFileTemplateContentContributor(IVirtualFileProvider virtualFileProvider) + public VirtualFileTemplateContentContributor(ILocalizedTemplateContentReaderFactory localizedTemplateContentReaderFactory) { - _virtualFileProvider = virtualFileProvider; + _localizedTemplateContentReaderFactory = localizedTemplateContentReaderFactory; } public virtual async Task GetOrNullAsync(TemplateContentContributorContext context) { - var cultureName = context.Culture ?? - CultureInfo.CurrentUICulture.Name; - - var localizedReader = await CreateLocalizedReader(context); + var localizedReader = await _localizedTemplateContentReaderFactory + .CreateAsync(context.TemplateDefinition); return localizedReader.GetContent( - cultureName, + context.Culture ?? CultureInfo.CurrentUICulture.Name, context.TemplateDefinition.DefaultCultureName ); } - - protected async Task CreateLocalizedReader( - TemplateContentContributorContext context) - { - var virtualPath = context - .TemplateDefinition - .Properties - .GetOrDefault(VirtualPathPropertyName) as string; - - if (virtualPath == null) - { - return NullLocalizedTemplateContentReader.Instance; - } - - var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); - if (!fileInfo.Exists) - { - throw new AbpException("Could not find a file/folder at the location: " + virtualPath); - } - - if (fileInfo.IsDirectory) - { - var folderReader = new FolderLocalizedTemplateContentReader(); - await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); - return folderReader; - } - else //File - { - var singleFileReader = new SingleFileLocalizedTemplateContentReader(); - await singleFileReader.ReadContentsAsync(fileInfo); - return singleFileReader; - } - } } } \ No newline at end of file From 15ce08853d881694f5a5c2f470e8f6da7dc636ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 18:53:39 +0300 Subject: [PATCH 28/52] Rename template content readers. --- ...tReader.cs => FileInfoLocalizedTemplateContentReader.cs} | 2 +- .../VirtualFiles/LocalizedTemplateContentReaderFactory.cs | 4 ++-- .../VirtualFiles/VirtualFileTemplateContentContributor.cs | 3 ++- ...er.cs => VirtualFolderLocalizedTemplateContentReader.cs} | 6 ++++-- 4 files changed, 9 insertions(+), 6 deletions(-) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/{SingleFileLocalizedTemplateContentReader.cs => FileInfoLocalizedTemplateContentReader.cs} (82%) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/{FolderLocalizedTemplateContentReader.cs => VirtualFolderLocalizedTemplateContentReader.cs} (88%) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs similarity index 82% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs index 23e4bfc14a..4ff941fb84 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.FileProviders; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class SingleFileLocalizedTemplateContentReader : ILocalizedTemplateContentReader + public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentReader { private string _content; diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs index 41527c4e11..b22a6fb7d5 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs @@ -39,13 +39,13 @@ namespace Volo.Abp.TextTemplating.VirtualFiles if (fileInfo.IsDirectory) { - var folderReader = new FolderLocalizedTemplateContentReader(); + var folderReader = new VirtualFolderLocalizedTemplateContentReader(); await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); return folderReader; } else //File { - var singleFileReader = new SingleFileLocalizedTemplateContentReader(); + var singleFileReader = new FileInfoLocalizedTemplateContentReader(); await singleFileReader.ReadContentsAsync(fileInfo); return singleFileReader; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index 1240352bd2..e06336c658 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -10,7 +10,8 @@ namespace Volo.Abp.TextTemplating.VirtualFiles private readonly ILocalizedTemplateContentReaderFactory _localizedTemplateContentReaderFactory; - public VirtualFileTemplateContentContributor(ILocalizedTemplateContentReaderFactory localizedTemplateContentReaderFactory) + public VirtualFileTemplateContentContributor( + ILocalizedTemplateContentReaderFactory localizedTemplateContentReaderFactory) { _localizedTemplateContentReaderFactory = localizedTemplateContentReaderFactory; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs similarity index 88% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs index 65bb2423a8..0f2a23a2fa 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs @@ -7,11 +7,13 @@ using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class FolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader + public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader { private Dictionary _dictionary; - public async Task ReadContentsAsync(IVirtualFileProvider virtualFileProvider, string virtualPath) + public async Task ReadContentsAsync( + IVirtualFileProvider virtualFileProvider, + string virtualPath) { _dictionary = new Dictionary(); From f75e7baef6229e789d4b6a580bc1a159a32e5e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 19:04:16 +0300 Subject: [PATCH 29/52] Added caching for the LocalizedTemplateContentReaderFactory. --- .../LocalizedTemplateContentReaderFactory.cs | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs index b22a6fb7d5..f4f8384777 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.VirtualFileSystem; @@ -8,18 +11,48 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public class LocalizedTemplateContentReaderFactory : ILocalizedTemplateContentReaderFactory, ISingletonDependency { private readonly IVirtualFileProvider _virtualFileProvider; + private readonly Dictionary _readerCache; + private readonly ReaderWriterLockSlim _lock; public LocalizedTemplateContentReaderFactory(IVirtualFileProvider virtualFileProvider) { _virtualFileProvider = virtualFileProvider; + _readerCache = new Dictionary(); + _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); } - public Task CreateAsync(TemplateDefinition templateDefinition) + public async Task CreateAsync(TemplateDefinition templateDefinition) { - return CreateLocalizedReader(templateDefinition); + _lock.EnterUpgradeableReadLock(); + + try + { + var reader = _readerCache.GetOrDefault(templateDefinition.Name); + if (reader != null) + { + return reader; + } + + _lock.EnterWriteLock(); + + try + { + reader = await CreateInternalAsync(templateDefinition); + _readerCache[templateDefinition.Name] = reader; + return reader; + } + finally + { + _lock.ExitWriteLock(); + } + } + finally + { + _lock.ExitUpgradeableReadLock(); + } } - protected async Task CreateLocalizedReader( + protected virtual async Task CreateInternalAsync( TemplateDefinition templateDefinition) { var virtualPath = templateDefinition From da78d48b101dca381ad4a95ba9070f22fb60c065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 19:17:10 +0300 Subject: [PATCH 30/52] Remove unused classes of the text template system --- .../TemplateContentContributorInitializationContext.cs | 9 --------- .../Abp/TextTemplating/TemplateContentContributorList.cs | 9 --------- 2 files changed, 18 deletions(-) delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs deleted file mode 100644 index bf6ea65909..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Volo.Abp.TextTemplating -{ - public class TemplateContentContributorInitializationContext - { - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs deleted file mode 100644 index 3c67a03dcc..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.TextTemplating -{ - public class TemplateContentContributorList : List - { - - } -} \ No newline at end of file From fe35f33265937df6c4fc30c85fe497e5409ae07f Mon Sep 17 00:00:00 2001 From: Ahmet Date: Tue, 28 Apr 2020 21:57:07 +0300 Subject: [PATCH 31/52] NameFix --- .../Volo/Abp/TextTemplating/TemplateDefinitionManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index 224b220396..28909330fe 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -24,7 +24,7 @@ namespace Volo.Abp.TextTemplating Options = options.Value; TemplateDefinitions = - new Lazy>(CreateEmailTemplateDefinitions, true); + new Lazy>(CreateTextTemplateDefinitions, true); } public virtual TemplateDefinition Get(string name) @@ -51,7 +51,7 @@ namespace Volo.Abp.TextTemplating return TemplateDefinitions.Value.GetOrDefault(name); } - protected virtual IDictionary CreateEmailTemplateDefinitions() + protected virtual IDictionary CreateTextTemplateDefinitions() { var templates = new Dictionary(); From 41d1e12a747933df42c5cf0c490ed4109dcb161b Mon Sep 17 00:00:00 2001 From: Ahmet Date: Wed, 29 Apr 2020 00:17:05 +0300 Subject: [PATCH 32/52] contributors reversed for requesting content --- .../Volo/Abp/TextTemplating/TemplateContentProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index d7d7982f16..fdb6a3c9dd 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -52,7 +52,7 @@ namespace Volo.Abp.TextTemplating cultureName ); - foreach (var contentContributorType in Options.ContentContributors) + foreach (var contentContributorType in Options.ContentContributors.Reverse()) { var contributor = (ITemplateContentContributor) scope.ServiceProvider .GetRequiredService(contentContributorType); From 9a0ecfb9948603d36f67c16f511b60022375727b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 12:03:35 +0300 Subject: [PATCH 33/52] Remove old TODO --- .../Abp/Localization/LocalizationResourceContributorList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs index daa6abe667..a81adf3562 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.Localization { public LocalizedString GetOrNull(string cultureName, string name) { - foreach (var contributor in this.AsQueryable().Reverse()) //TODO: Reverse? + foreach (var contributor in this.AsQueryable().Reverse()) { var localString = contributor.GetOrNull(cultureName, name); if (localString != null) From 5aec15a123940977301320b0c99087e10969f35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 12:54:25 +0300 Subject: [PATCH 34/52] Move culture fallback logic to the TemplateContentProvider --- .../ITemplateContentProvider.cs | 6 +- .../TemplateContentContributorContext.cs | 6 +- .../TextTemplating/TemplateContentProvider.cs | 101 +++++++++++++++--- .../FileInfoLocalizedTemplateContentReader.cs | 2 +- .../ILocalizedTemplateContentReader.cs | 2 +- .../NullLocalizedTemplateContentReader.cs | 4 +- .../VirtualFileTemplateContentContributor.cs | 5 +- ...ualFolderLocalizedTemplateContentReader.cs | 30 +----- 8 files changed, 98 insertions(+), 58 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs index 12c8f8f8d8..b352fc0503 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -7,12 +7,14 @@ namespace Volo.Abp.TextTemplating { Task GetContentOrNullAsync( [NotNull] string templateName, - [CanBeNull] string cultureName = null + [CanBeNull] string cultureName = null, + bool tryDefaults = true ); Task GetContentOrNullAsync( [NotNull] TemplateDefinition templateDefinition, - [CanBeNull] string cultureName = null + [CanBeNull] string cultureName = null, + bool tryDefaults = true ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs index 773bf1a0a4..bb304a3344 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs @@ -11,17 +11,17 @@ namespace Volo.Abp.TextTemplating [NotNull] public IServiceProvider ServiceProvider { get; } - [CanBeNull] + [NotNull] public string Culture { get; } public TemplateContentContributorContext( [NotNull] TemplateDefinition templateDefinition, [NotNull] IServiceProvider serviceProvider, - [CanBeNull] string culture) + [NotNull] string culture) { TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); - Culture = culture; + Culture = Check.NotNull(culture, nameof(culture)); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index fdb6a3c9dd..dcb5d88850 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -1,9 +1,11 @@ -using System.Linq; +using System.Globalization; +using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.TextTemplating { @@ -23,17 +25,19 @@ namespace Volo.Abp.TextTemplating _templateDefinitionManager = templateDefinitionManager; } - public Task GetContentOrNullAsync( + public virtual Task GetContentOrNullAsync( [NotNull] string templateName, - [CanBeNull] string cultureName = null) + [CanBeNull] string cultureName = null, + bool tryDefaults = true) { var template = _templateDefinitionManager.Get(templateName); return GetContentOrNullAsync(template, cultureName); } - public async Task GetContentOrNullAsync( + public virtual async Task GetContentOrNullAsync( [NotNull] TemplateDefinition templateDefinition, - [CanBeNull] string cultureName = null) + [CanBeNull] string cultureName = null, + bool tryDefaults = true) { Check.NotNull(templateDefinition, nameof(templateDefinition)); @@ -44,30 +48,93 @@ namespace Volo.Abp.TextTemplating ); } + if (cultureName == null) + { + cultureName = CultureInfo.CurrentUICulture.Name; + } + using (var scope = ServiceScopeFactory.CreateScope()) { - var context = new TemplateContentContributorContext( - templateDefinition, - scope.ServiceProvider, - cultureName + var contributors = Options.ContentContributors + .Select(type => (ITemplateContentContributor) scope.ServiceProvider.GetRequiredService(type)) + .Reverse() + .ToArray(); + + //Try to get from the requested culture + var templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + cultureName + ) ); - foreach (var contentContributorType in Options.ContentContributors.Reverse()) + if (templateString != null) + { + return templateString; + } + + if (!tryDefaults) { - var contributor = (ITemplateContentContributor) scope.ServiceProvider - .GetRequiredService(contentContributorType); + return null; + } + + //Try to get from same culture without country code + if (cultureName.Contains("-")) //Example: "tr-TR" + { + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + CultureHelper.GetBaseCultureName(cultureName) + ) + ); + + if (templateString != null) + { + return templateString; + } + } + + //Try to get from default culture + if (templateDefinition.DefaultCultureName != null) + { + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + templateDefinition.DefaultCultureName + ) + ); - var templateString = await contributor.GetOrNullAsync(context); if (templateString != null) { return templateString; } } } - - throw new AbpException( - $"None of the template content contributors could get the content for the template '{templateDefinition.Name}'" - ); + + //Not found + return null; + } + + protected virtual async Task GetContentOrNullAsync( + ITemplateContentContributor[] contributors, + TemplateContentContributorContext context) + { + foreach (var contributor in contributors) + { + var templateString = await contributor.GetOrNullAsync(context); + if (templateString != null) + { + return templateString; + } + } + + return null; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs index 4ff941fb84..3ddfb973f3 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs @@ -12,7 +12,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles _content = await fileInfo.ReadAsStringAsync(); } - public string GetContent(string culture, string defaultCultureName) + public string GetContentOrNull(string culture) { return _content; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs index 0b028138cd..611e50b005 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs @@ -2,6 +2,6 @@ { public interface ILocalizedTemplateContentReader { - public string GetContent(string culture, string defaultCultureName = null); + public string GetContentOrNull(string culture); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs index 0578a716ae..475559c634 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs @@ -9,9 +9,7 @@ } - public string GetContent( - string culture, - string defaultCultureName = null) + public string GetContentOrNull(string culture) { return null; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index e06336c658..668d1dc3ec 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -21,9 +21,8 @@ namespace Volo.Abp.TextTemplating.VirtualFiles var localizedReader = await _localizedTemplateContentReaderFactory .CreateAsync(context.TemplateDefinition); - return localizedReader.GetContent( - context.Culture ?? CultureInfo.CurrentUICulture.Name, - context.TemplateDefinition.DefaultCultureName + return localizedReader.GetContentOrNull( + context.Culture ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs index 0f2a23a2fa..b43ab722b7 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; -using Volo.Abp.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles @@ -34,34 +33,9 @@ namespace Volo.Abp.TextTemplating.VirtualFiles } } - public string GetContent(string cultureName, string defaultCultureName) + public string GetContentOrNull(string cultureName) { - var content = _dictionary.GetOrDefault(cultureName); - if (content != null) - { - return content; - } - - if (cultureName.Contains("-")) - { - var baseCultureName = CultureHelper.GetBaseCultureName(cultureName); - content = _dictionary.GetOrDefault(baseCultureName); - if (content != null) - { - return content; - } - } - - if (defaultCultureName != null) - { - content = _dictionary.GetOrDefault(defaultCultureName); - if (content != null) - { - return content; - } - } - - return null; + return _dictionary.GetOrDefault(cultureName); } } } \ No newline at end of file From 720b5d7e2f25bc9cc6fa2afd88a29cbff0f73cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 13:19:25 +0300 Subject: [PATCH 35/52] Add TemplateDefinitionIsInlineLocalized. --- .../Abp/TextTemplating/TemplateDefinition.cs | 2 ++ .../TemplateDefinitionContext.cs | 14 ++++----- .../TemplateDefinitionExtensions.cs | 19 +++++++----- .../TemplateDefinitionManager.cs | 30 +++++++++++++++++++ .../LocalizedTemplateContentReaderFactory.cs | 6 +--- .../TextTemplating/TemplateDefinitionTests.cs | 9 ++++-- 6 files changed, 59 insertions(+), 21 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index d165eb4d31..f3b31a769d 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -17,6 +17,8 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public Type LocalizationResource { get; set; } + public bool IsInlineLocalized { get; internal set; } + [CanBeNull] public string DefaultCultureName { get; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs index d0911f21ac..04c3876531 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs @@ -5,26 +5,26 @@ namespace Volo.Abp.TextTemplating { public class TemplateDefinitionContext : ITemplateDefinitionContext { - protected Dictionary TextTemplates { get; } + protected Dictionary Templates { get; } - public TemplateDefinitionContext(Dictionary textTemplates) + public TemplateDefinitionContext(Dictionary templates) { - TextTemplates = textTemplates; + Templates = templates; } public IReadOnlyList GetAll(string name) { - return TextTemplates.Values.ToImmutableList(); + return Templates.Values.ToImmutableList(); } public virtual TemplateDefinition GetOrNull(string name) { - return TextTemplates.GetOrDefault(name); + return Templates.GetOrDefault(name); } public virtual IReadOnlyList GetAll() { - return TextTemplates.Values.ToImmutableList(); + return Templates.Values.ToImmutableList(); } public virtual void Add(params TemplateDefinition[] definitions) @@ -36,7 +36,7 @@ namespace Volo.Abp.TextTemplating foreach (var definition in definitions) { - TextTemplates[definition.Name] = definition; + Templates[definition.Name] = definition; } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs index ef4a0ac9a3..0d17e9969c 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -1,16 +1,11 @@ -using JetBrains.Annotations; +using System.Collections.Generic; +using JetBrains.Annotations; using Volo.Abp.TextTemplating.VirtualFiles; namespace Volo.Abp.TextTemplating { public static class TemplateDefinitionExtensions { - /// - /// - /// - /// - /// - /// public static TemplateDefinition WithVirtualFilePath( [NotNull] this TemplateDefinition templateDefinition, [NotNull] string virtualPath) @@ -22,5 +17,15 @@ namespace Volo.Abp.TextTemplating virtualPath ); } + + public static string GetVirtualFilePathOrNull( + [NotNull] this TemplateDefinition templateDefinition) + { + Check.NotNull(templateDefinition, nameof(templateDefinition)); + + return templateDefinition + .Properties + .GetOrDefault(VirtualFileTemplateContentContributor.VirtualPathPropertyName) as string; + } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index 28909330fe..11081f6dff 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating { @@ -78,9 +79,38 @@ namespace Volo.Abp.TextTemplating { provider.PostDefine(context); } + + SetIsInlineLocalized( + templates, + scope.ServiceProvider + ); } return templates; } + + protected virtual void SetIsInlineLocalized( + Dictionary templates, + IServiceProvider serviceProvider) + { + var virtualFileProvider = serviceProvider.GetRequiredService(); + + foreach (var templateDefinition in templates.Values) + { + var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); + if (virtualPath == null) + { + continue; + } + + var fileInfo = virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) + { + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); + } + + templateDefinition.IsInlineLocalized = !fileInfo.IsDirectory; + } + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs index f4f8384777..b30e5771f4 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -55,10 +54,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles protected virtual async Task CreateInternalAsync( TemplateDefinition templateDefinition) { - var virtualPath = templateDefinition - .Properties - .GetOrDefault(VirtualFileTemplateContentContributor.VirtualPathPropertyName) as string; - + var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); if (virtualPath == null) { return NullLocalizedTemplateContentReader.Instance; diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs index b711860089..51c78cc13e 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -15,8 +15,13 @@ namespace Volo.Abp.TextTemplating [Fact] public void Should_Retrieve_Template_Definition_By_Name() { - var definition = _templateDefinitionManager.Get(TestTemplates.WelcomeEmail); - definition.Name.ShouldBe(TestTemplates.WelcomeEmail); + var welcomeEmailTemplate = _templateDefinitionManager.Get(TestTemplates.WelcomeEmail); + welcomeEmailTemplate.Name.ShouldBe(TestTemplates.WelcomeEmail); + welcomeEmailTemplate.IsInlineLocalized.ShouldBeFalse(); + + var forgotPasswordEmailTemplate = _templateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail); + forgotPasswordEmailTemplate.Name.ShouldBe(TestTemplates.ForgotPasswordEmail); + forgotPasswordEmailTemplate.IsInlineLocalized.ShouldBeTrue(); } [Fact] From 12bde17d6688bdfb43dce016199267956764e777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 13:56:25 +0300 Subject: [PATCH 36/52] Revisit getting template content --- .../TemplateContentContributorContext.cs | 6 +- .../TextTemplating/TemplateContentProvider.cs | 62 +++++++++++++++---- .../Abp/TextTemplating/TemplateDefinition.cs | 2 +- .../Abp/TextTemplating/TemplateRenderer.cs | 21 +++++-- .../FileInfoLocalizedTemplateContentReader.cs | 7 ++- .../ILocalizedTemplateContentReader.cs | 6 +- .../VirtualFileTemplateContentContributor.cs | 3 +- ...ualFolderLocalizedTemplateContentReader.cs | 5 ++ 8 files changed, 86 insertions(+), 26 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs index bb304a3344..773bf1a0a4 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs @@ -11,17 +11,17 @@ namespace Volo.Abp.TextTemplating [NotNull] public IServiceProvider ServiceProvider { get; } - [NotNull] + [CanBeNull] public string Culture { get; } public TemplateContentContributorContext( [NotNull] TemplateDefinition templateDefinition, [NotNull] IServiceProvider serviceProvider, - [NotNull] string culture) + [CanBeNull] string culture) { TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); - Culture = Check.NotNull(culture, nameof(culture)); + Culture = culture; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index dcb5d88850..2e9c7af269 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -47,14 +47,12 @@ namespace Volo.Abp.TextTemplating $"No template content contributor was registered. Use {nameof(AbpTextTemplatingOptions)} to register contributors!" ); } - - if (cultureName == null) - { - cultureName = CultureInfo.CurrentUICulture.Name; - } - + using (var scope = ServiceScopeFactory.CreateScope()) { + var searchCultureName = cultureName ?? + CultureInfo.CurrentUICulture.Name; + var contributors = Options.ContentContributors .Select(type => (ITemplateContentContributor) scope.ServiceProvider.GetRequiredService(type)) .Reverse() @@ -66,7 +64,7 @@ namespace Volo.Abp.TextTemplating new TemplateContentContributorContext( templateDefinition, scope.ServiceProvider, - cultureName + searchCultureName ) ); @@ -77,18 +75,36 @@ namespace Volo.Abp.TextTemplating if (!tryDefaults) { + if (templateDefinition.IsInlineLocalized && cultureName == null) + { + //Try to get culture independent content + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + null + ) + ); + + if (templateString != null) + { + return templateString; + } + } + return null; } //Try to get from same culture without country code - if (cultureName.Contains("-")) //Example: "tr-TR" + if (searchCultureName.Contains("-")) //Example: "tr-TR" { templateString = await GetContentOrNullAsync( contributors, new TemplateContentContributorContext( templateDefinition, scope.ServiceProvider, - CultureHelper.GetBaseCultureName(cultureName) + CultureHelper.GetBaseCultureName(searchCultureName) ) ); @@ -97,16 +113,16 @@ namespace Volo.Abp.TextTemplating return templateString; } } - - //Try to get from default culture - if (templateDefinition.DefaultCultureName != null) + + if (templateDefinition.IsInlineLocalized) { + //Try to get culture independent content templateString = await GetContentOrNullAsync( contributors, new TemplateContentContributorContext( templateDefinition, scope.ServiceProvider, - templateDefinition.DefaultCultureName + null ) ); @@ -115,6 +131,26 @@ namespace Volo.Abp.TextTemplating return templateString; } } + else + { + //Try to get from default culture + if (templateDefinition.DefaultCultureName != null) + { + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + templateDefinition.DefaultCultureName + ) + ); + + if (templateString != null) + { + return templateString; + } + } + } } //Not found diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index f3b31a769d..769b970733 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public Type LocalizationResource { get; set; } - public bool IsInlineLocalized { get; internal set; } + public bool IsInlineLocalized { get; set; } [CanBeNull] public string DefaultCultureName { get; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 8b67a380f0..6a1fff00ef 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Localization; @@ -35,16 +34,30 @@ namespace Volo.Abp.TextTemplating { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); - cultureName ??= CultureInfo.CurrentUICulture.Name; + if (globalContext == null) + { + globalContext = new Dictionary(); + } - using (CultureHelper.Use(cultureName)) + if (cultureName == null) { return await RenderInternalAsync( templateName, - globalContext ?? new Dictionary(), + globalContext, model ); } + else + { + using (CultureHelper.Use(cultureName)) + { + return await RenderInternalAsync( + templateName, + globalContext, + model + ); + } + } } protected virtual async Task RenderInternalAsync( diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs index 3ddfb973f3..cde913f307 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs @@ -14,7 +14,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public string GetContentOrNull(string culture) { - return _content; + if (culture == null) + { + return _content; + } + + return null; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs index 611e50b005..7d837d731d 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs @@ -1,7 +1,9 @@ -namespace Volo.Abp.TextTemplating.VirtualFiles +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating.VirtualFiles { public interface ILocalizedTemplateContentReader { - public string GetContentOrNull(string culture); + public string GetContentOrNull([CanBeNull] string culture); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index 668d1dc3ec..ede33a3652 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using System.Threading.Tasks; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating.VirtualFiles diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs index b43ab722b7..638a7db190 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs @@ -35,6 +35,11 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public string GetContentOrNull(string cultureName) { + if (cultureName == null) + { + return null; + } + return _dictionary.GetOrDefault(cultureName); } } From c71550b98ccdff528761ac69df22ce6aad68f5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 14:16:55 +0300 Subject: [PATCH 37/52] Add useCurrentCultureIfCultureNameIsNull option. --- .../ITemplateContentProvider.cs | 6 +- .../TextTemplating/TemplateContentProvider.cs | 83 +++++++++---------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs index b352fc0503..19248dc161 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -8,13 +8,15 @@ namespace Volo.Abp.TextTemplating Task GetContentOrNullAsync( [NotNull] string templateName, [CanBeNull] string cultureName = null, - bool tryDefaults = true + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true ); Task GetContentOrNullAsync( [NotNull] TemplateDefinition templateDefinition, [CanBeNull] string cultureName = null, - bool tryDefaults = true + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 2e9c7af269..8710068bca 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; @@ -26,9 +27,10 @@ namespace Volo.Abp.TextTemplating } public virtual Task GetContentOrNullAsync( - [NotNull] string templateName, + [NotNull] string templateName, [CanBeNull] string cultureName = null, - bool tryDefaults = true) + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true) { var template = _templateDefinitionManager.Get(templateName); return GetContentOrNullAsync(template, cultureName); @@ -37,7 +39,8 @@ namespace Volo.Abp.TextTemplating public virtual async Task GetContentOrNullAsync( [NotNull] TemplateDefinition templateDefinition, [CanBeNull] string cultureName = null, - bool tryDefaults = true) + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true) { Check.NotNull(templateDefinition, nameof(templateDefinition)); @@ -47,64 +50,50 @@ namespace Volo.Abp.TextTemplating $"No template content contributor was registered. Use {nameof(AbpTextTemplatingOptions)} to register contributors!" ); } - + using (var scope = ServiceScopeFactory.CreateScope()) { - var searchCultureName = cultureName ?? - CultureInfo.CurrentUICulture.Name; - - var contributors = Options.ContentContributors - .Select(type => (ITemplateContentContributor) scope.ServiceProvider.GetRequiredService(type)) - .Reverse() - .ToArray(); - - //Try to get from the requested culture - var templateString = await GetContentOrNullAsync( - contributors, - new TemplateContentContributorContext( - templateDefinition, - scope.ServiceProvider, - searchCultureName - ) - ); + string templateString = null; - if (templateString != null) + if (cultureName == null && useCurrentCultureIfCultureNameIsNull) { - return templateString; + cultureName = CultureInfo.CurrentUICulture.Name; } - if (!tryDefaults) + var contributors = CreateTemplateContentContributors(scope.ServiceProvider); + + //Try to get from the requested culture + if (cultureName != null) { - if (templateDefinition.IsInlineLocalized && cultureName == null) - { - //Try to get culture independent content - templateString = await GetContentOrNullAsync( - contributors, - new TemplateContentContributorContext( - templateDefinition, - scope.ServiceProvider, - null - ) - ); + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + cultureName + ) + ); - if (templateString != null) - { - return templateString; - } + if (templateString != null) + { + return templateString; } + } + if (!tryDefaults) + { return null; } //Try to get from same culture without country code - if (searchCultureName.Contains("-")) //Example: "tr-TR" + if (cultureName != null && cultureName.Contains("-")) //Example: "tr-TR" { templateString = await GetContentOrNullAsync( contributors, new TemplateContentContributorContext( templateDefinition, scope.ServiceProvider, - CultureHelper.GetBaseCultureName(searchCultureName) + CultureHelper.GetBaseCultureName(cultureName) ) ); @@ -113,7 +102,7 @@ namespace Volo.Abp.TextTemplating return templateString; } } - + if (templateDefinition.IsInlineLocalized) { //Try to get culture independent content @@ -157,6 +146,14 @@ namespace Volo.Abp.TextTemplating return null; } + protected virtual ITemplateContentContributor[] CreateTemplateContentContributors(IServiceProvider serviceProvider) + { + return Options.ContentContributors + .Select(type => (ITemplateContentContributor)serviceProvider.GetRequiredService(type)) + .Reverse() + .ToArray(); + } + protected virtual async Task GetContentOrNullAsync( ITemplateContentContributor[] contributors, TemplateContentContributorContext context) From c10f97c1d1ff3c480599f7efd11b04cc3d25556a Mon Sep 17 00:00:00 2001 From: Ahmet Date: Wed, 29 Apr 2020 17:02:08 +0300 Subject: [PATCH 38/52] TemplateDefinition DisplayName added --- .../Volo.Abp.TextTemplating.csproj | 1 + .../TextTemplating/AbpTextTemplatingModule.cs | 4 ++- .../TextTemplating/TemplateContentProvider.cs | 25 ++++++++----------- .../Abp/TextTemplating/TemplateDefinition.cs | 25 ++++++++++++++++--- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj index 404259ae17..7bcc0400b5 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -19,6 +19,7 @@ + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index aca0ac8f53..8d76391829 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -1,13 +1,15 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating { [DependsOn( - typeof(AbpVirtualFileSystemModule) + typeof(AbpVirtualFileSystemModule), + typeof(AbpLocalizationAbstractionsModule) )] public class AbpTextTemplatingModule : AbpModule { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 8710068bca..55a3979e21 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -63,21 +63,18 @@ namespace Volo.Abp.TextTemplating var contributors = CreateTemplateContentContributors(scope.ServiceProvider); //Try to get from the requested culture - if (cultureName != null) - { - templateString = await GetContentOrNullAsync( - contributors, - new TemplateContentContributorContext( - templateDefinition, - scope.ServiceProvider, - cultureName - ) - ); + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + cultureName + ) + ); - if (templateString != null) - { - return templateString; - } + if (templateString != null) + { + return templateString; } if (!tryDefaults) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 769b970733..db2ccf6f63 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -1,14 +1,29 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using Volo.Abp.Localization; namespace Volo.Abp.TextTemplating { public class TemplateDefinition { + public const int MaxNameLength = 128; + [NotNull] public string Name { get; } + [NotNull] + public ILocalizableString DisplayName + { + get => _displayName; + set + { + Check.NotNull(value, nameof(value)); + _displayName = value; + } + } + private ILocalizableString _displayName; + public bool IsLayout { get; } [CanBeNull] @@ -44,14 +59,16 @@ namespace Volo.Abp.TextTemplating public Dictionary Properties { get; } public TemplateDefinition( - [NotNull] string name, - [CanBeNull] Type localizationResource = null, + [NotNull] string name, + [CanBeNull] Type localizationResource = null, + [CanBeNull] ILocalizableString displayName = null, bool isLayout = false, - string layout = null, + string layout = null, string defaultCultureName = null) { - Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + Name = Check.NotNullOrWhiteSpace(name, nameof(name), MaxNameLength); LocalizationResource = localizationResource; + DisplayName = displayName ?? new FixedLocalizableString(Name); IsLayout = isLayout; Layout = layout; DefaultCultureName = defaultCultureName; From bdc142454e1ed6752d988e60447cc9de9193720e Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 30 Apr 2020 09:27:15 +0800 Subject: [PATCH 39/52] Compare the value of the property to determine whether it is changed. Resolve #3726 --- .../EntityHistory/EntityHistoryHelper.cs | 5 ++- ...ithDisableAuditingAndPropertyHasAudited.cs | 6 ++- .../Volo/Abp/Auditing/Auditing_Tests.cs | 39 +++++++++++++++++-- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index af728686fd..8db091eb30 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -32,7 +32,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory IAuditingStore auditingStore, IOptions options, IClock clock, - IJsonSerializer jsonSerializer, + IJsonSerializer jsonSerializer, IAuditingHelper auditingHelper) { _clock = clock; @@ -241,7 +241,8 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory } } - if (propertyEntry.IsModified) + var isModified = !(propertyEntry.OriginalValue?.Equals(propertyEntry.CurrentValue) ?? propertyEntry.CurrentValue == null); + if (isModified) { return true; } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithDisableAuditingAndPropertyHasAudited.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithDisableAuditingAndPropertyHasAudited.cs index 28a98bac8e..f9fcc878b1 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithDisableAuditingAndPropertyHasAudited.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithDisableAuditingAndPropertyHasAudited.cs @@ -11,16 +11,20 @@ namespace Volo.Abp.Auditing.App.Entities } - public AppEntityWithDisableAuditingAndPropertyHasAudited(Guid id, string name, string name2) + public AppEntityWithDisableAuditingAndPropertyHasAudited(Guid id, string name, string name2, string name3) : base(id) { Name = name; Name2 = name2; + Name3 = name3; } [Audited] public string Name { get; set; } public string Name2 { get; set; } + + [Audited] + public string Name3 { get; set; } } } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index 502ba6dcc8..f9f8da6180 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs @@ -7,6 +7,7 @@ using NSubstitute; using Volo.Abp.Auditing.App.Entities; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.Auditing @@ -15,10 +16,12 @@ namespace Volo.Abp.Auditing { private IAuditingStore _auditingStore; private IAuditingManager _auditingManager; + private IUnitOfWorkManager _unitOfWorkManager; public Auditing_Tests() { _auditingManager = GetRequiredService(); + _unitOfWorkManager = GetRequiredService(); } protected override void AfterAddApplication(IServiceCollection services) @@ -158,18 +161,46 @@ namespace Volo.Abp.Auditing using (var scope = _auditingManager.BeginScope()) { var repository = ServiceProvider.GetRequiredService>(); - await repository.InsertAsync(new AppEntityWithDisableAuditingAndPropertyHasAudited(Guid.NewGuid(), "test name", "test name2")); + await repository.InsertAsync(new AppEntityWithDisableAuditingAndPropertyHasAudited(Guid.NewGuid(), "test name", "test name2", "test name3")); await scope.SaveAsync(); } #pragma warning disable 4014 _auditingStore.Received().SaveAsync(Arg.Is(x => - x.EntityChanges.Count == 1 && x.EntityChanges[0].PropertyChanges.Count == 1 && - x.EntityChanges[0].PropertyChanges[0].PropertyName == - nameof(AppEntityWithDisableAuditingAndPropertyHasAudited.Name))); + x.EntityChanges.Count == 1 && x.EntityChanges[0].PropertyChanges.Count == 2 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithDisableAuditingAndPropertyHasAudited.Name) && + x.EntityChanges[0].PropertyChanges[1].PropertyName == nameof(AppEntityWithDisableAuditingAndPropertyHasAudited.Name3))); #pragma warning restore 4014 } + [Fact] + public virtual async Task Should_Write_AuditLog_For_Entity_That_Property_Has_Audited_Attribute_Even_Entity_Has_DisableAuditing_Attribute2() + { + var entityId = Guid.NewGuid(); + var repository = ServiceProvider.GetRequiredService>(); + await repository.InsertAsync(new AppEntityWithDisableAuditingAndPropertyHasAudited(entityId, "test name", "test name2", "test name3")); + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + entity.Name = "new name1"; + + await repository.UpdateAsync(entity); + + await uow.CompleteAsync(); + } + + await scope.SaveAsync(); + } + +#pragma warning disable 4014 + _auditingStore.Received().SaveAsync(Arg.Is(x => + x.EntityChanges.Count == 1 && x.EntityChanges[0].PropertyChanges.Count == 1 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithDisableAuditingAndPropertyHasAudited.Name))); +#pragma warning restore 4014 + } [Fact] public virtual async Task Should_Write_AuditLog_If_There_No_Action_And_No_EntityChanges() From 61f8bc32d9f8418e618a3b7aa9a0514bbd1a2b2c Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 30 Apr 2020 09:34:01 +0800 Subject: [PATCH 40/52] Rename unit test method name. --- .../Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index f9f8da6180..2887eb8510 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs @@ -174,7 +174,7 @@ namespace Volo.Abp.Auditing } [Fact] - public virtual async Task Should_Write_AuditLog_For_Entity_That_Property_Has_Audited_Attribute_Even_Entity_Has_DisableAuditing_Attribute2() + public virtual async Task Should_Write_AuditLog_For_Entity_That_Property_Has_Audited_Attribute_And_Has_Changed_Even_Entity_Has_DisableAuditing_Attribute() { var entityId = Guid.NewGuid(); var repository = ServiceProvider.GetRequiredService>(); From d56cf1311f03a6998bf0473c79f4a4a5d7613ba1 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 30 Apr 2020 21:44:47 +0800 Subject: [PATCH 41/52] Translate part-1 document --- docs/en/Tutorials/Part-1.md | 4 +- .../images/bookstore-book-list-2.png | Bin 33396 -> 0 bytes .../images/bookstore-book-list.png | Bin 33481 -> 0 bytes .../images/bookstore-create-template.png | Bin 32422 -> 0 bytes .../bookstore-localization-files-v2.png | Bin 6323 -> 0 bytes .../images/bookstore-new-book-button.png | Bin 34614 -> 0 bytes .../bookstore-pmc-add-book-migration-v2.png | Bin 7712 -> 0 bytes .../images/bookstore-swagger.png | Bin 35254 -> 0 bytes docs/zh-Hans/Tutorials/Part-1.md | 1079 ++++++++++++++++- .../images/bookstore-actions-buttons.png | Bin 0 -> 29435 bytes .../images/bookstore-add-create-dialog-v2.png | Bin .../images/bookstore-add-edit-dialog.png | Bin .../images/bookstore-add-index-page-v2.png | Bin .../images/bookstore-angular-file-tree.png | Bin 0 -> 92799 bytes .../images/bookstore-appservice-tests.png | Bin .../images/bookstore-book-list-2.png | Bin 0 -> 82301 bytes .../Tutorials/images/bookstore-book-list.png | Bin 0 -> 62133 bytes .../images/bookstore-books-table-actions.png | Bin .../images/bookstore-books-table.png | Bin .../images/bookstore-confirmation-popup.png | Bin 0 -> 13523 bytes .../images/bookstore-create-dialog-2.png | Bin .../images/bookstore-create-dialog.png | Bin .../bookstore-create-project-angular.png | Bin 0 -> 24546 bytes .../images/bookstore-create-project-mvc.png | Bin 0 -> 181404 bytes .../bookstore-creating-book-list-terminal.png | Bin 0 -> 25327 bytes ...okstore-creating-books-module-terminal.png | Bin 0 -> 23145 bytes .../images/bookstore-database-tables-ef.png | Bin 0 -> 53560 bytes .../bookstore-database-tables-mongodb.png | Bin 0 -> 42687 bytes .../images/bookstore-edit-button.png | Bin 0 -> 94192 bytes .../images/bookstore-empty-new-book-modal.png | Bin 0 -> 104068 bytes .../bookstore-final-actions-dropdown.png | Bin 0 -> 13747 bytes .../images/bookstore-generate-state-books.png | Bin 0 -> 17624 bytes .../images/bookstore-homepage.png | Bin .../images/bookstore-index-js-file-v2.png | Bin .../bookstore-initial-book-list-page.png | Bin 0 -> 11811 bytes ...okstore-initial-books-page-with-layout.png | Bin 0 -> 10922 bytes .../bookstore-localization-files-v2.png | Bin 0 -> 6123 bytes .../images/bookstore-menu-items.png | Bin .../bookstore-migrations-applied-angular.png | Bin 0 -> 69174 bytes .../bookstore-migrations-applied-mvc.png | Bin 0 -> 199590 bytes .../images/bookstore-new-book-button.png | Bin 0 -> 80262 bytes .../images/bookstore-new-book-form-v2.png | Bin 0 -> 146611 bytes .../images/bookstore-new-book-form.png | Bin 0 -> 134689 bytes .../images/bookstore-new-menu-item.png | Bin 0 -> 6087 bytes ...bookstore-open-package-manager-console.png | Bin 0 -> 123776 bytes .../bookstore-pmc-add-book-migration-v2.png | Bin 0 -> 34181 bytes .../bookstore-pmc-add-book-migration.png | Bin .../bookstore-service-terminal-output.png | Bin 0 -> 17888 bytes .../bookstore-solution-structure-angular.png | Bin 0 -> 11845 bytes .../bookstore-solution-structure-mvc.png} | Bin .../bookstore-start-project-angular.png | Bin 0 -> 40858 bytes .../images/bookstore-start-project-mvc.png | Bin 0 -> 144467 bytes .../bookstore-swagger-book-dto-properties.png | Bin 0 -> 44547 bytes .../Tutorials/images/bookstore-swagger.png | Bin 0 -> 829976 bytes ...ookstore-test-js-proxy-getlist-network.png | Bin .../bookstore-test-js-proxy-getlist.png | Bin .../bookstore-test-projects-angular.png | Bin 0 -> 13218 bytes .../bookstore-test-projects-mvc.png} | Bin .../images/bookstore-test-projects-v2.png | Bin 0 -> 6864 bytes ...tore-update-database-after-book-entity.png | Bin 0 -> 76838 bytes .../images/bookstore-user-management.png | Bin .../bookstore-visual-studio-solution-v3.png | Bin 0 -> 12919 bytes .../images/generate-proxy-command.png | Bin 0 -> 88318 bytes .../Tutorials/images/generated-proxies.png | Bin 0 -> 130602 bytes .../images/mozilla-self-signed-cert-error.png | Bin 0 -> 42949 bytes 65 files changed, 1080 insertions(+), 3 deletions(-) delete mode 100644 docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png delete mode 100644 docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png delete mode 100644 docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png delete mode 100644 docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png delete mode 100644 docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png delete mode 100644 docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png delete mode 100644 docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-add-create-dialog-v2.png (100%) rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-add-edit-dialog.png (100%) rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-add-index-page-v2.png (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-appservice-tests.png (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-book-list.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-books-table-actions.png (100%) rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-books-table.png (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-create-dialog-2.png (100%) rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-create-dialog.png (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-creating-books-module-terminal.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-database-tables-ef.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-database-tables-mongodb.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-edit-button.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-final-actions-dropdown.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-generate-state-books.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-homepage.png (100%) rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-index-js-file-v2.png (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-initial-book-list-page.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-initial-books-page-with-layout.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-localization-files-v2.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-menu-items.png (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-angular.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-mvc.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-new-book-button.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-new-book-form-v2.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-new-book-form.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-new-menu-item.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-open-package-manager-console.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration-v2.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-pmc-add-book-migration.png (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-service-terminal-output.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-solution-structure-angular.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc/images/bookstore-visual-studio-solution-v3.png => images/bookstore-solution-structure-mvc.png} (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-start-project-angular.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-start-project-mvc.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-swagger-book-dto-properties.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-swagger.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-test-js-proxy-getlist-network.png (100%) rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-test-js-proxy-getlist.png (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-test-projects-angular.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc/images/bookstore-test-projects-v2.png => images/bookstore-test-projects-mvc.png} (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-update-database-after-book-entity.png rename docs/zh-Hans/Tutorials/{AspNetCore-Mvc => }/images/bookstore-user-management.png (100%) create mode 100644 docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png create mode 100644 docs/zh-Hans/Tutorials/images/generate-proxy-command.png create mode 100644 docs/zh-Hans/Tutorials/images/generated-proxies.png create mode 100644 docs/zh-Hans/Tutorials/images/mozilla-self-signed-cert-error.png diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index f846e34250..c684d1fc1c 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -61,7 +61,7 @@ After creating the project, you need to apply the initial migrations and create To run the project, right click to the {{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} project and click **Set As StartUp Project**. And run the web project by pressing **CTRL+F5** (*without debugging and fast*) or press **F5** (*with debugging and slow*). {{if UI == "NG"}}You will see the Swagger UI for BookStore API.{{end}} -Further information, see the [running the application section](../../Getting-Started-{{if UI == "NG"}}Angular{{else}}AspNetCore-MVC{{end}}-Template#running-the-application).Getting-Started-AspNetCore-MVC-Template#running-the-application +Further information, see the [running the application section](../Getting-Started?UI={{UI}}#run-the-application). ![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png) @@ -335,7 +335,7 @@ INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES ### Create the application service -The next step is to create an [application service](../../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects: +The next step is to create an [application service](../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects: * `Acme.BookStore.Application.Contracts` mainly contains your `DTO`s and application service interfaces. * `Acme.BookStore.Application` contains the implementations of your application services. diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png deleted file mode 100644 index a7d49a661b9ea3388a4f26350bb8a64a7a89c8bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33396 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 diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png deleted file mode 100644 index 7cc96c8c943ede472f900a0b0f73be8874b5e4c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32422 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 rq`-$Eum>2eJtG6rYUx;??k7z}`tIX684UzKl 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 z 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