From f2d3b712247111a6c869fb85457dca734b7b2985 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 25 Mar 2020 18:08:13 +0800 Subject: [PATCH 01/61] Introducing DynamicProxyIgnoreTypes. Resolve #3180 --- .../AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs | 6 +++++ .../Auditing/AuditingInterceptorRegistrar.cs | 3 ++- .../AuthorizationInterceptorRegistrar.cs | 3 ++- .../DynamicProxy/DynamicProxyIgnoreTypes.cs | 24 +++++++++++++++++++ .../Features/FeatureInterceptorRegistrar.cs | 3 ++- .../Abp/Uow/UnitOfWorkInterceptorRegistrar.cs | 3 ++- .../ValidationInterceptorRegistrar.cs | 3 ++- .../AbpAuthorizationTestModule.cs | 4 +++- ...plicationService_FluentValidation_Tests.cs | 4 +++- .../ApplicationService_Validation_Tests.cs | 4 +++- 10 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 21a675ddc4..e609b0a601 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Hosting; using Volo.Abp.ApiVersioning; using Volo.Abp.AspNetCore.Mvc.Conventions; @@ -20,6 +21,7 @@ using Volo.Abp.AspNetCore.Mvc.Json; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.VirtualFileSystem; using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; using Volo.Abp.Http.Modeling; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -38,6 +40,10 @@ namespace Volo.Abp.AspNetCore.Mvc { public override void PreConfigureServices(ServiceConfigurationContext context) { + DynamicProxyIgnoreTypes.IgnoreTypes.AddIfNotContains(typeof(ControllerBase)); + DynamicProxyIgnoreTypes.IgnoreTypes.AddIfNotContains(typeof(PageModel)); + DynamicProxyIgnoreTypes.IgnoreTypes.AddIfNotContains(typeof(ViewComponent)); + context.Services.AddConventionalRegistrar(new AbpAspNetCoreMvcConventionalRegistrar()); } diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs index 94920fe1be..6790995331 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; namespace Volo.Abp.Auditing { @@ -8,7 +9,7 @@ namespace Volo.Abp.Auditing { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (ShouldIntercept(context.ImplementationType)) + if (ShouldIntercept(context.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) { context.Interceptors.TryAdd(); } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs index 69a285ddc8..b44c299b28 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Authorization; using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; namespace Volo.Abp.Authorization { @@ -10,7 +11,7 @@ namespace Volo.Abp.Authorization { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (ShouldIntercept(context.ImplementationType)) + if (ShouldIntercept(context.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) { context.Interceptors.TryAdd(); } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs new file mode 100644 index 0000000000..5eae6ef092 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.DynamicProxy +{ + /// + /// Castle's dynamic proxy class feature will have performance issues for some components, such as the controller of Asp net core MVC. + /// For related discussions, see: https://github.com/castleproject/Core/issues/486 https://github.com/abpframework/abp/issues/3180 + /// The Abp framework may enable interceptors for certain components (UOW, Auditing, Authorization, etc.), which requires dynamic proxy classes, but will cause application performance to decline. + /// We need to use other methods for the controller to implement interception, such as middleware or MVC / Page filters. + /// So we provide some ignored types to avoid enabling dynamic proxy classes. + /// By default it is empty. When you use middleware or filters for these components in your application, you can add these types to the list. + /// + public static class DynamicProxyIgnoreTypes + { + public static List IgnoreTypes { get; } = new List(); + + public static bool Contains(Type type, bool includeDerivedTypes = true) + { + return includeDerivedTypes ? IgnoreTypes.Any(t => t.IsAssignableFrom(type)) : IgnoreTypes.Contains(type); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs index 8cb6cd7ea5..b2caa734b5 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Reflection; using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; namespace Volo.Abp.Features { @@ -9,7 +10,7 @@ namespace Volo.Abp.Features { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (ShouldIntercept(context.ImplementationType)) + if (ShouldIntercept(context.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) { context.Interceptors.TryAdd(); } diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs index 849ca8ffd8..c01e760d58 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs @@ -1,5 +1,6 @@ using System.Reflection; using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; namespace Volo.Abp.Uow { @@ -7,7 +8,7 @@ namespace Volo.Abp.Uow { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (UnitOfWorkHelper.IsUnitOfWorkType(context.ImplementationType.GetTypeInfo())) + if (UnitOfWorkHelper.IsUnitOfWorkType(context.ImplementationType.GetTypeInfo()) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) { context.Interceptors.TryAdd(); } diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs index 81f6eb6597..187f64436e 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs @@ -1,4 +1,5 @@ using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; namespace Volo.Abp.Validation { @@ -6,7 +7,7 @@ namespace Volo.Abp.Validation { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (typeof(IValidationEnabled).IsAssignableFrom(context.ImplementationType)) + if (typeof(IValidationEnabled).IsAssignableFrom(context.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) { context.Interceptors.TryAdd(); } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs index 51337ae871..492a36f8b9 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Authorization.TestServices; using Volo.Abp.Autofac; +using Volo.Abp.DynamicProxy; using Volo.Abp.Modularity; namespace Volo.Abp.Authorization @@ -13,7 +14,8 @@ namespace Volo.Abp.Authorization { context.Services.OnRegistred(onServiceRegistredContext => { - if (typeof(IMyAuthorizedService1).IsAssignableFrom(onServiceRegistredContext.ImplementationType)) + if (typeof(IMyAuthorizedService1).IsAssignableFrom(onServiceRegistredContext.ImplementationType) && + !DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType)) { onServiceRegistredContext.Interceptors.TryAdd(); } diff --git a/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs index 5bf11b6d5e..a93eabda1d 100644 --- a/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs +++ b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.Autofac; using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; using Volo.Abp.Modularity; using Volo.Abp.Testing; using Volo.Abp.Validation; @@ -108,7 +109,8 @@ namespace Volo.Abp.FluentValidation { context.Services.OnRegistred(onServiceRegistredContext => { - if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType)) + if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType) && + !DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType)) { onServiceRegistredContext.Interceptors.TryAdd(); } diff --git a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs index 0d13867819..de9067e2f7 100644 --- a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs +++ b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs @@ -8,6 +8,7 @@ using Shouldly; using Volo.Abp.Application.Dtos; using Volo.Abp.Autofac; using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; using Volo.Abp.Modularity; using Volo.Abp.Testing; using Xunit; @@ -198,7 +199,8 @@ namespace Volo.Abp.Validation { context.Services.OnRegistred(onServiceRegistredContext => { - if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType)) + if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType) && + !DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType)) { onServiceRegistredContext.Interceptors.TryAdd(); } From 1475940d853379a867bef3d899c9000199144a95 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 27 Mar 2020 10:24:51 +0800 Subject: [PATCH 02/61] Refactor. --- .../Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs | 8 ++++---- .../Abp/Auditing/AuditingInterceptorRegistrar.cs | 7 ++++++- .../AuthorizationInterceptorRegistrar.cs | 6 +++--- .../Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs | 12 +++++++++--- .../Volo/Abp/Features/FeatureInterceptorRegistrar.cs | 7 ++++--- .../Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs | 10 ++++++++-- .../Abp/Validation/ValidationInterceptorRegistrar.cs | 10 ++++++++-- 7 files changed, 42 insertions(+), 18 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index e609b0a601..668e7a77dc 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -40,10 +40,10 @@ namespace Volo.Abp.AspNetCore.Mvc { public override void PreConfigureServices(ServiceConfigurationContext context) { - DynamicProxyIgnoreTypes.IgnoreTypes.AddIfNotContains(typeof(ControllerBase)); - DynamicProxyIgnoreTypes.IgnoreTypes.AddIfNotContains(typeof(PageModel)); - DynamicProxyIgnoreTypes.IgnoreTypes.AddIfNotContains(typeof(ViewComponent)); - + DynamicProxyIgnoreTypes.Add(); + DynamicProxyIgnoreTypes.Add(); + DynamicProxyIgnoreTypes.Add(); + context.Services.AddConventionalRegistrar(new AbpAspNetCoreMvcConventionalRegistrar()); } diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs index 6790995331..a407335f3d 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs @@ -9,7 +9,7 @@ namespace Volo.Abp.Auditing { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (ShouldIntercept(context.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) + if (ShouldIntercept(context.ImplementationType)) { context.Interceptors.TryAdd(); } @@ -17,6 +17,11 @@ namespace Volo.Abp.Auditing private static bool ShouldIntercept(Type type) { + if (DynamicProxyIgnoreTypes.Contains(type)) + { + return false; + } + if (ShouldAuditTypeByDefault(type)) { return true; diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs index b44c299b28..e33a8fca7e 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs @@ -11,7 +11,7 @@ namespace Volo.Abp.Authorization { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (ShouldIntercept(context.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) + if (ShouldIntercept(context.ImplementationType)) { context.Interceptors.TryAdd(); } @@ -19,8 +19,8 @@ namespace Volo.Abp.Authorization private static bool ShouldIntercept(Type type) { - return type.IsDefined(typeof(AuthorizeAttribute), true) || - AnyMethodHasAuthorizeAttribute(type); + return !DynamicProxyIgnoreTypes.Contains(type) && + (type.IsDefined(typeof(AuthorizeAttribute), true) || AnyMethodHasAuthorizeAttribute(type)); } private static bool AnyMethodHasAuthorizeAttribute(Type implementationType) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs index 5eae6ef092..6cdefb6705 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Volo.Abp.Threading; namespace Volo.Abp.DynamicProxy { @@ -14,11 +15,16 @@ namespace Volo.Abp.DynamicProxy /// public static class DynamicProxyIgnoreTypes { - public static List IgnoreTypes { get; } = new List(); - + private static HashSet IgnoredTypes { get; } = new HashSet(); + + public static void Add() + { + IgnoredTypes.Locking(() => IgnoredTypes.AddIfNotContains(typeof(T))); + } + public static bool Contains(Type type, bool includeDerivedTypes = true) { - return includeDerivedTypes ? IgnoreTypes.Any(t => t.IsAssignableFrom(type)) : IgnoreTypes.Contains(type); + return includeDerivedTypes ? IgnoredTypes.Any(t => t.IsAssignableFrom(type)) : IgnoredTypes.Contains(type); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs index b2caa734b5..c20b98c143 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.Features { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (ShouldIntercept(context.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) + if (ShouldIntercept(context.ImplementationType)) { context.Interceptors.TryAdd(); } @@ -18,8 +18,9 @@ namespace Volo.Abp.Features private static bool ShouldIntercept(Type type) { - return type.IsDefined(typeof(RequiresFeatureAttribute), true) || - AnyMethodHasRequiresFeatureAttribute(type); + return !DynamicProxyIgnoreTypes.Contains(type) && + (type.IsDefined(typeof(RequiresFeatureAttribute), true) || + AnyMethodHasRequiresFeatureAttribute(type)); } private static bool AnyMethodHasRequiresFeatureAttribute(Type implementationType) diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs index c01e760d58..5331cb1d0e 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System; +using System.Reflection; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; @@ -8,10 +9,15 @@ namespace Volo.Abp.Uow { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (UnitOfWorkHelper.IsUnitOfWorkType(context.ImplementationType.GetTypeInfo()) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) + if (ShouldIntercept(context.ImplementationType)) { context.Interceptors.TryAdd(); } } + + private static bool ShouldIntercept(Type type) + { + return !DynamicProxyIgnoreTypes.Contains(type) && UnitOfWorkHelper.IsUnitOfWorkType(type.GetTypeInfo()); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs index 187f64436e..1474efd710 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs @@ -1,4 +1,5 @@ -using Volo.Abp.DependencyInjection; +using System; +using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; namespace Volo.Abp.Validation @@ -7,10 +8,15 @@ namespace Volo.Abp.Validation { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (typeof(IValidationEnabled).IsAssignableFrom(context.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(context.ImplementationType)) + if (ShouldIntercept(context.ImplementationType)) { context.Interceptors.TryAdd(); } } + + private static bool ShouldIntercept(Type type) + { + return !DynamicProxyIgnoreTypes.Contains(type) && typeof(IValidationEnabled).IsAssignableFrom(type); + } } } \ No newline at end of file From 0b1606f40bc9d207b73844ad56b2c7cf43806c2e Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 27 Mar 2020 11:20:17 +0800 Subject: [PATCH 03/61] Add AbpAuditPageFilter, AbpFeaturePageFilter, AbpUowPageFilter, AbpExceptionPageFilter. --- .../ActionDescriptorExtensions.cs | 31 +++++ .../AspNetCore/Mvc/AbpMvcOptionsExtensions.cs | 13 +- .../Mvc/Auditing/AbpAuditPageFilter.cs | 103 ++++++++++++++++ .../AbpExceptionPageFilter.cs | 112 ++++++++++++++++++ .../Mvc/Features/AbpFeaturePageFilter.cs | 44 +++++++ .../AspNetCore/Mvc/Uow/AbpUowPageFilter.cs | 105 ++++++++++++++++ 6 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs index fb992cb4af..97b5a6c61b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Reflection; using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.RazorPages; using Volo.Abp; using Volo.Abp.AspNetCore.Mvc; @@ -37,5 +38,35 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions { return actionDescriptor is ControllerActionDescriptor; } + + public static PageActionDescriptor AsPageActionDescriptor(this ActionDescriptor actionDescriptor) + { + if (!actionDescriptor.IsPageAction()) + { + throw new AbpException($"{nameof(actionDescriptor)} should be type of {typeof(PageActionDescriptor).AssemblyQualifiedName}"); + } + + return actionDescriptor as PageActionDescriptor; + } + + public static MethodInfo GetPageActionMethodInfo(this ActionDescriptor actionDescriptor) + { + return actionDescriptor.AsPageActionDescriptor().GetMethodInfo(); + } + + public static Type GetPageActionReturnType(this PageActionDescriptor actionDescriptor) + { + return actionDescriptor.GetPageActionMethodInfo().ReturnType; + } + + public static bool HasObjectResult(this PageActionDescriptor actionDescriptor) + { + return ActionResultHelper.IsObjectResult(actionDescriptor.GetReturnType()); + } + + public static bool IsPageAction(this ActionDescriptor actionDescriptor) + { + return actionDescriptor is PageActionDescriptor; + } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 4aedf0b087..64d9daca93 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -16,7 +16,8 @@ namespace Volo.Abp.AspNetCore.Mvc public static void AddAbp(this MvcOptions options, IServiceCollection services) { AddConventions(options, services); - AddFilters(options); + AddActionFilters(options); + AddPageFilters(options); AddModelBinders(options); AddMetadataProviders(options, services); } @@ -26,7 +27,7 @@ namespace Volo.Abp.AspNetCore.Mvc options.Conventions.Add(new AbpServiceConventionWrapper(services)); } - private static void AddFilters(MvcOptions options) + private static void AddActionFilters(MvcOptions options) { options.Filters.AddService(typeof(AbpAuditActionFilter)); options.Filters.AddService(typeof(AbpNoContentActionFilter)); @@ -36,6 +37,14 @@ namespace Volo.Abp.AspNetCore.Mvc options.Filters.AddService(typeof(AbpExceptionFilter)); } + private static void AddPageFilters(MvcOptions options) + { + options.Filters.AddService(typeof(AbpAuditPageFilter)); + options.Filters.AddService(typeof(AbpFeaturePageFilter)); + options.Filters.AddService(typeof(AbpUowPageFilter)); + options.Filters.AddService(typeof(AbpExceptionPageFilter)); + } + private static void AddModelBinders(MvcOptions options) { options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider()); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs new file mode 100644 index 0000000000..23e94790e9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs @@ -0,0 +1,103 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Volo.Abp.Aspects; +using Volo.Abp.Auditing; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.Auditing +{ + public class AbpAuditPageFilter : IAsyncPageFilter, ITransientDependency + { + protected AbpAuditingOptions Options { get; } + private readonly IAuditingHelper _auditingHelper; + private readonly IAuditingManager _auditingManager; + + public AbpAuditPageFilter(IOptions options, IAuditingHelper auditingHelper, IAuditingManager auditingManager) + { + Options = options.Value; + _auditingHelper = auditingHelper; + _auditingManager = auditingManager; + } + + public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) + { + return Task.CompletedTask; + } + + public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) + { + if (context.HandlerMethod == null || !ShouldSaveAudit(context, out var auditLog, out var auditLogAction)) + { + await next(); + return; + } + + using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.Auditing)) + { + var stopwatch = Stopwatch.StartNew(); + + try + { + var result = await next(); + + if (result.Exception != null && !result.ExceptionHandled) + { + auditLog.Exceptions.Add(result.Exception); + } + } + catch (Exception ex) + { + auditLog.Exceptions.Add(ex); + throw; + } + finally + { + stopwatch.Stop(); + auditLogAction.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); + auditLog.Actions.Add(auditLogAction); + } + } + } + + private bool ShouldSaveAudit(PageHandlerExecutingContext context, out AuditLogInfo auditLog, out AuditLogActionInfo auditLogAction) + { + auditLog = null; + auditLogAction = null; + + if (!Options.IsEnabled) + { + return false; + } + + if (!context.ActionDescriptor.IsPageAction()) + { + return false; + } + + var auditLogScope = _auditingManager.Current; + if (auditLogScope == null) + { + return false; + } + + if (!_auditingHelper.ShouldSaveAudit(context.ActionDescriptor.GetMethodInfo(), true)) + { + return false; + } + + auditLog = auditLogScope.Log; + auditLogAction = _auditingHelper.CreateAuditLogAction( + auditLog, + context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(), + context.ActionDescriptor.AsControllerActionDescriptor().MethodInfo, + context.HandlerArguments + ); + + return true; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs new file mode 100644 index 0000000000..8aa45688ca --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs @@ -0,0 +1,112 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.AspNetCore.ExceptionHandling; +using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; +using Volo.Abp.Http; +using Volo.Abp.Json; + +namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling +{ + public class AbpExceptionPageFilter : IAsyncPageFilter, ITransientDependency + { + public ILogger Logger { get; set; } + + private readonly IExceptionToErrorInfoConverter _errorInfoConverter; + private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder; + private readonly IJsonSerializer _jsonSerializer; + + public AbpExceptionPageFilter( + IExceptionToErrorInfoConverter errorInfoConverter, + IHttpExceptionStatusCodeFinder statusCodeFinder, + IJsonSerializer jsonSerializer) + { + _errorInfoConverter = errorInfoConverter; + _statusCodeFinder = statusCodeFinder; + _jsonSerializer = jsonSerializer; + + Logger = NullLogger.Instance; + } + + + public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) + { + return Task.CompletedTask; + } + + public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) + { + if (context.HandlerMethod == null || !ShouldHandleException(context)) + { + await next(); + return; + } + + var pageHandlerExecutedContext = await next(); + if (pageHandlerExecutedContext.Exception == null) + { + return;; + } + + await HandleAndWrapException(pageHandlerExecutedContext); + } + + protected virtual bool ShouldHandleException(PageHandlerExecutingContext context) + { + //TODO: Create DontWrap attribute to control wrapping..? + + if (context.ActionDescriptor.IsPageAction() && + context.ActionDescriptor.HasObjectResult()) + { + return true; + } + + if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json)) + { + return true; + } + + if (context.HttpContext.Request.IsAjax()) + { + return true; + } + + return false; + } + + protected virtual async Task HandleAndWrapException(PageHandlerExecutedContext context) + { + //TODO: Trigger an AbpExceptionHandled event or something like that. + + context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true"); + context.HttpContext.Response.StatusCode = (int)_statusCodeFinder.GetStatusCode(context.HttpContext, context.Exception); + + var remoteServiceErrorInfo = _errorInfoConverter.Convert(context.Exception); + + context.Result = new ObjectResult(new RemoteServiceErrorResponse(remoteServiceErrorInfo)); + + var logLevel = context.Exception.GetLogLevel(); + + Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); + Logger.LogException(context.Exception, logLevel); + + await context.HttpContext + .RequestServices + .GetRequiredService() + .NotifyAsync( + new ExceptionNotificationContext(context.Exception) + ); + + context.Exception = null; //Handled! + } + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs new file mode 100644 index 0000000000..ceb117d2ff --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Volo.Abp.Aspects; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + public class AbpFeaturePageFilter : IAsyncPageFilter, ITransientDependency + { + private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService; + + public AbpFeaturePageFilter(IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService) + { + _methodInvocationAuthorizationService = methodInvocationAuthorizationService; + } + + public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) + { + return Task.CompletedTask; + } + + public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) + { + if (context.HandlerMethod == null || !context.ActionDescriptor.IsPageAction()) + { + await next(); + return; + } + + var methodInfo = context.ActionDescriptor.GetMethodInfo(); + + using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.FeatureChecking)) + { + await _methodInvocationAuthorizationService.CheckAsync( + new MethodInvocationFeatureCheckerContext(methodInfo) + ); + + await next(); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs new file mode 100644 index 0000000000..aeed982173 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs @@ -0,0 +1,105 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Uow; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; + +namespace Volo.Abp.AspNetCore.Mvc.Uow +{ + public class AbpUowPageFilter : IAsyncPageFilter, ITransientDependency + { + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly AbpUnitOfWorkDefaultOptions _defaultOptions; + + public AbpUowPageFilter(IUnitOfWorkManager unitOfWorkManager, IOptions options) + { + _unitOfWorkManager = unitOfWorkManager; + _defaultOptions = options.Value; + } + public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) + { + return Task.CompletedTask; + } + + public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) + { + if (context.HandlerMethod == null || !context.ActionDescriptor.IsPageAction()) + { + await next(); + return; + } + + var methodInfo = context.ActionDescriptor.GetMethodInfo(); + var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(methodInfo); + + context.HttpContext.Items["_AbpActionInfo"] = new AbpActionInfoInHttpContext + { + IsObjectResult = context.ActionDescriptor.HasObjectResult() + }; + + if (unitOfWorkAttr?.IsDisabled == true) + { + await next(); + return; + } + + var options = CreateOptions(context, unitOfWorkAttr); + + //Trying to begin a reserved UOW by AbpUnitOfWorkMiddleware + if (_unitOfWorkManager.TryBeginReserved(AbpUnitOfWorkMiddleware.UnitOfWorkReservationName, options)) + { + var result = await next(); + if (!Succeed(result)) + { + await RollbackAsync(context); + } + + return; + } + + //Begin a new, independent unit of work + using (var uow = _unitOfWorkManager.Begin(options)) + { + var result = await next(); + if (Succeed(result)) + { + await uow.CompleteAsync(context.HttpContext.RequestAborted); + } + } + } + + private AbpUnitOfWorkOptions CreateOptions(PageHandlerExecutingContext context, UnitOfWorkAttribute unitOfWorkAttribute) + { + var options = new AbpUnitOfWorkOptions(); + + unitOfWorkAttribute?.SetOptions(options); + + if (unitOfWorkAttribute?.IsTransactional == null) + { + options.IsTransactional = _defaultOptions.CalculateIsTransactional( + autoValue: !string.Equals(context.HttpContext.Request.Method, HttpMethod.Get.Method, StringComparison.OrdinalIgnoreCase) + ); + } + + return options; + } + + private async Task RollbackAsync(PageHandlerExecutingContext context) + { + var currentUow = _unitOfWorkManager.Current; + if (currentUow != null) + { + await currentUow.RollbackAsync(context.HttpContext.RequestAborted); + } + } + + private static bool Succeed(PageHandlerExecutedContext result) + { + return result.Exception == null || result.ExceptionHandled; + } + } +} \ No newline at end of file From fd5eda2b7f533aa4b0cf51a7384c8ebfd15293da Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 27 Mar 2020 16:32:21 +0800 Subject: [PATCH 04/61] Add unit tests for razor page filters. --- .../ActionDescriptorExtensions.cs | 25 ------- .../AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs | 7 +- .../AspNetCore/Mvc/AbpMvcOptionsExtensions.cs | 2 +- .../Mvc/Auditing/AbpAuditPageFilter.cs | 6 +- .../AbpExceptionPageFilter.cs | 2 +- .../Mvc/Features/AbpFeaturePageFilter.cs | 2 +- .../AspNetCore/Mvc/Uow/AbpUowPageFilter.cs | 4 +- .../Volo.Abp.AspNetCore.Mvc.Tests.csproj | 19 ++++++ .../Mvc/AbpAspNetCoreMvcTestModule.cs | 10 ++- .../Mvc/Auditing/AuditTestController_Tests.cs | 1 + .../Mvc/Auditing/AuditTestPage.cshtml | 19 ++++++ .../Mvc/Auditing/AuditTestPage.cshtml.cs | 33 ++++++++++ .../Mvc/Auditing/AuditTestPage_Tests.cs | 65 +++++++++++++++++++ .../ExceptionTestController_Tests.cs | 4 ++ .../ExceptionTestPage.cshtml | 19 ++++++ .../ExceptionTestPage.cshtml.cs | 18 +++++ .../ExceptionTestPage_Tests.cs | 56 ++++++++++++++++ .../Mvc/Features/FeatureTestPage.cshtml | 19 ++++++ .../Mvc/Features/FeatureTestPage.cshtml.cs | 27 ++++++++ .../Mvc/Features/FeatureTestPage_Tests.cs | 34 ++++++++++ ...WorkPageFilter_Exception_Rollback_Tests.cs | 37 +++++++++++ .../Mvc/Uow/UnitOfWorkPageFilter_Tests.cs | 22 +++++++ .../Mvc/Uow/UnitOfWorkTestPage.cshtml | 19 ++++++ .../Mvc/Uow/UnitOfWorkTestPage.cshtml.cs | 54 +++++++++++++++ 24 files changed, 469 insertions(+), 35 deletions(-) create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage_Tests.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Tests.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs index 97b5a6c61b..f54375c844 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs @@ -38,31 +38,6 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions { return actionDescriptor is ControllerActionDescriptor; } - - public static PageActionDescriptor AsPageActionDescriptor(this ActionDescriptor actionDescriptor) - { - if (!actionDescriptor.IsPageAction()) - { - throw new AbpException($"{nameof(actionDescriptor)} should be type of {typeof(PageActionDescriptor).AssemblyQualifiedName}"); - } - - return actionDescriptor as PageActionDescriptor; - } - - public static MethodInfo GetPageActionMethodInfo(this ActionDescriptor actionDescriptor) - { - return actionDescriptor.AsPageActionDescriptor().GetMethodInfo(); - } - - public static Type GetPageActionReturnType(this PageActionDescriptor actionDescriptor) - { - return actionDescriptor.GetPageActionMethodInfo().ReturnType; - } - - public static bool HasObjectResult(this PageActionDescriptor actionDescriptor) - { - return ActionResultHelper.IsObjectResult(actionDescriptor.GetReturnType()); - } public static bool IsPageAction(this ActionDescriptor actionDescriptor) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 668e7a77dc..4d970acd45 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; @@ -12,7 +12,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.Extensions.Hosting; using Volo.Abp.ApiVersioning; using Volo.Abp.AspNetCore.Mvc.Conventions; @@ -114,6 +116,9 @@ namespace Volo.Abp.AspNetCore.Mvc //Use DI to create view components context.Services.Replace(ServiceDescriptor.Singleton()); + //Use DI to create razor page + context.Services.Replace(ServiceDescriptor.Singleton()); + //Add feature providers var partManager = context.Services.GetSingletonInstance(); var application = context.Services.GetSingletonInstance(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 64d9daca93..f1225f1abf 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -39,10 +39,10 @@ namespace Volo.Abp.AspNetCore.Mvc private static void AddPageFilters(MvcOptions options) { + options.Filters.AddService(typeof(AbpExceptionPageFilter)); options.Filters.AddService(typeof(AbpAuditPageFilter)); options.Filters.AddService(typeof(AbpFeaturePageFilter)); options.Filters.AddService(typeof(AbpUowPageFilter)); - options.Filters.AddService(typeof(AbpExceptionPageFilter)); } private static void AddModelBinders(MvcOptions options) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs index 23e94790e9..6e9996574c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs @@ -84,7 +84,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing return false; } - if (!_auditingHelper.ShouldSaveAudit(context.ActionDescriptor.GetMethodInfo(), true)) + if (!_auditingHelper.ShouldSaveAudit(context.HandlerMethod.MethodInfo, true)) { return false; } @@ -92,8 +92,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing auditLog = auditLogScope.Log; auditLogAction = _auditingHelper.CreateAuditLogAction( auditLog, - context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(), - context.ActionDescriptor.AsControllerActionDescriptor().MethodInfo, + context.HandlerMethod.GetType(), + context.HandlerMethod.MethodInfo, context.HandlerArguments ); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs index 8aa45688ca..fb0a7c5a1f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs @@ -63,7 +63,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling //TODO: Create DontWrap attribute to control wrapping..? if (context.ActionDescriptor.IsPageAction() && - context.ActionDescriptor.HasObjectResult()) + ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType)) { return true; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs index ceb117d2ff..83e26ace9c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs @@ -29,7 +29,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Features return; } - var methodInfo = context.ActionDescriptor.GetMethodInfo(); + var methodInfo = context.HandlerMethod.MethodInfo; using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.FeatureChecking)) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs index aeed982173..993a12b0e7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs @@ -33,12 +33,12 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow return; } - var methodInfo = context.ActionDescriptor.GetMethodInfo(); + var methodInfo = context.HandlerMethod.MethodInfo; var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(methodInfo); context.HttpContext.Items["_AbpActionInfo"] = new AbpActionInfoInHttpContext { - IsObjectResult = context.ActionDescriptor.HasObjectResult() + IsObjectResult = ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType) }; if (unitOfWorkAttr?.IsDisabled == true) diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj index ddb3f5bc44..d47b369902 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj @@ -24,6 +24,7 @@ + @@ -39,6 +40,24 @@ PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index 5e74ec4bfa..39b7d9af5f 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -1,6 +1,9 @@ -using System; +using System; +using System.Linq; using Localization.Resources.AbpUi; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Authorization; using Volo.Abp.AspNetCore.Mvc.Localization; @@ -73,6 +76,11 @@ namespace Volo.Abp.AspNetCore.Mvc options.Languages.Add(new LanguageInfo("en", "en", "English")); options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); }); + + Configure(options => + { + options.RootDirectory = "/Volo/Abp/AspNetCore/Mvc"; + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs index 49bc689e14..3f934d5fe8 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs @@ -51,6 +51,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing await _auditingStore.Received().SaveAsync(Arg.Any()); } + [Fact] public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object() { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml new file mode 100644 index 0000000000..bf85af77ef --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.Auditing.AuditTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml.cs new file mode 100644 index 0000000000..6425d6585b --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Auditing; + +namespace Volo.Abp.AspNetCore.Mvc.Auditing +{ + public class AuditTestPage : AbpPageModel + { + private readonly AbpAuditingOptions _options; + + public AuditTestPage(IOptions options) + { + _options = options.Value; + } + + public IActionResult OnGetAuditSuccessForGetRequests() + { + return new OkResult(); + } + + public IActionResult OnGetAuditFailForGetRequests() + { + throw new UserFriendlyException("Exception occurred!"); + } + + public ObjectResult OnGetAuditFailForGetRequestsReturningObject() + { + throw new UserFriendlyException("Exception occurred!"); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs new file mode 100644 index 0000000000..7d5298e253 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs @@ -0,0 +1,65 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using NSubstitute; +using Volo.Abp.Auditing; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Auditing +{ + public class AuditTestPage_Tests : AspNetCoreMvcTestBase + { + private readonly AbpAuditingOptions _options; + private IAuditingStore _auditingStore; + + public AuditTestPage_Tests() + { + _options = ServiceProvider.GetRequiredService>().Value; + _auditingStore = ServiceProvider.GetRequiredService(); + } + + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + _auditingStore = Substitute.For(); + services.Replace(ServiceDescriptor.Singleton(_auditingStore)); + base.ConfigureServices(context, services); + } + + [Fact] + public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests() + { + _options.IsEnabledForGetRequests = true; + _options.AlwaysLogOnException = false; + await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditSuccessForGetRequests"); + await _auditingStore.Received().SaveAsync(Arg.Any()); + } + + [Fact] + public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always() + { + _options.IsEnabled = true; + _options.AlwaysLogOnException = true; + + try + { + await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditFailForGetRequests", System.Net.HttpStatusCode.Forbidden); + } + catch { } + + await _auditingStore.Received().SaveAsync(Arg.Any()); + } + + [Fact] + public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object() + { + _options.IsEnabled = true; + _options.AlwaysLogOnException = true; + + await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditFailForGetRequestsReturningObject", System.Net.HttpStatusCode.Forbidden); + + await _auditingStore.Received().SaveAsync(Arg.Any()); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs index 0de46fbe55..46cdd073e7 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs @@ -30,9 +30,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling result.Error.ShouldNotBeNull(); result.Error.Message.ShouldBe("This is a sample exception!"); +#pragma warning disable 4014 _fakeExceptionSubscriber .Received() .HandleAsync(Arg.Any()); +#pragma warning restore 4014 } [Fact] @@ -44,9 +46,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling ) ); +#pragma warning disable 4014 _fakeExceptionSubscriber .DidNotReceive() .HandleAsync(Arg.Any()); +#pragma warning restore 4014 } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml new file mode 100644 index 0000000000..7ba36da037 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.ExceptionHandling.ExceptionTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs new file mode 100644 index 0000000000..717d31effe --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling +{ + public class ExceptionTestPage : AbpPageModel + { + public void OnGetUserFriendlyException1() + { + throw new UserFriendlyException("This is a sample exception!"); + } + + public IActionResult OnGetUserFriendlyException2() + { + throw new UserFriendlyException("This is a sample exception!"); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs new file mode 100644 index 0000000000..7013c0c676 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs @@ -0,0 +1,56 @@ +using System.Net; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NSubstitute; +using Shouldly; +using Volo.Abp.ExceptionHandling; +using Volo.Abp.Http; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling +{ + public class ExceptionTestPage_Tests : AspNetCoreMvcTestBase + { + private IExceptionSubscriber _fakeExceptionSubscriber; + + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + base.ConfigureServices(context, services); + + _fakeExceptionSubscriber = Substitute.For(); + + services.AddSingleton(_fakeExceptionSubscriber); + } + + [Fact] + public async Task Should_Return_RemoteServiceErrorResponse_For_UserFriendlyException_For_Void_Return_Value() + { + var result = await GetResponseAsObjectAsync("/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException1", HttpStatusCode.Forbidden); + result.Error.ShouldNotBeNull(); + result.Error.Message.ShouldBe("This is a sample exception!"); + +#pragma warning disable 4014 + _fakeExceptionSubscriber + .Received() + .HandleAsync(Arg.Any()); +#pragma warning restore 4014 + } + + [Fact] + public async Task Should_Not_Handle_Exceptions_For_ActionResult_Return_Values() + { + await Assert.ThrowsAsync( + async () => await GetResponseAsObjectAsync( + "/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException2" + ) + ); + +#pragma warning disable 4014 + _fakeExceptionSubscriber + .DidNotReceive() + .HandleAsync(Arg.Any()); +#pragma warning restore 4014 + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml new file mode 100644 index 0000000000..8f0450becb --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.Features.FeatureTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs new file mode 100644 index 0000000000..6a051a29ee --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Features; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + public class FeatureTestPage : AbpPageModel + { + [RequiresFeature("AllowedFeature")] + public Task OnGetAllowedFeatureAsync() + { + return Task.CompletedTask; + } + + [RequiresFeature("NotAllowedFeature")] + public void OnGetNotAllowedFeature() + { + + } + + public ObjectResult OnGetNoFeature() + { + return new ObjectResult(42); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage_Tests.cs new file mode 100644 index 0000000000..91226f1938 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage_Tests.cs @@ -0,0 +1,34 @@ +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + public class FeatureTestPage_Tests : AspNetCoreMvcTestBase + { + [Fact] + public async Task Should_Allow_Enabled_Features() + { + await GetResponseAsStringAsync( + "/Features/FeatureTestPage?handler=AllowedFeature" + ); + } + + [Fact] + public async Task Should_Not_Allow_Not_Enabled_Features() + { + await GetResponseAsStringAsync( + "/Features/FeatureTestPage?handler=NotAllowedFeature", + HttpStatusCode.Unauthorized + ); + } + + [Fact] + public async Task Should_Allow_Actions_With_No_Feature() + { + await GetResponseAsStringAsync( + "/Features/FeatureTestPage?handler=NoFeature" + ); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs new file mode 100644 index 0000000000..b09d3ca2c9 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs @@ -0,0 +1,37 @@ +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Http; +using Volo.Abp.Json; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Uow +{ + public class UnitOfWorkPageFilter_Exception_Rollback_Tests : AspNetCoreMvcTestBase + { + [Fact] + public async Task Should_Rollback_Transaction_For_Handled_Exceptions() + { + var result = await GetResponseAsObjectAsync("/Uow/UnitOfWorkTestPage?handler=HandledException", HttpStatusCode.Forbidden); + result.Error.ShouldNotBeNull(); + result.Error.Message.ShouldBe("This is a sample exception!"); + } + + [Fact] + public async Task Should_Gracefully_Handle_Exceptions_On_Complete() + { + var response = await GetResponseAsync("/Uow/UnitOfWorkTestPage?handler=ExceptionOnComplete", HttpStatusCode.Forbidden); + + response.Headers.GetValues(AbpHttpConsts.AbpErrorFormat).FirstOrDefault().ShouldBe("true"); + + var resultAsString = await response.Content.ReadAsStringAsync(); + + var result = ServiceProvider.GetRequiredService().Deserialize(resultAsString); + + result.Error.ShouldNotBeNull(); + result.Error.Message.ShouldBe(TestUnitOfWorkConfig.ExceptionOnCompleteMessage); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Tests.cs new file mode 100644 index 0000000000..820ccf91b7 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Tests.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Uow +{ + public class UnitOfWorkPageFilter_Tests: AspNetCoreMvcTestBase + { + [Fact] + public async Task Get_Actions_Should_Not_Be_Transactional() + { + await GetResponseAsStringAsync("/Uow/UnitOfWorkTestPage?handler=RequiresUow"); + } + + [Fact] + public async Task Non_Get_Actions_Should_Be_Transactional() + { + var result = await Client.PostAsync("/Uow/UnitOfWorkTestPage?handler=RequiresUow", null); + result.IsSuccessStatusCode.ShouldBeTrue(); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml new file mode 100644 index 0000000000..da30a64818 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.Uow.UnitOfWorkTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs new file mode 100644 index 0000000000..ce54e8d34f --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Uow; + +namespace Volo.Abp.AspNetCore.Mvc.Uow +{ + [IgnoreAntiforgeryToken] + public class UnitOfWorkTestPage : AbpPageModel + { + private readonly TestUnitOfWorkConfig _testUnitOfWorkConfig; + + public UnitOfWorkTestPage(TestUnitOfWorkConfig testUnitOfWorkConfig) + { + _testUnitOfWorkConfig = testUnitOfWorkConfig; + } + + public IActionResult OnGetRequiresUow() + { + CurrentUnitOfWork.ShouldNotBeNull(); + CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse(); + + return Content("OK"); + } + + public IActionResult OnPostRequiresUow() + { + CurrentUnitOfWork.ShouldNotBeNull(); + CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue(); + + return Content("OK"); + } + + [UnitOfWork(isTransactional: true)] + public void OnGetHandledException() + { + CurrentUnitOfWork.ShouldNotBeNull(); + CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue(); + + throw new UserFriendlyException("This is a sample exception!"); + } + + public ObjectResult OnGetExceptionOnComplete() + { + CurrentUnitOfWork.ShouldNotBeNull(); + CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse(); + + _testUnitOfWorkConfig.ThrowExceptionOnComplete = true; + + //Prevent rendering of pages. + return new ObjectResult(""); + } + } +} \ No newline at end of file From 406d1cdb9b22c22d028043790d2f0bfc8aed015a Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 27 Mar 2020 16:34:21 +0800 Subject: [PATCH 05/61] Remove ViewComponent in DynamicProxyIgnoreTypes. --- .../Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 4d970acd45..b3f70b161d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -44,7 +44,6 @@ namespace Volo.Abp.AspNetCore.Mvc { DynamicProxyIgnoreTypes.Add(); DynamicProxyIgnoreTypes.Add(); - DynamicProxyIgnoreTypes.Add(); context.Services.AddConventionalRegistrar(new AbpAspNetCoreMvcConventionalRegistrar()); } From abda1104aed9ec70b2a08e6ced5399b1cdf29f80 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 27 Mar 2020 21:23:30 +0800 Subject: [PATCH 06/61] Add authorization unit test for razor page. --- .../Volo.Abp.AspNetCore.Mvc.Tests.csproj | 4 ++ .../Mvc/Authorization/AuthTestPage.cshtml | 19 +++++++++ .../Mvc/Authorization/AuthTestPage.cshtml.cs | 18 +++++++++ .../Mvc/Authorization/AuthTestPage_Tests.cs | 40 +++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage_Tests.cs diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj index d47b369902..bb841bc085 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj @@ -57,6 +57,10 @@ true PreserveNewest + + true + PreserveNewest + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml new file mode 100644 index 0000000000..cf94310510 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.Authorization.AuthTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml.cs new file mode 100644 index 0000000000..5ae66edb5f --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.AspNetCore.Mvc.Authorization +{ + [Authorize] + public class AuthTestPage : AbpPageModel + { + public static Guid FakeUserId { get; } = Guid.NewGuid(); + + public ActionResult OnGet() + { + return Content("OK"); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage_Tests.cs new file mode 100644 index 0000000000..ec8a043fbe --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage_Tests.cs @@ -0,0 +1,40 @@ +using System.Security.Claims; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.AspNetCore.TestBase; +using Volo.Abp.Autofac; +using Volo.Abp.MemoryDb; +using Volo.Abp.Modularity; +using Volo.Abp.Security.Claims; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Authorization +{ + [DependsOn( + typeof(AbpAspNetCoreTestBaseModule), + typeof(AbpMemoryDbTestModule), + typeof(AbpAspNetCoreMvcModule), + typeof(AbpAutofacModule) + )] + public class AuthTestPage_Tests: AspNetCoreMvcTestBase + { + private readonly FakeUserClaims _fakeRequiredService; + + public AuthTestPage_Tests() + { + _fakeRequiredService = GetRequiredService(); + } + + [Fact] + public async Task Should_Call_Simple_Authorized_Method_With_Authenticated_User() + { + _fakeRequiredService.Claims.AddRange(new[] + { + new Claim(AbpClaimTypes.UserId, AuthTestController.FakeUserId.ToString()) + }); + + var result = await GetResponseAsStringAsync("/Authorization/AuthTestPage"); + result.ShouldBe("OK"); + } + } +} \ No newline at end of file From 65a7ea84a2173a1b900f4426d0b2dd799750d19e Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 30 Mar 2020 14:54:03 +0800 Subject: [PATCH 07/61] Removed UnitOfWork attribute in razor page model. --- .../src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs | 3 --- .../src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs | 1 - 2 files changed, 4 deletions(-) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 014088983a..db0cf372d7 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -87,7 +87,6 @@ namespace Volo.Abp.Account.Web.Pages.Account return Page(); } - [UnitOfWork] //TODO: Will be removed when we implement action filter public virtual async Task OnPostAsync(string action) { await CheckLocalLoginAsync(); @@ -140,7 +139,6 @@ namespace Volo.Abp.Account.Web.Pages.Account return RedirectSafely(ReturnUrl, ReturnUrlHash); } - [UnitOfWork] public virtual async Task OnPostExternalLogin(string provider) { var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }); @@ -150,7 +148,6 @@ namespace Volo.Abp.Account.Web.Pages.Account return await Task.FromResult(Challenge(properties, provider)); } - [UnitOfWork] public virtual async Task OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) { //TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index 0cb71f2ed9..db085d6587 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -36,7 +36,6 @@ namespace Volo.Abp.Account.Web.Pages.Account await CheckSelfRegistrationAsync(); } - [UnitOfWork] //TODO: Will be removed when we implement action filter public virtual async Task OnPostAsync() { ValidateModel(); From 0139ee819433699a7c5e54fe3019820e59d7f292 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 7 Apr 2020 11:00:29 +0300 Subject: [PATCH 08/61] docs: add content strategy documentation --- docs/en/UI/Angular/Content-Strategy.md | 95 ++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 docs/en/UI/Angular/Content-Strategy.md diff --git a/docs/en/UI/Angular/Content-Strategy.md b/docs/en/UI/Angular/Content-Strategy.md new file mode 100644 index 0000000000..e3d3ca9849 --- /dev/null +++ b/docs/en/UI/Angular/Content-Strategy.md @@ -0,0 +1,95 @@ +# ContentStrategy + +`ContentStrategy` is an abstract class exposed by @abp/ng.core package. It helps you create inline scripts or styles. + +## API + + +### constructor + +```js +constructor( + public content: string, + protected domStrategy?: DomStrategy, + protected contentSecurityStrategy?: ContentSecurityStrategy +) +``` + +- `content` is set to `` element will place at the **end** of ``. + +Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. + + +### How to Insert Styles + +If you pass a `StyleContentStrategy` instance as the first parameter of `insertContent` method, the `DomInsertionService` will create a `` element will place at the **end** of ``. + +Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. + + +## API + + +### inserted + +```js +inserted: Set +``` + +All previously inserted contents are stored via this property as hashes. It is a simple [JavaScript Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). + + + +### insertContent + +```js +insertContent(strategy: ContentStrategy): void +``` + +`strategy` parameter is the primary focus here and is explained above. + + +## What's Next? + +- [TrackByService](./Track-By-Service.md) diff --git a/docs/en/UI/Angular/Lazy-Load-Service.md b/docs/en/UI/Angular/Lazy-Load-Service.md index 43ee7bc6b8..a03381869e 100644 --- a/docs/en/UI/Angular/Lazy-Load-Service.md +++ b/docs/en/UI/Angular/Lazy-Load-Service.md @@ -210,4 +210,4 @@ load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Obser ## What's Next? -- [TrackByService](./Track-By-Service.md) +- [DomInsertionService](./Dom-Insertion-Service.md) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 8034913c4c..471e215e3a 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -349,6 +349,10 @@ "text": "Lazy Loading Scripts & Styles", "path": "UI/Angular/Lazy-Load-Service.md" }, + { + "text": "DomInsertionService", + "path": "UI/Angular/Dom-Insertion-Service.md" + }, { "text": "TrackByService", "path": "UI/Angular/Track-By-Service.md" From 97ee0edf4fa8377e0ec2eb49bc04acea2a243862 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 7 Apr 2020 12:08:42 +0300 Subject: [PATCH 11/61] docs: refactor strategy docs --- .../UI/Angular/Content-Security-Strategy.md | 4 +-- docs/en/UI/Angular/Content-Strategy.md | 10 +++--- docs/en/UI/Angular/Cross-Origin-Strategy.md | 4 +-- docs/en/UI/Angular/Dom-Strategy.md | 32 ++++++++++++++++--- docs/en/UI/Angular/Loading-Strategy.md | 10 +++--- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/docs/en/UI/Angular/Content-Security-Strategy.md b/docs/en/UI/Angular/Content-Security-Strategy.md index c45cf23c58..f32317c9ca 100644 --- a/docs/en/UI/Angular/Content-Security-Strategy.md +++ b/docs/en/UI/Angular/Content-Security-Strategy.md @@ -50,7 +50,7 @@ Predefined content security strategies are accessible via `CONTENT_SECURITY_STRA ### Loose ```js -Loose(nonce: string) +CONTENT_SECURITY_STRATEGY.Loose(nonce: string) ``` `nonce` will be set. @@ -59,7 +59,7 @@ Loose(nonce: string) ### None ```js -None() +CONTENT_SECURITY_STRATEGY.None() ``` Nothing will be done. diff --git a/docs/en/UI/Angular/Content-Strategy.md b/docs/en/UI/Angular/Content-Strategy.md index e3d3ca9849..8544042c32 100644 --- a/docs/en/UI/Angular/Content-Strategy.md +++ b/docs/en/UI/Angular/Content-Strategy.md @@ -57,7 +57,7 @@ Predefined content strategies are accessible via `CONTENT_STRATEGY` constant. ### AppendScriptToBody ```js -AppendScriptToBody(content: string) +CONTENT_STRATEGY.AppendScriptToBody(content: string) ``` Creates a `` element will place at the **end Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. - ### How to Insert Styles If you pass a `StyleContentStrategy` instance as the first parameter of `insertContent` method, the `DomInsertionService` will create a `` element will place at t Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. +### How to Project Components & Templates + +If you pass a `ProjectionStrategy` as the first parameter of `projectContent` method, the `DomInsertionService` will resolve the projected component or template and place it at the designated target, such as containers or document body. If provided, it will also pass the component or the template a context. + +```js +const componentRef = this.domInsertionService.projectContent( + PROJECTION_STRATEGY.AppendComponentToBody(SomeOverlayComponent) +); +``` + +In the example above, `SomeOverlayComponent` component will placed at the **end** of `` and a `ComponentRef` will be returned. + +> You should keep the returned `ComponentRef` instance, as it is a reference to the projected component and you will need that reference to destroy the projected view and the component instance. + +```js +const componentRef = this.domInsertionService.projectContent( + PROJECTION_STRATEGY.ProjectComponentToContainer( + SomeOverlayComponent, + viewContainerRefOfTarget, + { someProp: "SOME_VALUE" } + ) +); +``` + +In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeOverlayComponent` component will placed inside it. Moreover, the given context will be applied, so `someProp` of the component will be set to `SOME_VALUE`. + +Please refer to [ProjectionStrategy](./Projection-Strategy.md) to see all available projection strategies and how you can build your own projection strategy. ## API ### insertContent ```js -insertContent(strategy: ContentStrategy): void +injectContent(injector: Injector): ComponentRef | EmbeddedViewRef ``` -`strategy` parameter is the primary focus here and is explained above. +`injector` parameter is the `Injector` instance you can pass to the projected content. It is not used in `TemplateProjectionStrategy`. ## What's Next? From 31624376c49de3b5f45f564c6d5a79ff629a9072 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 10 Apr 2020 09:20:20 +0300 Subject: [PATCH 42/61] chore: replace tilde with caret for abp versions --- npm/ng-packs/packages/account/package.json | 4 ++-- npm/ng-packs/packages/identity/package.json | 6 +++--- npm/ng-packs/packages/permission-management/package.json | 2 +- npm/ng-packs/packages/setting-management/package.json | 4 ++-- npm/ng-packs/packages/tenant-management/package.json | 6 +++--- npm/ng-packs/packages/theme-basic/package.json | 2 +- npm/ng-packs/packages/theme-shared/package.json | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/npm/ng-packs/packages/account/package.json b/npm/ng-packs/packages/account/package.json index 08c92c1e21..14c3553212 100644 --- a/npm/ng-packs/packages/account/package.json +++ b/npm/ng-packs/packages/account/package.json @@ -7,8 +7,8 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.account.config": "^2.5.0", - "@abp/ng.theme.shared": "^2.5.0" + "@abp/ng.account.config": "~2.5.0", + "@abp/ng.theme.shared": "~2.5.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/identity/package.json b/npm/ng-packs/packages/identity/package.json index 023f373ab7..5e9930608e 100644 --- a/npm/ng-packs/packages/identity/package.json +++ b/npm/ng-packs/packages/identity/package.json @@ -7,9 +7,9 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.identity.config": "^2.5.0", - "@abp/ng.permission-management": "^2.5.0", - "@abp/ng.theme.shared": "^2.5.0" + "@abp/ng.identity.config": "~2.5.0", + "@abp/ng.permission-management": "~2.5.0", + "@abp/ng.theme.shared": "~2.5.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/permission-management/package.json b/npm/ng-packs/packages/permission-management/package.json index 0b538d00fa..520ec54765 100644 --- a/npm/ng-packs/packages/permission-management/package.json +++ b/npm/ng-packs/packages/permission-management/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "^2.5.0" + "@abp/ng.theme.shared": "~2.5.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/setting-management/package.json b/npm/ng-packs/packages/setting-management/package.json index 5788bdc72b..716df5385e 100644 --- a/npm/ng-packs/packages/setting-management/package.json +++ b/npm/ng-packs/packages/setting-management/package.json @@ -7,8 +7,8 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.setting-management.config": "^2.5.0", - "@abp/ng.theme.shared": "^2.5.0" + "@abp/ng.setting-management.config": "~2.5.0", + "@abp/ng.theme.shared": "~2.5.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/tenant-management/package.json b/npm/ng-packs/packages/tenant-management/package.json index 8c07199b6c..49baed552f 100644 --- a/npm/ng-packs/packages/tenant-management/package.json +++ b/npm/ng-packs/packages/tenant-management/package.json @@ -7,9 +7,9 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.feature-management": "^2.5.0", - "@abp/ng.tenant-management.config": "^2.5.0", - "@abp/ng.theme.shared": "^2.5.0" + "@abp/ng.feature-management": "~2.5.0", + "@abp/ng.tenant-management.config": "~2.5.0", + "@abp/ng.theme.shared": "~2.5.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/theme-basic/package.json b/npm/ng-packs/packages/theme-basic/package.json index 3c07884583..bfbe9e170b 100644 --- a/npm/ng-packs/packages/theme-basic/package.json +++ b/npm/ng-packs/packages/theme-basic/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "^2.5.0" + "@abp/ng.theme.shared": "~2.5.0" }, "publishConfig": { "access": "public" diff --git a/npm/ng-packs/packages/theme-shared/package.json b/npm/ng-packs/packages/theme-shared/package.json index 9aece312d3..ce8ee044b9 100644 --- a/npm/ng-packs/packages/theme-shared/package.json +++ b/npm/ng-packs/packages/theme-shared/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.core": "^2.5.0", + "@abp/ng.core": "~2.5.0", "@fortawesome/fontawesome-free": "^5.12.1", "@ng-bootstrap/ng-bootstrap": "^5.3.0", "@ngx-validate/core": "^0.0.7", From 29c567bedeb100283ca65504dbc0d2553937c75f Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 10 Apr 2020 09:21:37 +0300 Subject: [PATCH 43/61] feat: add components enum to all modules #3425 --- npm/ng-packs/.vscode/settings.json | 4 +++- .../packages/account/src/lib/account-routing.module.ts | 7 ++++--- .../auth-wrapper/auth-wrapper.component.html | 4 +--- .../components/auth-wrapper/auth-wrapper.component.ts | 3 +++ .../src/lib/components/login/login.component.html | 2 +- .../src/lib/components/login/login.component.ts | 3 +++ .../manage-profile/manage-profile.component.html | 4 ++-- .../manage-profile/manage-profile.component.ts | 5 +++++ .../lib/components/register/register.component.html | 2 +- .../src/lib/components/register/register.component.ts | 3 +++ .../packages/account/src/lib/enums/components.ts | 9 +++++++++ npm/ng-packs/packages/account/src/public-api.ts | 1 + npm/ng-packs/packages/feature-management/package.json | 2 +- .../feature-management/src/lib/enums/components.ts | 3 +++ .../packages/feature-management/src/public-api.ts | 1 + .../src/lib/components/roles/roles.component.html | 2 +- .../src/lib/components/roles/roles.component.ts | 3 +++ .../src/lib/components/users/users.component.html | 2 +- .../src/lib/components/users/users.component.ts | 3 +++ .../packages/identity/src/lib/enums/components.ts | 4 ++++ .../identity/src/lib/identity-routing.module.ts | 5 +++-- npm/ng-packs/packages/identity/src/public-api.ts | 1 + .../permission-management/src/lib/enums/components.ts | 3 +++ .../packages/permission-management/src/public-api.ts | 1 + .../setting-management/src/lib/enums/components.ts | 3 +++ .../src/lib/setting-management-routing.module.ts | 3 ++- .../packages/setting-management/src/public-api.ts | 1 + .../src/lib/components/tenants/tenants.component.html | 10 +++++++--- .../src/lib/components/tenants/tenants.component.ts | 3 +++ .../tenant-management/src/lib/enums/components.ts | 3 +++ .../src/lib/tenant-management-routing.module.ts | 3 ++- .../packages/tenant-management/src/public-api.ts | 1 + .../packages/theme-basic/src/lib/enums/components.ts | 5 +++++ .../theme-basic/src/lib/services/initial.service.ts | 7 ++++--- npm/ng-packs/packages/theme-basic/src/public-api.ts | 1 + 35 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 npm/ng-packs/packages/account/src/lib/enums/components.ts create mode 100644 npm/ng-packs/packages/feature-management/src/lib/enums/components.ts create mode 100644 npm/ng-packs/packages/identity/src/lib/enums/components.ts create mode 100644 npm/ng-packs/packages/permission-management/src/lib/enums/components.ts create mode 100644 npm/ng-packs/packages/setting-management/src/lib/enums/components.ts create mode 100644 npm/ng-packs/packages/tenant-management/src/lib/enums/components.ts create mode 100644 npm/ng-packs/packages/theme-basic/src/lib/enums/components.ts diff --git a/npm/ng-packs/.vscode/settings.json b/npm/ng-packs/.vscode/settings.json index 9c2678df08..a04bf7ca0c 100644 --- a/npm/ng-packs/.vscode/settings.json +++ b/npm/ng-packs/.vscode/settings.json @@ -18,7 +18,9 @@ "titleBar.inactiveForeground": "#e7e7e799", "statusBar.background": "#1d70a2", "statusBarItem.hoverBackground": "#258ecd", - "statusBar.foreground": "#e7e7e7" + "statusBar.foreground": "#e7e7e7", + "statusBar.border": "#1d70a2", + "titleBar.border": "#1d70a2" }, "peacock.color": "#1D70A2" } diff --git a/npm/ng-packs/packages/account/src/lib/account-routing.module.ts b/npm/ng-packs/packages/account/src/lib/account-routing.module.ts index db4147f2a0..2225e7c592 100644 --- a/npm/ng-packs/packages/account/src/lib/account-routing.module.ts +++ b/npm/ng-packs/packages/account/src/lib/account-routing.module.ts @@ -9,6 +9,7 @@ import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from './components/login/login.component'; import { ManageProfileComponent } from './components/manage-profile/manage-profile.component'; import { RegisterComponent } from './components/register/register.component'; +import { eAccountComponents } from './enums/components'; const routes: Routes = [ { path: '', pathMatch: 'full', redirectTo: 'login' }, @@ -21,7 +22,7 @@ const routes: Routes = [ component: ReplaceableRouteContainerComponent, data: { replaceableComponent: { - key: 'Account.LoginComponent', + key: eAccountComponents.Login, defaultComponent: LoginComponent, } as ReplaceableComponents.RouteData, }, @@ -31,7 +32,7 @@ const routes: Routes = [ component: ReplaceableRouteContainerComponent, data: { replaceableComponent: { - key: 'Account.RegisterComponent', + key: eAccountComponents.Register, defaultComponent: RegisterComponent, } as ReplaceableComponents.RouteData, }, @@ -42,7 +43,7 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { replaceableComponent: { - key: 'Account.ManageProfileComponent', + key: eAccountComponents.ManageProfile, defaultComponent: ManageProfileComponent, } as ReplaceableComponents.RouteData, }, diff --git a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html index 6e732540d4..3377b74a88 100644 --- a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html +++ b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html @@ -1,9 +1,7 @@
- +
@@ -44,7 +44,7 @@
diff --git a/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts b/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts index d19a1dcd6a..f9ee2b8ff7 100644 --- a/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts @@ -1,6 +1,7 @@ import { fadeIn } from '@abp/ng.theme.shared'; import { transition, trigger, useAnimation } from '@angular/animations'; import { Component } from '@angular/core'; +import { eAccountComponents } from '../../enums/components'; @Component({ selector: 'abp-manage-profile', @@ -9,4 +10,8 @@ import { Component } from '@angular/core'; }) export class ManageProfileComponent { selectedTab = 0; + + changePasswordKey = eAccountComponents.ChangePassword; + + personalSettingsKey = eAccountComponents.PersonalSettings; } diff --git a/npm/ng-packs/packages/account/src/lib/components/register/register.component.html b/npm/ng-packs/packages/account/src/lib/components/register/register.component.html index f803d473cd..58fafdf541 100644 --- a/npm/ng-packs/packages/account/src/lib/components/register/register.component.html +++ b/npm/ng-packs/packages/account/src/lib/components/register/register.component.html @@ -1,6 +1,6 @@ ; diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html index 38b22789e9..e00dbfe866 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html @@ -262,7 +262,7 @@ visible: { value: visiblePermissions, twoWay: true } }, outputs: { visibleChange: onVisiblePermissionChange }, - componentKey: 'PermissionManagement.PermissionManagementComponent' + componentKey: permissionManagementKey }; let init = initTemplate " diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts index 1e943a6be6..e208f9ffec 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts @@ -24,6 +24,7 @@ import { import { Identity } from '../../models/identity'; import { IdentityService } from '../../services/identity.service'; import { IdentityState } from '../../states/identity.state'; +import { ePermissionManagementComponents } from '@abp/ng.permission-management'; @Component({ selector: 'abp-users', templateUrl: './users.component.html', @@ -62,6 +63,8 @@ export class UsersComponent implements OnInit { sortKey = ''; + permissionManagementKey = ePermissionManagementComponents.PermissionManagement; + trackByFn: TrackByFunction = (index, item) => Object.keys(item)[0] || index; onVisiblePermissionChange = event => { diff --git a/npm/ng-packs/packages/identity/src/lib/enums/components.ts b/npm/ng-packs/packages/identity/src/lib/enums/components.ts new file mode 100644 index 0000000000..abadd38955 --- /dev/null +++ b/npm/ng-packs/packages/identity/src/lib/enums/components.ts @@ -0,0 +1,4 @@ +export const enum eIdentityComponents { + Roles = 'Identity.RolesComponent', + Users = 'Identity.UsersComponent', +} diff --git a/npm/ng-packs/packages/identity/src/lib/identity-routing.module.ts b/npm/ng-packs/packages/identity/src/lib/identity-routing.module.ts index eff6a7ce98..f7d5463fcd 100644 --- a/npm/ng-packs/packages/identity/src/lib/identity-routing.module.ts +++ b/npm/ng-packs/packages/identity/src/lib/identity-routing.module.ts @@ -10,6 +10,7 @@ import { NgModule, Type } from '@angular/core'; import { RouterModule, Routes, Router, ActivatedRoute } from '@angular/router'; import { RolesComponent } from './components/roles/roles.component'; import { UsersComponent } from './components/users/users.component'; +import { eIdentityComponents } from './enums/components'; const routes: Routes = [ { path: '', redirectTo: 'roles', pathMatch: 'full' }, @@ -24,7 +25,7 @@ const routes: Routes = [ data: { requiredPolicy: 'AbpIdentity.Roles', replaceableComponent: { - key: 'Identity.RolesComponent', + key: eIdentityComponents.Roles, defaultComponent: RolesComponent, } as ReplaceableComponents.RouteData, }, @@ -35,7 +36,7 @@ const routes: Routes = [ data: { requiredPolicy: 'AbpIdentity.Users', replaceableComponent: { - key: 'Identity.UsersComponent', + key: eIdentityComponents.Users, defaultComponent: UsersComponent, } as ReplaceableComponents.RouteData, }, diff --git a/npm/ng-packs/packages/identity/src/public-api.ts b/npm/ng-packs/packages/identity/src/public-api.ts index b401fed1c6..1a2b217931 100644 --- a/npm/ng-packs/packages/identity/src/public-api.ts +++ b/npm/ng-packs/packages/identity/src/public-api.ts @@ -4,6 +4,7 @@ export * from './lib/identity.module'; export * from './lib/actions/identity.actions'; +export * from './lib/enums/components'; export * from './lib/components'; export * from './lib/models/identity'; export * from './lib/services'; diff --git a/npm/ng-packs/packages/permission-management/src/lib/enums/components.ts b/npm/ng-packs/packages/permission-management/src/lib/enums/components.ts new file mode 100644 index 0000000000..175d39c999 --- /dev/null +++ b/npm/ng-packs/packages/permission-management/src/lib/enums/components.ts @@ -0,0 +1,3 @@ +export const enum ePermissionManagementComponents { + PermissionManagement = 'PermissionManagement.PermissionManagementComponent', +} diff --git a/npm/ng-packs/packages/permission-management/src/public-api.ts b/npm/ng-packs/packages/permission-management/src/public-api.ts index 3182363f25..c9a8445ebd 100644 --- a/npm/ng-packs/packages/permission-management/src/public-api.ts +++ b/npm/ng-packs/packages/permission-management/src/public-api.ts @@ -5,6 +5,7 @@ export * from './lib/permission-management.module'; export * from './lib/actions'; export * from './lib/components'; +export * from './lib/enums/components'; export * from './lib/models'; export * from './lib/services'; export * from './lib/states'; diff --git a/npm/ng-packs/packages/setting-management/src/lib/enums/components.ts b/npm/ng-packs/packages/setting-management/src/lib/enums/components.ts new file mode 100644 index 0000000000..7dafe76b7d --- /dev/null +++ b/npm/ng-packs/packages/setting-management/src/lib/enums/components.ts @@ -0,0 +1,3 @@ +export const enum eSettingManagementComponents { + SettingManagement = 'SettingManagement.SettingManagementComponent', +} diff --git a/npm/ng-packs/packages/setting-management/src/lib/setting-management-routing.module.ts b/npm/ng-packs/packages/setting-management/src/lib/setting-management-routing.module.ts index d9a50073e1..f394abc962 100644 --- a/npm/ng-packs/packages/setting-management/src/lib/setting-management-routing.module.ts +++ b/npm/ng-packs/packages/setting-management/src/lib/setting-management-routing.module.ts @@ -6,6 +6,7 @@ import { import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { SettingManagementComponent } from './components/setting-management.component'; +import { eSettingManagementComponents } from './enums/components'; const routes: Routes = [ { @@ -18,7 +19,7 @@ const routes: Routes = [ data: { requiredPolicy: 'AbpAccount.SettingManagement', replaceableComponent: { - key: 'SettingManagement.SettingManagementComponent', + key: eSettingManagementComponents.SettingManagement, defaultComponent: SettingManagementComponent, } as ReplaceableComponents.RouteData, }, diff --git a/npm/ng-packs/packages/setting-management/src/public-api.ts b/npm/ng-packs/packages/setting-management/src/public-api.ts index 8027d769f9..ac030c3ccf 100644 --- a/npm/ng-packs/packages/setting-management/src/public-api.ts +++ b/npm/ng-packs/packages/setting-management/src/public-api.ts @@ -1,2 +1,3 @@ export * from './lib/setting-management.module'; export * from './lib/components/setting-management.component'; +export * from './lib/enums/components'; diff --git a/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html b/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html index 2b0276ede8..10761a3618 100644 --- a/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html +++ b/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html @@ -141,7 +141,9 @@
- +
- + , }, diff --git a/npm/ng-packs/packages/tenant-management/src/public-api.ts b/npm/ng-packs/packages/tenant-management/src/public-api.ts index 20cecd353f..003074b2c4 100644 --- a/npm/ng-packs/packages/tenant-management/src/public-api.ts +++ b/npm/ng-packs/packages/tenant-management/src/public-api.ts @@ -1,6 +1,7 @@ export * from './lib/tenant-management.module'; export * from './lib/actions'; export * from './lib/components'; +export * from './lib/enums/components'; export * from './lib/models'; export * from './lib/services'; export * from './lib/states'; diff --git a/npm/ng-packs/packages/theme-basic/src/lib/enums/components.ts b/npm/ng-packs/packages/theme-basic/src/lib/enums/components.ts new file mode 100644 index 0000000000..e773e17464 --- /dev/null +++ b/npm/ng-packs/packages/theme-basic/src/lib/enums/components.ts @@ -0,0 +1,5 @@ +export const enum eThemeBasicComponents { + ApplicationLayout = 'Theme.ApplicationLayoutComponent', + AccountLayout = 'Theme.AccountLayoutComponent', + EmptyLayout = 'Theme.EmptyLayoutComponent', +} diff --git a/npm/ng-packs/packages/theme-basic/src/lib/services/initial.service.ts b/npm/ng-packs/packages/theme-basic/src/lib/services/initial.service.ts index 2510b77a8d..c5bc93cbf0 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/services/initial.service.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/services/initial.service.ts @@ -5,6 +5,7 @@ import styles from '../constants/styles'; import { ApplicationLayoutComponent } from '../components/application-layout/application-layout.component'; import { AccountLayoutComponent } from '../components/account-layout/account-layout.component'; import { EmptyLayoutComponent } from '../components/empty-layout/empty-layout.component'; +import { eThemeBasicComponents } from '../enums/components'; @Injectable({ providedIn: 'root' }) export class InitialService { @@ -13,15 +14,15 @@ export class InitialService { this.store.dispatch([ new AddReplaceableComponent({ - key: 'Theme.ApplicationLayoutComponent', + key: eThemeBasicComponents.ApplicationLayout, component: ApplicationLayoutComponent, }), new AddReplaceableComponent({ - key: 'Theme.AccountLayoutComponent', + key: eThemeBasicComponents.AccountLayout, component: AccountLayoutComponent, }), new AddReplaceableComponent({ - key: 'Theme.EmptyLayoutComponent', + key: eThemeBasicComponents.EmptyLayout, component: EmptyLayoutComponent, }), ]); diff --git a/npm/ng-packs/packages/theme-basic/src/public-api.ts b/npm/ng-packs/packages/theme-basic/src/public-api.ts index b1316143c0..ee01995829 100644 --- a/npm/ng-packs/packages/theme-basic/src/public-api.ts +++ b/npm/ng-packs/packages/theme-basic/src/public-api.ts @@ -5,5 +5,6 @@ export * from './lib/theme-basic.module'; export * from './lib/actions'; export * from './lib/components'; +export * from './lib/enums/components'; export * from './lib/models'; export * from './lib/states'; From da2a45b1125c8c69c100a6a3f0e8cb800654ebd1 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 10 Apr 2020 09:48:37 +0300 Subject: [PATCH 44/61] docs: update component replacement doc resolves #3080 --- docs/en/UI/Angular/Component-Replacement.md | 30 ++++++--------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index d718b46117..d1f88c0400 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -11,15 +11,18 @@ Create a new component that you want to use instead of an ABP component. Add tha Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below: ```js -import { ..., AddReplaceableComponent } from '@abp/ng.core'; +import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action +import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum +import { Store } from '@ngxs/store'; // imported Store +//... export class AppComponent { - constructor(..., private store: Store) {} + constructor(..., private store: Store) {} // injected Store ngOnInit() { this.store.dispatch( new AddReplaceableComponent({ component: YourNewRoleComponent, - key: 'Identity.RolesComponent', + key: eIdentityComponents.Roles, }), ); //... @@ -56,6 +59,7 @@ Open the `app.component.ts` and add the below content: ```js import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent +import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent import { Store } from '@ngxs/store'; // imported Store //... @@ -67,7 +71,7 @@ export class AppComponent { this.store.dispatch( new AddReplaceableComponent({ component: MyApplicationLayoutComponent, - key: 'Theme.ApplicationLayoutComponent', + key: eThemeBasicComponents.AccountLayout, }), ); @@ -76,24 +80,6 @@ export class AppComponent { } ``` -### Available Replaceable Components - -| Component key | Description | -| -------------------------------------------------- | --------------------------------------------- | -| Account.LoginComponent | Login page | -| Account.RegisterComponent | Register page | -| Account.ManageProfileComponent | Manage Profile page | -| Account.AuthWrapperComponent | This component wraps register and login pages | -| Account.ChangePasswordComponent | Change password form | -| Account.PersonalSettingsComponent | Personal settings form | -| Account.TenantBoxComponentInputs | Tenant changing box | -| FeatureManagement.FeatureManagementComponent | Features modal | -| Identity.UsersComponent | Users page | -| Identity.RolesComponent | Roles page | -| PermissionManagement.PermissionManagementComponent | Permissions modal | -| SettingManagement.SettingManagementComponent | Setting Management page | -| TenantManagement.TenantsComponent | Tenants page | - ## What's Next? - [Custom Setting Page](./Custom-Setting-Page.md) From bc4c454fe2a863cb2e7d3a4e251040274925e4d4 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 10 Apr 2020 09:53:06 +0300 Subject: [PATCH 45/61] chore: update ng-packs abp versions --- npm/ng-packs/package.json | 26 ++++---- npm/ng-packs/yarn.lock | 132 +++++++++++++++++++------------------- 2 files changed, 79 insertions(+), 79 deletions(-) diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index a2b9011647..4f65497d69 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -22,19 +22,19 @@ "generate:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" }, "devDependencies": { - "@abp/ng.account": "~2.4.1", - "@abp/ng.account.config": "~2.4.1", - "@abp/ng.core": "^2.4.1", - "@abp/ng.feature-management": "^2.4.1", - "@abp/ng.identity": "~2.4.1", - "@abp/ng.identity.config": "~2.4.1", - "@abp/ng.permission-management": "^2.4.1", - "@abp/ng.setting-management": "~2.4.1", - "@abp/ng.setting-management.config": "~2.4.1", - "@abp/ng.tenant-management": "~2.4.1", - "@abp/ng.tenant-management.config": "~2.4.1", - "@abp/ng.theme.basic": "~2.4.1", - "@abp/ng.theme.shared": "^2.4.1", + "@abp/ng.account": "~2.5.0", + "@abp/ng.account.config": "~2.5.0", + "@abp/ng.core": "~2.5.0", + "@abp/ng.feature-management": "~2.5.0", + "@abp/ng.identity": "~2.5.0", + "@abp/ng.identity.config": "~2.5.0", + "@abp/ng.permission-management": "~2.5.0", + "@abp/ng.setting-management": "~2.5.0", + "@abp/ng.setting-management.config": "~2.5.0", + "@abp/ng.tenant-management": "~2.5.0", + "@abp/ng.tenant-management.config": "~2.5.0", + "@abp/ng.theme.basic": "~2.5.0", + "@abp/ng.theme.shared": "~2.5.0", "@abp/utils": "^2.4.0", "@angular-builders/jest": "^8.2.0", "@angular-devkit/build-angular": "~0.803.21", diff --git a/npm/ng-packs/yarn.lock b/npm/ng-packs/yarn.lock index 1d93a6ef79..5e156ae867 100644 --- a/npm/ng-packs/yarn.lock +++ b/npm/ng-packs/yarn.lock @@ -2,26 +2,26 @@ # yarn lockfile v1 -"@abp/ng.account.config@^2.4.1", "@abp/ng.account.config@~2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.4.1.tgz#871f3947d7751203ba8fd00902b5e87c2894cca1" - integrity sha512-6ikmQi+hmZi59xtEq54MZftH0i9qzxYWex4lk33woBiNXHYBKA/AzrXkksgjnQgmN5kydxGsHcq2vlAgb+WgSA== +"@abp/ng.account.config@^2.5.0", "@abp/ng.account.config@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.5.0.tgz#08122916985765f62b0cd9ef2d9a8609a485131b" + integrity sha512-Ld7nsGOw3TafWaJ64umHB3NwI4tgeLKkhU6IO7/Dbx0UZvcRepND07pLPAbpgxPxSffNDmREHnA+lrtP1i96vA== dependencies: tslib "^1.9.0" -"@abp/ng.account@~2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.4.1.tgz#09c51101b2511f0352ffd3ef56b5f2e74d81cead" - integrity sha512-mFhFPKPsmHNYKBXWxK2ht9TkXccj5xkPfn3PGSkX0QiqJZ+SrJBb3bYa2VOsQ9BstWB1/FEX3ke07MB5h4uRTQ== +"@abp/ng.account@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.5.0.tgz#b5271da490136f7e23cbae21588e9fd6f8f398c7" + integrity sha512-C6RD9L0+gkyjuCSUGP9C55h+oMIoPNkSe3fF4RWkLaqb1hLriIEqyeTODHnhdV4TfEVkVCmhS88XhoNGwmFz/g== dependencies: - "@abp/ng.account.config" "^2.4.1" - "@abp/ng.theme.shared" "^2.4.1" + "@abp/ng.account.config" "^2.5.0" + "@abp/ng.theme.shared" "^2.5.0" tslib "^1.9.0" -"@abp/ng.core@^2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.4.1.tgz#4a568ef1f0d9e33de555a8942a0ef421e754d55f" - integrity sha512-aML9iyneu8UDaKP1x4dBzr20EVXhaVO75g1A235OEqT+qYJ7Z1qXgp01u8DgeNM+jxNWc2UVYgtPf5SbMJsR4Q== +"@abp/ng.core@^2.5.0", "@abp/ng.core@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.5.0.tgz#e7168615b4078e3fa494c4efcc69842acbd2c627" + integrity sha512-opYkOFCKeU17YvNJhfcG1dOwZZ4F2QGspTNTagNZ9fepmeNl0yqFamFdRM6irfEEL3cOBQhUpI2NQZP+ugLOEg== dependencies: "@abp/utils" "^2.4.0" "@angular/localize" "~9.1.0" @@ -35,86 +35,86 @@ ts-toolbelt "^6.3.6" tslib "^1.9.0" -"@abp/ng.feature-management@^2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.4.1.tgz#4e7f16fcf109d76bd3eadb296a54730da770c8c1" - integrity sha512-adkQKFr1XsYAq/qpXxtLo5ULBuTer12viPLgfldEmsnsGWi/0bCVr0U4Gw+4KdsE2SjmcMriaZnL2/yVJa5rjA== +"@abp/ng.feature-management@^2.5.0", "@abp/ng.feature-management@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.5.0.tgz#3bc4501a2abbe6b6447e5b4a2b2d86fb07ce8a59" + integrity sha512-keZ3gCDMvU/e17tsBBrZpLmpIpDm/2TTSAmCoRRy3GH4BQICXr/XJmssStNx7V7sdo6vAPiW9pcM9zI/C8IrBw== dependencies: - "@abp/ng.theme.shared" "^2.4.1" + "@abp/ng.theme.shared" "^2.5.0" tslib "^1.9.0" -"@abp/ng.identity.config@^2.4.1", "@abp/ng.identity.config@~2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.4.1.tgz#74a0c3b18ba1737b83144f1b03c97e75dc1afe0b" - integrity sha512-PeieQ/GeThgxx22n3rBnqPQuCylB5+0Vog53GAhFQFXXH5W0vMDbxTARYnQr74gnAf3UYS48XzEzyvAuIz1MGg== +"@abp/ng.identity.config@^2.5.0", "@abp/ng.identity.config@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.5.0.tgz#d5daad786ae8c6d61e887f4ec52497635abfff94" + integrity sha512-nvVkRzT3gsLZTzjjMkR20FjmmnxDC7viUNRSjp4ufGqHfWbUaNS16chrWqvr+0f7JEzeULKWfXQEjWafqLWBbg== dependencies: tslib "^1.9.0" -"@abp/ng.identity@~2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.4.1.tgz#ede6ebcc1d7d2f15ce9aeeb3ab8b25bb26def5cd" - integrity sha512-4T1BnvL13UggIj0SrV5AWN1VLMnej59jaLEyjAbu1Myry9xeiQ8Grh0GOBJ6euNOKiuYtqwQlFl06NlLdHqu2g== +"@abp/ng.identity@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.5.0.tgz#b2e2dea559ce89dce52a82cf02d612864076ab2c" + integrity sha512-RIZnYRhNMbyjaX/Nb43fgG8/hBcvfBLpd3ZpP6arJDUY4YLvN3zvGJ9d52+3nGgj9WpXZTm+/X7c8Iuu5a2ocA== dependencies: - "@abp/ng.identity.config" "^2.4.1" - "@abp/ng.permission-management" "^2.4.1" - "@abp/ng.theme.shared" "^2.4.1" + "@abp/ng.identity.config" "^2.5.0" + "@abp/ng.permission-management" "^2.5.0" + "@abp/ng.theme.shared" "^2.5.0" tslib "^1.9.0" -"@abp/ng.permission-management@^2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.4.1.tgz#e58d650fbcf8a161bbce01083b35891cba3e1aa4" - integrity sha512-gNfVmXAIPQP+L5Bq4bj/LAU+mKhJqAd9AQKxDLmqmoXpNjrjxA0Y51bU/XY/mjYx+tZC2i59L+eMIOZfVmn3Dw== +"@abp/ng.permission-management@^2.5.0", "@abp/ng.permission-management@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.5.0.tgz#c769a97aee8517ec724dc5b7756e1dfc8fe7fcf1" + integrity sha512-grmJ46Qf26cwGfDt5acL6eXXWBDgjvL/04/ZLtxsP4Hf+tSlnL/1rWz5fxMF8548sQ6vZlVIdrSa4Tz/Dh0WLw== dependencies: - "@abp/ng.theme.shared" "^2.4.1" + "@abp/ng.theme.shared" "^2.5.0" tslib "^1.9.0" -"@abp/ng.setting-management.config@^2.4.1", "@abp/ng.setting-management.config@~2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.4.1.tgz#4e643934647aaf5f5ea70045364e8856a1958bf5" - integrity sha512-z76nIPa9NCUqJwS2UYYCNrOsv9r+EfJ+e32iplrN5anqSfi0q2QAbK/Qjo7PIaB6x/JsEER/zQ5gAFKfPle41w== +"@abp/ng.setting-management.config@^2.5.0", "@abp/ng.setting-management.config@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.5.0.tgz#4841d73b9df7763084dcac45bd656fbee87c4602" + integrity sha512-mSihz0aoB5Ly+tanPE4Vh3Aam9g3nDtSQelUuFJoyRnw1HSmMmWOqnJi9UEguhCEBtww9G5P6OC8/icpHaq/3Q== dependencies: tslib "^1.9.0" -"@abp/ng.setting-management@~2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.4.1.tgz#b3028d8e65866529c2bb6a22980a110fa5247b67" - integrity sha512-oA6ZUJUQFvmY7QLff/cXnmefN2hGTwhD9iumZSckvl1TitOhzJuj1uQ8kT2c0Az13a5R14Zuk44KYwn2wGFeSg== +"@abp/ng.setting-management@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.5.0.tgz#32291ebc6610838f825cc79d091700d578586e0a" + integrity sha512-maamVGc5L/44XsRANXaVYLn9uocGUH8RUtKXZT9iKdlcsemKa7xEMzZ84lzoiAu+xAfqfJm/R7TolC/gWonUDg== dependencies: - "@abp/ng.setting-management.config" "^2.4.1" - "@abp/ng.theme.shared" "^2.4.1" + "@abp/ng.setting-management.config" "^2.5.0" + "@abp/ng.theme.shared" "^2.5.0" tslib "^1.9.0" -"@abp/ng.tenant-management.config@^2.4.1", "@abp/ng.tenant-management.config@~2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.4.1.tgz#fe77a00fee18d2f221e68b7edd1d9d52c4a24595" - integrity sha512-0WsqESU5eXCkH9A4AVSZCOGOTo+pL8jW9raYxCbxp8A6GGxHA78UiqjLO/ZqA9ercXt9lh2s3JIbclhsOJWHBA== +"@abp/ng.tenant-management.config@^2.5.0", "@abp/ng.tenant-management.config@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.5.0.tgz#00b3abae5d513d61fcc0aae6c73d64a61d087e5f" + integrity sha512-gaW9n+Fo9AH2OPw3V3qaxTvG8zIe4jjMXKIvzqk+e4djXVjLHZ3fdP+uyJqpMmaypyd2REoVCARqVFHgvZBzaA== dependencies: tslib "^1.9.0" -"@abp/ng.tenant-management@~2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.4.1.tgz#e9bffd96b1805036d840d078a8e7c78dd027c5eb" - integrity sha512-HEyWwXrgKWdrZ84Txw+0k+PGBb/ekqMKmwytOrG3Z7fxTRqWVMP/6QbHcbJD929zR4aehAmEAfvex/210qD1rw== +"@abp/ng.tenant-management@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.5.0.tgz#7452ab92d9d7f2bf1200a956e7ad8800c4ccebb1" + integrity sha512-7O7tsJl2OVbEIiSJ17r+ZJFcynOo89el0RREwihACkBz+2TeQoHo2ycZ7oPXauMy1aRjsjpkmbCJGP28OD0a6w== dependencies: - "@abp/ng.feature-management" "^2.4.1" - "@abp/ng.tenant-management.config" "^2.4.1" - "@abp/ng.theme.shared" "^2.4.1" + "@abp/ng.feature-management" "^2.5.0" + "@abp/ng.tenant-management.config" "^2.5.0" + "@abp/ng.theme.shared" "^2.5.0" tslib "^1.9.0" -"@abp/ng.theme.basic@~2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.4.1.tgz#faf06ea09557323e77ffb648f55ff915ecc252de" - integrity sha512-fV9a6wfeWN5C1UmYGDzaT3bQMfkUzAt0T/JRaAiWTsWTUV/y69prGqD2dMEMvdIAPR3zhKK/125N6+apl/l0iQ== +"@abp/ng.theme.basic@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.5.0.tgz#ef13f448ea6f356a5571d364da00e971e715cfec" + integrity sha512-+q22k5AfxgFRtGP4YO602jmeeuH2pfVBzCcJUmwRzE2xiINx4e55zCwKiFl5YvNUttcKkaNNhj9LcOXU6W42rw== dependencies: - "@abp/ng.theme.shared" "^2.4.1" + "@abp/ng.theme.shared" "^2.5.0" tslib "^1.9.0" -"@abp/ng.theme.shared@^2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.4.1.tgz#1c0c5c1714099136a6ecd256df66602099721ef3" - integrity sha512-iXqVrpjCYkG8Dc6tocn21qDnu2i9bi625CEk7J+U7O+L0X2ECYqHJXH8DqQrEeQ0F3lun0gOXUc3wo83G15PrA== +"@abp/ng.theme.shared@^2.5.0", "@abp/ng.theme.shared@~2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.5.0.tgz#a9c53f95f1a0bdf2fe046838e64c658ce54c5655" + integrity sha512-iRft0LWh9dzpnrKxmo0AuAXND6+o3Eov6LFpNbZD7guuZfeFVehcSX/X0ZYIpq1a/A7cfegtMEokdwRb0cGdGA== dependencies: - "@abp/ng.core" "^2.4.1" + "@abp/ng.core" "^2.5.0" "@fortawesome/fontawesome-free" "^5.12.1" "@ng-bootstrap/ng-bootstrap" "^5.3.0" "@ngx-validate/core" "^0.0.7" From af51255d3dff3c11b9cbfabe18928773f921c65a Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 10 Apr 2020 09:56:17 +0300 Subject: [PATCH 46/61] refactor: make content projection a separate service --- .../UI/Angular/Content-Projection-Service.md | 78 +++++++++++++++++++ docs/en/UI/Angular/Dom-Insertion-Service.md | 36 +-------- docs/en/docs-nav.json | 4 + .../services/content-projection.service.ts | 14 ++++ .../src/lib/services/dom-insertion.service.ts | 10 +-- .../packages/core/src/lib/services/index.ts | 1 + .../tests/content-projection.service.spec.ts | 38 +++++++++ .../lib/tests/dom-insertion.service.spec.ts | 32 +------- 8 files changed, 143 insertions(+), 70 deletions(-) create mode 100644 docs/en/UI/Angular/Content-Projection-Service.md create mode 100644 npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts diff --git a/docs/en/UI/Angular/Content-Projection-Service.md b/docs/en/UI/Angular/Content-Projection-Service.md new file mode 100644 index 0000000000..6bd30e44c6 --- /dev/null +++ b/docs/en/UI/Angular/Content-Projection-Service.md @@ -0,0 +1,78 @@ +# Content Projection + +You can use the `ContentProjectionService` in @abp/ng.core package in order to project content in an easy and explicit way. + +## Getting Started + +You do not have to provide the `ContentProjectionService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services. + +```js +import { ContentProjectionService } from '@abp/ng.core'; + +@Component({ + /* class metadata here */ +}) +class DemoComponent { + constructor(private contentProjectionService: ContentProjectionService) {} +} +``` + +## Usage + +You can use the `projectContent` method of `ContentProjectionService` to render components and templates dynamically in your project. + +### How to Project Components to Root Level + +If you pass a `RootComponentProjectionStrategy` as the first parameter of `projectContent` method, the `ContentProjectionService` will resolve the projected component and place it at the root level. If provided, it will also pass the component a context. + +```js +const strategy = PROJECTION_STRATEGY.AppendComponentToBody( + SomeOverlayComponent, + { someOverlayProp: "SOME_VALUE" } +); + +const componentRef = this.ContentProjectionService.projectContent(strategy); +``` + +In the example above, `SomeOverlayComponent` component will placed at the **end** of `` and a `ComponentRef` will be returned. Additionally, the given context will be applied, so `someOverlayProp` of the component will be set to `SOME_VALUE`. + +> You should keep the returned `ComponentRef` instance, as it is a reference to the projected component and you will need that reference to destroy the projected view and the component instance. + +### How to Project Components and Templates into a Container + +If you pass a `ComponentProjectionStrategy` or `TemplateProjectionStrategy` as the first parameter of `projectContent` method, and a `ViewContainerRef` as the second parameter of that strategy, the `ContentProjectionService` will project the component or template to the given container. If provided, it will also pass the component or the template a context. + +```js +const strategy = PROJECTION_STRATEGY.ProjectComponentToContainer( + SomeComponent, + viewContainerRefOfTarget, + { someProp: "SOME_VALUE" } +); + +const componentRef = this.ContentProjectionService.projectContent(strategy); +``` + +In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeComponent` component will placed inside it. Moreover, the given context will be applied, so `someProp` of the component will be set to `SOME_VALUE`. + +> You should keep the returned `ComponentRef` or `EmbeddedViewRef`, as they are a reference to the projected content and you will need them to destroy it when necessary. + +Please refer to [ProjectionStrategy](./Projection-Strategy.md) to see all available projection strategies and how you can build your own projection strategy. + +## API + +### projectContent + +```js +projectContent | TemplateRef>( + projectionStrategy: ProjectionStrategy, + injector = this.injector, +): ComponentRef | EmbeddedViewRef +``` + +- `projectionStrategy` parameter is the primary focus here and is explained above. +- `injector` parameter is the `Injector` instance you can pass to the projected content. It is not used in `TemplateProjectionStrategy`. + + +## What's Next? + +- [TrackByService](./Track-By-Service.md) diff --git a/docs/en/UI/Angular/Dom-Insertion-Service.md b/docs/en/UI/Angular/Dom-Insertion-Service.md index 5159ae2c35..d5ea9fe3a2 100644 --- a/docs/en/UI/Angular/Dom-Insertion-Service.md +++ b/docs/en/UI/Angular/Dom-Insertion-Service.md @@ -1,4 +1,4 @@ -# How to Insert Scripts and Styles +# Dom Insertion (of Scripts and Styles) You can use the `DomInsertionService` in @abp/ng.core package in order to insert scripts and styles in an easy and explicit way. @@ -71,45 +71,17 @@ In the example above, `` element will place at t Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. -### How to Project Components & Templates - -If you pass a `ProjectionStrategy` as the first parameter of `projectContent` method, the `DomInsertionService` will resolve the projected component or template and place it at the designated target, such as containers or document body. If provided, it will also pass the component or the template a context. - -```js -const componentRef = this.domInsertionService.projectContent( - PROJECTION_STRATEGY.AppendComponentToBody(SomeOverlayComponent) -); -``` - -In the example above, `SomeOverlayComponent` component will placed at the **end** of `` and a `ComponentRef` will be returned. - -> You should keep the returned `ComponentRef` instance, as it is a reference to the projected component and you will need that reference to destroy the projected view and the component instance. - -```js -const componentRef = this.domInsertionService.projectContent( - PROJECTION_STRATEGY.ProjectComponentToContainer( - SomeOverlayComponent, - viewContainerRefOfTarget, - { someProp: "SOME_VALUE" } - ) -); -``` - -In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeOverlayComponent` component will placed inside it. Moreover, the given context will be applied, so `someProp` of the component will be set to `SOME_VALUE`. - -Please refer to [ProjectionStrategy](./Projection-Strategy.md) to see all available projection strategies and how you can build your own projection strategy. - ## API ### insertContent ```js -injectContent(injector: Injector): ComponentRef | EmbeddedViewRef +insertContent(contentStrategy: ContentStrategy): void ``` -`injector` parameter is the `Injector` instance you can pass to the projected content. It is not used in `TemplateProjectionStrategy`. +- `contentStrategy` parameter is the primary focus here and is explained above. ## What's Next? -- [TrackByService](./Track-By-Service.md) +- [ContentProjectionService](./Content-Projection-Service.md) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 471e215e3a..20214b8447 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -353,6 +353,10 @@ "text": "DomInsertionService", "path": "UI/Angular/Dom-Insertion-Service.md" }, + { + "text": "ContentProjectionService", + "path": "UI/Angular/Content-Projection-Service.md" + }, { "text": "TrackByService", "path": "UI/Angular/Track-By-Service.md" diff --git a/npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts b/npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts new file mode 100644 index 0000000000..dfcdea330a --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts @@ -0,0 +1,14 @@ +import { Injectable, Injector, TemplateRef, Type } from '@angular/core'; +import { ProjectionStrategy } from '../strategies/projection.strategy'; + +@Injectable({ providedIn: 'root' }) +export class ContentProjectionService { + constructor(private injector: Injector) {} + + projectContent | TemplateRef>( + projectionStrategy: ProjectionStrategy, + injector = this.injector, + ) { + return projectionStrategy.injectContent(injector); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts b/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts index 5e53a64995..c10967ebbb 100644 --- a/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts @@ -1,6 +1,5 @@ -import { Injectable, Injector, TemplateRef, Type } from '@angular/core'; +import { Injectable, Injector } from '@angular/core'; import { ContentStrategy } from '../strategies/content.strategy'; -import { ProjectionStrategy } from '../strategies/projection.strategy'; import { generateHash } from '../utils'; @Injectable({ providedIn: 'root' }) @@ -17,11 +16,4 @@ export class DomInsertionService { contentStrategy.insertElement(); this.inserted.add(hash); } - - projectContent | TemplateRef>( - projectionStrategy: ProjectionStrategy, - injector = this.injector, - ) { - return projectionStrategy.injectContent(injector); - } } diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index a64e721c67..f8b016bbd1 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -1,6 +1,7 @@ export * from './application-configuration.service'; export * from './auth.service'; export * from './config-state.service'; +export * from './content-projection.service'; export * from './dom-insertion.service'; export * from './lazy-load.service'; export * from './localization.service'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts new file mode 100644 index 0000000000..30f9f92e73 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts @@ -0,0 +1,38 @@ +import { Component, ComponentRef, NgModule } from '@angular/core'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; +import { ContentProjectionService } from '../services'; +import { PROJECTION_STRATEGY } from '../strategies'; + +describe('ContentProjectionService', () => { + @Component({ template: '
bar
' }) + class TestComponent {} + + // createServiceFactory does not accept entryComponents directly + @NgModule({ + declarations: [TestComponent], + entryComponents: [TestComponent], + }) + class TestModule {} + + let componentRef: ComponentRef; + let spectator: SpectatorService; + const createService = createServiceFactory({ + service: ContentProjectionService, + imports: [TestModule], + }); + + beforeEach(() => (spectator = createService())); + + afterEach(() => componentRef.destroy()); + + describe('#projectContent', () => { + it('should call injectContent of given projectionStrategy and return what it returns', () => { + const strategy = PROJECTION_STRATEGY.AppendComponentToBody(TestComponent); + componentRef = spectator.service.projectContent(strategy); + const foo = document.querySelector('body > ng-component > div.foo'); + + expect(componentRef).toBeInstanceOf(ComponentRef); + expect(foo.textContent).toBe('bar'); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts index 8ae75c2f15..f8e8565496 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts @@ -1,25 +1,11 @@ -import { Component, ComponentRef, NgModule } from '@angular/core'; import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; import { DomInsertionService } from '../services'; -import { CONTENT_STRATEGY, PROJECTION_STRATEGY } from '../strategies'; +import { CONTENT_STRATEGY } from '../strategies'; describe('DomInsertionService', () => { - @Component({ template: '
bar
' }) - class TestComponent {} - - // createServiceFactory does not accept entryComponents directly - @NgModule({ - declarations: [TestComponent], - entryComponents: [TestComponent], - }) - class TestModule {} - - let spectator: SpectatorService; - const createService = createServiceFactory({ - service: DomInsertionService, - imports: [TestModule], - }); let styleElements: NodeListOf; + let spectator: SpectatorService; + const createService = createServiceFactory(DomInsertionService); beforeEach(() => (spectator = createService())); @@ -56,16 +42,4 @@ describe('DomInsertionService', () => { expect(spectator.service.inserted.has(1437348290)).toBe(true); }); }); - - describe('#projectContent', () => { - it('should call injectContent of given projectionStrategy and return what it returns', () => { - const strategy = PROJECTION_STRATEGY.AppendComponentToBody(TestComponent); - const componentRef = spectator.service.projectContent(strategy); - const foo = document.querySelector('body > ng-component > div.foo'); - - expect(componentRef).toBeInstanceOf(ComponentRef); - expect(foo.textContent).toBe('bar'); - componentRef.destroy(); - }); - }); }); From 83d9ad3f08678dfeb3db9b3969ff921a52ec9cb4 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 10 Apr 2020 10:04:04 +0300 Subject: [PATCH 47/61] refactor: remove unnecessary initializer --- .../packages/core/src/lib/tests/projection.strategy.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts index 1166d44f57..a0b3bb61bb 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts @@ -213,7 +213,7 @@ describe('TemplateProjectionStrategy', () => { describe('PROJECTION_STRATEGY', () => { const content = undefined; const containerRef = ({ length: 0 } as any) as ViewContainerRef; - let context = undefined; + let context: any; test.each` name | Strategy | containerStrategy From 1b4bfd03f48aadc8ee21a3302c5f962bb1a68899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levent=20Arman=20=C3=96zak?= Date: Fri, 10 Apr 2020 10:17:16 +0300 Subject: [PATCH 48/61] docs: use ApplicationLayout instead of AccountLayout key --- docs/en/UI/Angular/Component-Replacement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index d1f88c0400..fb85aa476e 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -71,7 +71,7 @@ export class AppComponent { this.store.dispatch( new AddReplaceableComponent({ component: MyApplicationLayoutComponent, - key: eThemeBasicComponents.AccountLayout, + key: eThemeBasicComponents.ApplicationLayout, }), ); From 6ae41180056d71cf4d12d4a9b2d98adefa7d46a2 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 10 Apr 2020 10:25:38 +0300 Subject: [PATCH 49/61] refactor: remove unused injector --- .../packages/core/src/lib/services/dom-insertion.service.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts b/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts index c10967ebbb..d4b30b731d 100644 --- a/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector } from '@angular/core'; +import { Injectable } from '@angular/core'; import { ContentStrategy } from '../strategies/content.strategy'; import { generateHash } from '../utils'; @@ -6,8 +6,6 @@ import { generateHash } from '../utils'; export class DomInsertionService { readonly inserted = new Set(); - constructor(private injector: Injector) {} - insertContent(contentStrategy: ContentStrategy) { const hash = generateHash(contentStrategy.content); From 0d1295ad7beb32dcc76e4ca5042d70104fd8e127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 10 Apr 2020 10:28:02 +0300 Subject: [PATCH 50/61] Don't add en language file for the timeago. --- .../Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs index a276849db6..9edbf065cb 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs @@ -17,6 +17,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago public override void ConfigureDynamicResources(BundleConfigurationContext context) { var cultureName = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; + if (cultureName.StartsWith("en")) + { + return; + } + var cultureFileName = $"/libs/timeago/locales/jquery.timeago.{cultureName}.js"; if (context.FileProvider.GetFileInfo(cultureFileName).Exists) From 0999be319d3a5c6411ed6e679ae5778a7be61a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 10 Apr 2020 12:10:20 +0300 Subject: [PATCH 51/61] Add IHasExtraProperties Interface section --- docs/en/Object-Extensions.md | 106 ++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/docs/en/Object-Extensions.md b/docs/en/Object-Extensions.md index fad3ff2b0c..70a83e8ef2 100644 --- a/docs/en/Object-Extensions.md +++ b/docs/en/Object-Extensions.md @@ -1,3 +1,107 @@ # Object Extensions -TODO \ No newline at end of file +ABP Framework provides an **object extension system** to allow you to **add extra properties** to an existing object **without modifying** the related class. This allows to extend functionalities implemented by a depended [application module](Modules/Index.md), especially when you want to [extend entities](Customizing-Application-Modules-Extending-Entities.md) and [DTOs](Customizing-Application-Modules-Overriding-Services.md) defined by the module. + +> Object extension system is not normally not needed for your own objects since you can easily add regular properties to your own classes. + +## IHasExtraProperties Interface + +This is the interface to make a class extensible. It simply defines a `Dictionary` property: + +````csharp +Dictionary ExtraProperties { get; } +```` + +Then you can add or get extra properties using this dictionary. + +### Base Classes + +`IHasExtraProperties` interface is implemented by several base classes by default: + +* Implemented by the `AggregateRoot` class (see [entities](Entities.md)). +* Implemented by `ExtensibleEntityDto`, `ExtensibleAuditedEntityDto`... base [DTO](Data-Transfer-Objects.md) classes. +* Implemented by the `ExtensibleObject`, which is a simple base class can be inherited for any type of object. + +So, if you inherit from these classes, your class will also be extensible. If not, you can always implement it manually. + +### Fundamental Extension Methods + +While you can directly use the `ExtraProperties` property of a class, it is suggested to use the following extension methods while working with the extra properties. + +#### SetProperty + +Used to set the value of an extra property: + +````csharp +user.SetProperty("Title", "My Title"); +user.SetProperty("IsSuperUser", true); +```` + +`SetProperty` returns the same object, so you can chain it: + +````csharp +user.SetProperty("Title", "My Title") + .SetProperty("IsSuperUser", true); +```` + +#### GetProperty + +Used to read the value of an extra property: + +````csharp +var title = user.GetProperty("Title"); + +if (user.GetProperty("IsSuperUser")) +{ + //... +} +```` + +* `GetProperty` is a generic method and takes the object type as the generic parameter. +* Returns the default value if given property was not set before (default value is `0` for `int`, `false` for `bool`... etc). + +##### Non Primitive Property Types + +If your property type is not a primitive (int, bool, enum, string... etc) type, then you need to use non-generic version of the `GetProperty` which returns an `object`. + +#### HasProperty + +Used to check if the object has a property set before. + +#### RemoveProperty + +Used to remove a property from the object. Use this methods instead of setting a `null` value for the property. + +### Some Best Practices + +Using magic strings for the property names is dangerous since you can easily type the property name wrong - it is not type safe. Instead; + +* Define a constant for your extra property names +* Create extension methods to easily set your extra properties. + +Example: + +````csharp +public static class IdentityUserExtensions +{ + private const string TitlePropertyName = "Title"; + + public static void SetTitle(this IdentityUser user, string title) + { + user.SetProperty(TitlePropertyName, title); + } + + public static string GetTitle(this IdentityUser user) + { + return user.GetProperty(TitlePropertyName); + } +} +```` + +Then you can easily set or get the `Title` property: + +````csharp +user.SetTitle("My Title"); +var title = user.GetTitle(); +```` + From 98f97f0228970c8f8f00554790c70bdddb4b7056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 10 Apr 2020 12:10:40 +0300 Subject: [PATCH 52/61] Rename ExtensibleObjectMapper file. --- .../{ExtendedObjectMapper.cs => ExtensibleObjectMapper.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/{ExtendedObjectMapper.cs => ExtensibleObjectMapper.cs} (100%) diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtendedObjectMapper.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs similarity index 100% rename from framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtendedObjectMapper.cs rename to framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs From 30622bb8f8a117562d0fd5435c869ec65ddbfe9b Mon Sep 17 00:00:00 2001 From: Mehmet Erim <34455572+mehmet-erim@users.noreply.github.com> Date: Fri, 10 Apr 2020 13:10:59 +0300 Subject: [PATCH 53/61] Update Content-Projection-Service.md --- docs/en/UI/Angular/Content-Projection-Service.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/UI/Angular/Content-Projection-Service.md b/docs/en/UI/Angular/Content-Projection-Service.md index 6bd30e44c6..664123c2fb 100644 --- a/docs/en/UI/Angular/Content-Projection-Service.md +++ b/docs/en/UI/Angular/Content-Projection-Service.md @@ -31,7 +31,7 @@ const strategy = PROJECTION_STRATEGY.AppendComponentToBody( { someOverlayProp: "SOME_VALUE" } ); -const componentRef = this.ContentProjectionService.projectContent(strategy); +const componentRef = this.contentProjectionService.projectContent(strategy); ``` In the example above, `SomeOverlayComponent` component will placed at the **end** of `` and a `ComponentRef` will be returned. Additionally, the given context will be applied, so `someOverlayProp` of the component will be set to `SOME_VALUE`. From d27ddb398b070d7a0b960d0855c7bb43de476142 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 10 Apr 2020 14:29:42 +0300 Subject: [PATCH 54/61] refactor: add undefined control to loading.directive --- .../theme-shared/src/lib/directives/loading.directive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts index 40b76cbfc8..bed4fab0da 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts @@ -54,7 +54,7 @@ export class LoadingDirective implements OnInit, OnDestroy { if (newValue && !this.rootNode) { this.rootNode = (this.componentRef.hostView as EmbeddedViewRef).rootNodes[0]; this.targetElement.appendChild(this.rootNode); - } else { + } else if (this.rootNode) { this.renderer.removeChild(this.rootNode.parentElement, this.rootNode); this.rootNode = null; } From 04cd0eae4d82232fc68c2fd99c1d92bfddb4b533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 10 Apr 2020 14:46:11 +0300 Subject: [PATCH 55/61] Update Object-Extensions.md --- docs/en/Object-Extensions.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/en/Object-Extensions.md b/docs/en/Object-Extensions.md index 70a83e8ef2..5841eb739c 100644 --- a/docs/en/Object-Extensions.md +++ b/docs/en/Object-Extensions.md @@ -105,3 +105,15 @@ user.SetTitle("My Title"); var title = user.GetTitle(); ```` +## Object Extension Manager + +While you can set arbitrary properties to an extensible object (which implements the `IHasExtraProperties` interface), `ObjectExtensionManager` is used to explicitly define extra properties for extensible classes. + +Explicitly defining an extra property has some use cases: + +* Allows to control how the extra property is handled on object to object mapping (see the section below). +* Allows to define metadata for the property. For example, you can map an extra property to a table field in the database while using the [EF Core](Entity-Framework-Core.md). + +### AddOrUpdate + +`AddOrUpdate` is the main method to define a new extra property or update an extra property definition. \ No newline at end of file From d316d96e3e11c9160b4da6d53d73e4587cf04b17 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 10 Apr 2020 14:50:36 +0300 Subject: [PATCH 56/61] docs: fix mistakes in content projection service --- docs/en/UI/Angular/Content-Projection-Service.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/UI/Angular/Content-Projection-Service.md b/docs/en/UI/Angular/Content-Projection-Service.md index 664123c2fb..db7c52be81 100644 --- a/docs/en/UI/Angular/Content-Projection-Service.md +++ b/docs/en/UI/Angular/Content-Projection-Service.md @@ -49,10 +49,10 @@ const strategy = PROJECTION_STRATEGY.ProjectComponentToContainer( { someProp: "SOME_VALUE" } ); -const componentRef = this.ContentProjectionService.projectContent(strategy); +const componentRef = this.contentProjectionService.projectContent(strategy); ``` -In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeComponent` component will placed inside it. Moreover, the given context will be applied, so `someProp` of the component will be set to `SOME_VALUE`. +In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeComponent` component will be placed inside it. In addition, the given context will be applied and `someProp` of the component will be set to `SOME_VALUE`. > You should keep the returned `ComponentRef` or `EmbeddedViewRef`, as they are a reference to the projected content and you will need them to destroy it when necessary. From 0072dec0b6f782e1a306c0d714dc0dc607efaa52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 10 Apr 2020 16:18:45 +0300 Subject: [PATCH 57/61] complete the object extension manager document. --- docs/en/Object-Extensions.md | 150 +++++++++++++++++- docs/en/docs-nav.json | 4 + .../ObjectExtending/ExtensibleObjectMapper.cs | 2 - 3 files changed, 153 insertions(+), 3 deletions(-) diff --git a/docs/en/Object-Extensions.md b/docs/en/Object-Extensions.md index 5841eb739c..bbcb96374c 100644 --- a/docs/en/Object-Extensions.md +++ b/docs/en/Object-Extensions.md @@ -114,6 +114,154 @@ Explicitly defining an extra property has some use cases: * Allows to control how the extra property is handled on object to object mapping (see the section below). * Allows to define metadata for the property. For example, you can map an extra property to a table field in the database while using the [EF Core](Entity-Framework-Core.md). +> `ObjectExtensionManager` implements the singleton pattern (`ObjectExtensionManager.Instance`) and you should define object extensions before your application startup. The [application startup template](Startup-Templates/Application.md) has some pre-defined static classes to safely define object extensions inside. + ### AddOrUpdate -`AddOrUpdate` is the main method to define a new extra property or update an extra property definition. \ No newline at end of file +`AddOrUpdate` is the main method to define a extra properties or update extra properties for an object. + +Example: Define extra properties for the `IdentityUser` entity: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdate(options => + { + options.AddOrUpdateProperty("SocialSecurityNumber"); + options.AddOrUpdateProperty("IsSuperUser"); + } + ); +```` + +### AddOrUpdateProperty + +While `AddOrUpdateProperty` can be used on the `options` as shown before, if you want to define a single extra property, you can use the shortcut extension method too: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty("SocialSecurityNumber"); +```` + +Sometimes it would be practical to define a single extra property to multiple types. Instead of defining one by one, you can use the following code: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + new[] + { + typeof(IdentityUserDto), + typeof(IdentityUserCreateDto), + typeof(IdentityUserUpdateDto) + }, + "SocialSecurityNumber" + ); +```` + +#### Property Configuration + +`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition. + +Example: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + "SocialSecurityNumber", + options => + { + options.CheckPairDefinitionOnMapping = false; + }); +```` + +> See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option. + +`options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document. + +## Object to Object Mapping + +Assume that you've added an extra property to an extensible entity object and used auto [object to object mapping](Object-To-Object-Mapping.md) to map this entity to an extensible DTO class. You need to be careful in such a case, because the extra property may contain a **sensitive data** that should not be available to clients. + +This section offers some **good practices** to control your extra properties on object mapping. + +### MapExtraPropertiesTo + +`MapExtraPropertiesTo` is an extension method provided by the ABP Framework to copy extra properties from an object to another in a controlled manner. Example usage: + +````csharp +identityUser.MapExtraPropertiesTo(identityUserDto); +```` + +`MapExtraPropertiesTo` **requires to define properties** (as described above) in **both sides** (`IdentityUser` and `IdentityUserDto` in this case) in order to copy the value to the target object. Otherwise, it doesn't copy the value even if it does exists in the source object (`identityUser` in this example). There are some ways to overload this restriction. + +#### MappingPropertyDefinitionChecks + +`MapExtraPropertiesTo` gets an additional parameter to control the definition check for a single mapping operation: + +````csharp +identityUser.MapExtraPropertiesTo( + identityUserDto, + MappingPropertyDefinitionChecks.None +); +```` + +> Be careful since `MappingPropertyDefinitionChecks.None` copies all extra properties without any check. `MappingPropertyDefinitionChecks` enum has other members too. + +If you want to completely disable definition check for a property, you can do it while defining the extra property (or update an existing definition) as shown below: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + "SocialSecurityNumber", + options => + { + options.CheckPairDefinitionOnMapping = false; + }); +```` + +#### Ignored Properties + +You may want to ignore some properties on a specific mapping operation: + +````csharp +identityUser.MapExtraPropertiesTo( + identityUserDto, + ignoredProperties: new[] {"MySensitiveProp"} +); +```` + +Ignored properties are not copied to the target object. + +#### AutoMapper Integration + +If you're using the [AutoMapper](https://automapper.org/) library, the ABP Framework also provides an extension method to utilize the `MapExtraPropertiesTo` method defined above. + +You can use the `MapExtraProperties()` method inside your mapping profile. + +````csharp +public class MyProfile : Profile +{ + public MyProfile() + { + CreateMap() + .MapExtraProperties(); + } +} +```` + +It has the same parameters with the `MapExtraPropertiesTo` method. + +## Entity Framework Core Database Mapping + +If you're using the EF Core, you can map an extra property to a table field in the database. Example: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + "SocialSecurityNumber", + options => + { + options.MapEfCore(b => b.HasMaxLength(32)); + } + ); +```` + +See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more. \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 471e215e3a..f45a4192bb 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -155,6 +155,10 @@ { "text": "Data Filtering", "path": "Data-Filtering.md" + }, + { + "text": "Object Extensions", + "path": "Object-Extensions.md" } ] }, diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs index f2b7aeecdb..671aff40f2 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs @@ -122,8 +122,6 @@ namespace Volo.Abp.ObjectExtending } } - //TODO: Move these methods to a class like ObjectExtensionHelper - public static bool CanMapProperty( [NotNull] string propertyName, MappingPropertyDefinitionChecks? definitionChecks = null, From 88768be435a86e3ece3e80c5b455fdd81341c04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 10 Apr 2020 16:31:40 +0300 Subject: [PATCH 58/61] Update best practices guide --- .../en/Best-Practices/Application-Services.md | 26 ++++++++++++------- .../Best-Practices/Data-Transfer-Objects.md | 1 + 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/en/Best-Practices/Application-Services.md b/docs/en/Best-Practices/Application-Services.md index 0979304931..876105c214 100644 --- a/docs/en/Best-Practices/Application-Services.md +++ b/docs/en/Best-Practices/Application-Services.md @@ -17,17 +17,18 @@ ##### Basic DTO -**Do** define a **basic** DTO for an entity. +**Do** define a **basic** DTO for an aggregate root. -- Include all the **primitive properties** directly on the entity. - - Exception: Can **exclude** properties for **security** reasons (like User.Password). +- Include all the **primitive properties** directly on the aggregate root. + - Exception: Can **exclude** properties for **security** reasons (like `User.Password`). - Include all the **sub collections** of the entity where every item in the collection is a simple **relation DTO**. +- Inherit from one of the **extensible entity DTO** classes for aggregate roots (and entities implement the `IHasExtraProperties`). Example: ```c# [Serializable] -public class IssueDto : FullAuditedEntityDto +public class IssueDto : ExtensibleFullAuditedEntityDto { public string Title { get; set; } public string Text { get; set; } @@ -57,7 +58,7 @@ Example: ````C# [Serializable] -public class IssueWithDetailsDto : FullAuditedEntityDto +public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto { public string Title { get; set; } public string Text { get; set; } @@ -66,14 +67,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto } [Serializable] -public class MilestoneDto : EntityDto +public class MilestoneDto : ExtensibleEntityDto { public string Name { get; set; } public bool IsClosed { get; set; } } [Serializable] -public class LabelDto : EntityDto +public class LabelDto : ExtensibleEntityDto { public string Name { get; set; } public string Color { get; set; } @@ -120,6 +121,7 @@ Task> GetListAsync(QuestionListQueryDto queryDto); * **Do** use the `CreateAsync` **method name**. * **Do** get a **specialized input** DTO to create the entity. +* **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed. * **Do** use **data annotations** for input validation. * Share constants between domain wherever possible (via constants defined in the **domain shared** package). * **Do** return **the detailed** DTO for new created entity. @@ -135,10 +137,11 @@ The related **DTO**: ````C# [Serializable] -public class CreateQuestionDto +public class CreateQuestionDto : ExtensibleObject { [Required] - [StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)] + [StringLength(QuestionConsts.MaxTitleLength, + MinimumLength = QuestionConsts.MinTitleLength)] public string Title { get; set; } [StringLength(QuestionConsts.MaxTextLength)] @@ -152,6 +155,7 @@ public class CreateQuestionDto - **Do** use the `UpdateAsync` **method name**. - **Do** get a **specialized input** DTO to update the entity. +- **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed. - **Do** get the Id of the entity as a separated primitive parameter. Do not include to the update DTO. - **Do** use **data annotations** for input validation. - Share constants between domain wherever possible (via constants defined in the **domain shared** package). @@ -200,6 +204,10 @@ This method votes a question and returns the current score of the question. * **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source. +#### Extra Properties + +* **Do** use either `MapExtraPropertiesTo` extension method ([see](Object-Extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services. + #### Manipulating / Deleting Entities * **Do** always get all the related entities from repositories to perform the operations on them. diff --git a/docs/en/Best-Practices/Data-Transfer-Objects.md b/docs/en/Best-Practices/Data-Transfer-Objects.md index 0c8580abb7..0fca0e86f2 100644 --- a/docs/en/Best-Practices/Data-Transfer-Objects.md +++ b/docs/en/Best-Practices/Data-Transfer-Objects.md @@ -2,6 +2,7 @@ * **Do** define DTOs in the **application contracts** package. * **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto`, `CreationAuditedEntityDto`, `AuditedEntityDto`, `FullAuditedEntityDto` and so on). + * **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto`), because aggregate roots are extensible objects and extra properties are mapped to DTOs in this way. * **Do** define DTO members with **public getter and setter**. * **Do** use **data annotations** for **validation** on the properties of DTOs those are inputs of the service. * **Do** not add any **logic** into DTOs except implementing `IValidatableObject` when necessary. From fd8cd3a001acb86af4cdcfc604ce49a24135c279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 10 Apr 2020 16:33:25 +0300 Subject: [PATCH 59/61] remove unused doc --- docs/en/AutoMapper-Integration.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 docs/en/AutoMapper-Integration.md diff --git a/docs/en/AutoMapper-Integration.md b/docs/en/AutoMapper-Integration.md deleted file mode 100644 index d197861f25..0000000000 --- a/docs/en/AutoMapper-Integration.md +++ /dev/null @@ -1,3 +0,0 @@ -## AutoMapper Integration - -TODO \ No newline at end of file From 72c679329115c0d99d43eaf5336129fe395b1001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 10 Apr 2020 16:37:46 +0300 Subject: [PATCH 60/61] Resolved #3405 Document the object extension system. --- docs/en/Object-To-Object-Mapping.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/en/Object-To-Object-Mapping.md b/docs/en/Object-To-Object-Mapping.md index 52260402f1..b7463607e9 100644 --- a/docs/en/Object-To-Object-Mapping.md +++ b/docs/en/Object-To-Object-Mapping.md @@ -145,6 +145,23 @@ options.AddProfile(validate: true); > If you have multiple profiles and need to enable validation only for a few of them, first use `AddMaps` without validation, then use `AddProfile` for each profile you want to validate. +### Mapping the Object Extensions + +[Object extension system](Object-Extensions.md) allows to define extra properties for existing classes. ABP Framework provides a mapping definition extension to properly map extra properties of two objects. + +````csharp +public class MyProfile : Profile +{ + public MyProfile() + { + CreateMap() + .MapExtraProperties(); + } +} +```` + +It is suggested to use the `MapExtraProperties()` method if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](Object-Extensions.md) for more. + ## Advanced Topics ### IObjectMapper Interface From e26e7ab48d81a91dc143afe2c7f9f836873ff430 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Fri, 10 Apr 2020 18:35:29 +0300 Subject: [PATCH 61/61] fix mac download error --- .../Building/Steps/ProjectReferenceReplaceStep.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs index 63f63bef19..c7a6cc90f0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs @@ -110,7 +110,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps var oldNodeIncludeValue = oldNode.Attributes["Include"].Value; // ReSharper disable once PossibleNullReferenceException : Can not be null because nodes are selected with include attribute filter in previous method - if (oldNodeIncludeValue.Contains(_projectName) && _entries.Any(e=>e.Name.EndsWith(Path.GetFileName(oldNodeIncludeValue)))) + if (oldNodeIncludeValue.Contains(_projectName) && _entries.Any(e=>e.Name.EndsWith(GetProjectNameWithExtensionFromProjectReference(oldNodeIncludeValue)))) { continue; } @@ -125,6 +125,16 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps return doc.OuterXml; } + private string GetProjectNameWithExtensionFromProjectReference(string oldNodeIncludeValue) + { + if (string.IsNullOrWhiteSpace(oldNodeIncludeValue)) + { + return oldNodeIncludeValue; + } + + return oldNodeIncludeValue.Split('\\', '/').Last(); + } + protected abstract XmlElement GetNewReferenceNode(XmlDocument doc, string oldNodeIncludeValue);