From f2d3b712247111a6c869fb85457dca734b7b2985 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 25 Mar 2020 18:08:13 +0800 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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();