diff --git a/AElf.ExceptionHandler.ABP/AElf.ExceptionHandler.ABP.csproj b/AElf.ExceptionHandler.ABP/AElf.ExceptionHandler.ABP.csproj index 9c534c3..20375f2 100644 --- a/AElf.ExceptionHandler.ABP/AElf.ExceptionHandler.ABP.csproj +++ b/AElf.ExceptionHandler.ABP/AElf.ExceptionHandler.ABP.csproj @@ -16,9 +16,15 @@ AElf.ExceptionHandler.ABP + + $(DefineConstants);ABP_VERSION_8_2_0 + + - - + + + + @@ -31,4 +37,13 @@ + + + ..\..\..\..\..\..\usr\local\share\dotnet\shared\Microsoft.AspNetCore.App\8.0.6\Microsoft.AspNetCore.Mvc.Abstractions.dll + + + ..\..\..\..\..\..\usr\local\share\dotnet\shared\Microsoft.AspNetCore.App\8.0.6\Microsoft.AspNetCore.Mvc.Core.dll + + + diff --git a/AElf.ExceptionHandler.ABP/AOPExceptionModule.cs b/AElf.ExceptionHandler.ABP/AOPExceptionModule.cs index fb55094..86bd151 100644 --- a/AElf.ExceptionHandler.ABP/AOPExceptionModule.cs +++ b/AElf.ExceptionHandler.ABP/AOPExceptionModule.cs @@ -1,29 +1,45 @@ using System.Reflection; using AElf.ExceptionHandler.Extensions; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Castle; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; using Volo.Abp.Modularity; namespace AElf.ExceptionHandler.ABP; +[DependsOn( + typeof(AbpCastleCoreModule) +)] public class AOPExceptionModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) + public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.AddExceptionHandler(); - context.Services.AddTransient(); - - context.Services.OnRegistered(options => + } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + base.PostConfigureServices(context); + + var builder = context.Services.GetContainerBuilder(); + + context.Services.OnRegistered(RegisterExceptionHandlerIfNeeded); + + AutofacRegistration.Register(builder, context.Services, null); + } + + private static void RegisterExceptionHandlerIfNeeded(IOnServiceRegistredContext context) + { + if(ShouldIntercept(context.ImplementationType)) { - var methodInfos = options.ImplementationType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - // Check if any of the class methods is decorated with the ExceptionHandlerAttribute - foreach (var methodInfo in methodInfos) - { - if (methodInfo.IsDefined(typeof(ExceptionHandlerAttribute), true)) - { - var result = options.Interceptors.TryAdd(); - break; - } - } - }); + context.Interceptors.TryAdd(); + } + } + + private static bool ShouldIntercept(Type type) + { + return ExceptionHandlerHelper.IsExceptionHandlerType(type.GetTypeInfo()); + //return !DynamicProxyIgnoreTypes.Contains(type) && ExceptionHandlerHelper.IsExceptionHandlerType(type.GetTypeInfo()); } } \ No newline at end of file diff --git a/AElf.ExceptionHandler.ABP/AutofacRegistration.cs b/AElf.ExceptionHandler.ABP/AutofacRegistration.cs new file mode 100644 index 0000000..be98e1c --- /dev/null +++ b/AElf.ExceptionHandler.ABP/AutofacRegistration.cs @@ -0,0 +1,395 @@ +// This software is part of the Autofac IoC container +// Copyright © 2015 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Autofac; +using Autofac.Builder; +using Autofac.Core; +using Autofac.Core.Activators; +using Autofac.Core.Activators.Delegate; +using Autofac.Core.Activators.Reflection; +using Autofac.Core.Resolving.Pipeline; +using Autofac.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace AElf.ExceptionHandler.ABP; + +/// +/// Extension methods for registering ASP.NET Core dependencies with Autofac. +/// +public static class AutofacRegistration +{ + /// + /// Populates the Autofac container builder with the set of registered service descriptors + /// and makes and + /// available in the container. + /// + /// + /// The into which the registrations should be made. + /// + /// + /// A container builder that can be used to create an . + /// + public static void Populate( + this ContainerBuilder builder, + IServiceCollection services) + { + Populate(builder, services, null); + } + + /// + /// Populates the Autofac container builder with the set of registered service descriptors + /// and makes and + /// available in the container. Using this overload is incompatible with the ASP.NET Core + /// support for . + /// + /// + /// The into which the registrations should be made. + /// + /// + /// A container builder that can be used to create an . + /// + /// + /// If provided and not then all registrations with lifetime are registered + /// using + /// with provided + /// instead of using . + /// + /// + /// + /// Specifying a addresses a specific case where you have + /// an application that uses Autofac but where you need to isolate a set of services in a child scope. For example, + /// if you have a large application that self-hosts ASP.NET Core items, you may want to isolate the ASP.NET + /// Core registrations in a child lifetime scope so they don't show up for the rest of the application. + /// This overload allows that. Note it is the developer's responsibility to execute this and create an + /// using the child lifetime scope. + /// + /// + public static void Populate( + this ContainerBuilder builder, + IServiceCollection services, + object? lifetimeScopeTagForSingletons) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + builder.RegisterType() + .As() + .As() + .As() + .As() + .ExternallyOwned(); + + var autofacServiceScopeFactory = typeof(AutofacServiceProvider).Assembly.GetType("Autofac.Extensions.DependencyInjection.AutofacServiceScopeFactory"); + if (autofacServiceScopeFactory == null) + { + throw new AbpException("Unable get type of Autofac.Extensions.DependencyInjection.AutofacServiceScopeFactory!"); + } + + // Issue #83: IServiceScopeFactory must be a singleton and scopes must be flat, not hierarchical. + builder + .RegisterType(autofacServiceScopeFactory) + .As() + .SingleInstance(); + + // Shims for keyed service compatibility. + builder.RegisterSource(); + builder.ComponentRegistryBuilder.Registered += AddFromKeyedServiceParameterMiddleware; + + Register(builder, services, lifetimeScopeTagForSingletons); + } + + /// + /// Inspect each component registration, and determine whether or not we can avoid adding the + /// parameter to the resolve pipeline. + /// + private static void AddFromKeyedServiceParameterMiddleware(object? sender, ComponentRegisteredEventArgs e) + { + var needFromKeyedServiceParameter = false; + + // We can optimise quite significantly in the case where we are using the reflection activator. + // In that state we can inspect the constructors ahead of time and determine whether the parameter will even need to be added. + if (e.ComponentRegistration.Activator is ReflectionActivator reflectionActivator) + { + var constructors = reflectionActivator.ConstructorFinder.FindConstructors(reflectionActivator.LimitType); + + // Go through all the constructors; if any have a FromKeyedServices, then we must add our component middleware to + // the pipeline. + foreach (var constructor in constructors) + { + foreach (var constructorParameter in constructor.GetParameters()) + { + if (constructorParameter.GetCustomAttribute() is not null) + { + // One or more of the constructors we will use to activate has a FromKeyedServicesAttribute, + // we must add our middleware. + needFromKeyedServiceParameter = true; + break; + } + } + + if (needFromKeyedServiceParameter) + { + break; + } + } + } + else if (e.ComponentRegistration.Activator is DelegateActivator) + { + // For delegate activation there are very few paths that would let the FromKeyedServicesAttribute + // actually work, and none that MSDI supports directly. + // We're explicitly choosing here not to support [FromKeyedServices] on the Autofac-specific generic + // delegate resolve methods, to improve performance for the 99% case of other delegates that only + // receive an IComponentContext or an IServiceProvider. + needFromKeyedServiceParameter = false; + } + else if (e.ComponentRegistration.Activator is InstanceActivator) + { + // Instance activators don't use parameters. + needFromKeyedServiceParameter = false; + } + else + { + // Unknown activator, assume we need the parameter. + needFromKeyedServiceParameter = true; + } + + e.ComponentRegistration.PipelineBuilding += (_, pipeline) => + { + var keyedServiceMiddlewareType = typeof(AutofacServiceProvider).Assembly.GetType("Autofac.Extensions.DependencyInjection.KeyedServiceMiddleware"); + var instanceWithFromKeyedServicesParameter = (IResolveMiddleware)keyedServiceMiddlewareType!.GetProperty("InstanceWithFromKeyedServicesParameter", BindingFlags.Public | BindingFlags.Static)!.GetValue(null, null)!; + var instanceWithoutFromKeyedServicesParameter = (IResolveMiddleware)keyedServiceMiddlewareType!.GetProperty("InstanceWithoutFromKeyedServicesParameter", BindingFlags.Public | BindingFlags.Static)!.GetValue(null, null)!; + + if (needFromKeyedServiceParameter) + { + pipeline.Use(instanceWithFromKeyedServicesParameter, MiddlewareInsertionMode.StartOfPhase); + } + else + { + pipeline.Use(instanceWithoutFromKeyedServicesParameter, MiddlewareInsertionMode.StartOfPhase); + } + }; + } + + /// + /// Configures the exposed service type on a service registration. + /// + /// The activator data type. + /// The object registration style. + /// The registration being built. + /// The service descriptor with service type and key information. + /// + /// The , configured with the proper service type, + /// and available for additional configuration. + /// + private static IRegistrationBuilder ConfigureServiceType( + this IRegistrationBuilder registrationBuilder, + ServiceDescriptor descriptor) + { + if (descriptor.IsKeyedService) + { + var key = descriptor.ServiceKey!; + + // If it's keyed, the service key won't be null. A null key results in it _not_ being a keyed service. + registrationBuilder.Keyed(key, descriptor.ServiceType); + } + else + { + registrationBuilder.As(descriptor.ServiceType); + } + + return registrationBuilder; + } + + /// + /// Configures the lifecycle on a service registration. + /// + /// The activator data type. + /// The object registration style. + /// The registration being built. + /// The lifecycle specified on the service registration. + /// + /// If not then all registrations with lifetime are registered + /// using + /// with provided + /// instead of using . + /// + /// + /// The , configured with the proper lifetime scope, + /// and available for additional configuration. + /// + private static IRegistrationBuilder ConfigureLifecycle( + this IRegistrationBuilder registrationBuilder, + ServiceLifetime lifecycleKind, + object? lifetimeScopeTagForSingleton) + { + switch (lifecycleKind) + { + case ServiceLifetime.Singleton: + if (lifetimeScopeTagForSingleton == null) + { + registrationBuilder.SingleInstance(); + } + else + { + registrationBuilder.InstancePerMatchingLifetimeScope(lifetimeScopeTagForSingleton); + } + + break; + case ServiceLifetime.Scoped: + registrationBuilder.InstancePerLifetimeScope(); + break; + case ServiceLifetime.Transient: + registrationBuilder.InstancePerDependency(); + break; + } + + return registrationBuilder; + } + + /// + /// Populates the Autofac container builder with the set of registered service descriptors. + /// + /// + /// The into which the registrations should be made. + /// + /// + /// A container builder that can be used to create an . + /// + /// + /// If not then all registrations with lifetime are registered + /// using + /// with provided + /// instead of using . + /// + [SuppressMessage("CA2000", "CA2000", Justification = "Registrations created here are disposed when the built container is disposed.")] + public static void Register( + ContainerBuilder builder, + IServiceCollection services, + object? lifetimeScopeTagForSingletons) + { + var moduleContainer = services.GetSingletonInstance(); + var registrationActionList = services.GetRegistrationActionList(); + var activatedActionList = services.GetServiceActivatedActionList(); + + foreach (var descriptor in services) + { + var implementationType = descriptor.NormalizedImplementationType(); + + if(implementationType == null) + { + continue; + } + + if (implementationType != null) + { + var methods = implementationType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + // if none of the methods have the ExceptionHandler attribute, continue the loop + if (!methods.Any(m => m.GetCustomAttributes(typeof(ExceptionHandlerAttribute), true).Any())) + { + continue; + } + } + + if (implementationType != null) + { + // Test if the an open generic type is being registered + var serviceTypeInfo = descriptor.ServiceType.GetTypeInfo(); + if (serviceTypeInfo.IsGenericTypeDefinition) + { + builder + .RegisterGeneric(implementationType) + .ConfigureServiceType(descriptor) + .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) + .ConfigureExceptionHandlerAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList); + } + else + { + builder + .RegisterType(implementationType) + .ConfigureServiceType(descriptor) + .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) + .ConfigureExceptionHandlerAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList); + } + + continue; + } + + if (descriptor.IsKeyedService && descriptor.KeyedImplementationFactory != null) + { + var registration = RegistrationBuilder.ForDelegate(descriptor.ServiceType, (context, parameters) => + { + // At this point the context is always a ResolveRequestContext, which will expose the actual service type. + var requestContext = (ResolveRequestContext)context; + + var serviceProvider = context.Resolve(); + + var keyedService = (Autofac.Core.KeyedService)requestContext.Service; + + var key = keyedService.ServiceKey; + + return descriptor.KeyedImplementationFactory(serviceProvider, key); + }) + .ConfigureServiceType(descriptor) + .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) + .CreateRegistration(); + //TODO: ConfigureAbpConventions ? + + builder.RegisterComponent(registration); + + continue; + } + + if (!descriptor.IsKeyedService && descriptor.ImplementationFactory != null) + { + var registration = RegistrationBuilder.ForDelegate(descriptor.ServiceType, (context, parameters) => + { + var serviceProvider = context.Resolve(); + return descriptor.ImplementationFactory(serviceProvider); + }) + .ConfigureServiceType(descriptor) + .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) + .CreateRegistration(); + //TODO: ConfigureAbpConventions ? + + builder.RegisterComponent(registration); + + continue; + } + + // It's not a type or factory, so it must be an instance. + builder + .RegisterInstance(descriptor.NormalizedImplementationInstance()!) + .ConfigureServiceType(descriptor) + .ConfigureLifecycle(descriptor.Lifetime, null); + } + } +} diff --git a/AElf.ExceptionHandler.ABP/ExceptionHandlerAbpRegistrationBuilderExtensions.cs b/AElf.ExceptionHandler.ABP/ExceptionHandlerAbpRegistrationBuilderExtensions.cs new file mode 100644 index 0000000..9b24298 --- /dev/null +++ b/AElf.ExceptionHandler.ABP/ExceptionHandlerAbpRegistrationBuilderExtensions.cs @@ -0,0 +1,144 @@ +using System.Reflection; +using Autofac.Builder; +using Autofac.Core; +using Autofac.Extras.DynamicProxy; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Autofac; +using Volo.Abp.Castle.DynamicProxy; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Modularity; + +namespace AElf.ExceptionHandler.ABP; + +public static class ExceptionHandlerAbpRegistrationBuilderExtensions +{ + public static IRegistrationBuilder ConfigureExceptionHandlerAbpConventions( + this IRegistrationBuilder registrationBuilder, + ServiceDescriptor serviceDescriptor, + IModuleContainer moduleContainer, + ServiceRegistrationActionList registrationActionList, + ServiceActivatedActionList activatedActionList) + where TActivatorData : ReflectionActivatorData + { + registrationBuilder = registrationBuilder.InvokeActivatedActions(activatedActionList, serviceDescriptor); + + var serviceType = registrationBuilder.RegistrationData.Services.OfType().FirstOrDefault()?.ServiceType; + if (serviceType == null) + { + return registrationBuilder; + } + + var implementationType = registrationBuilder.ActivatorData.ImplementationType; + if (implementationType == null) + { + return registrationBuilder; + } + + registrationBuilder = registrationBuilder.EnablePropertyInjection(moduleContainer, implementationType); + registrationBuilder = registrationBuilder.InvokeRegistrationActions(registrationActionList, serviceType, implementationType); + + return registrationBuilder; + } + + private static IRegistrationBuilder InvokeActivatedActions( + this IRegistrationBuilder registrationBuilder, + ServiceActivatedActionList activatedActionList, + ServiceDescriptor serviceDescriptor) + where TActivatorData : ReflectionActivatorData + { + var actions = activatedActionList.GetActions(serviceDescriptor); + if (actions.Any()) + { + registrationBuilder.OnActivated(context => + {; + var serviceActivatedContext = new OnServiceActivatedContext(context.Instance!); + foreach (var action in actions) + { + action.Invoke(serviceActivatedContext); + } + }); + } + + return registrationBuilder; + } + + private static IRegistrationBuilder InvokeRegistrationActions( + this IRegistrationBuilder registrationBuilder, + ServiceRegistrationActionList registrationActionList, + Type serviceType, + Type implementationType) + where TActivatorData : ReflectionActivatorData + { + var serviceRegistredArgs = new OnServiceRegistredContext(serviceType, implementationType); + + foreach (var registrationAction in registrationActionList) + { + registrationAction.Invoke(serviceRegistredArgs); + } + + if (serviceRegistredArgs.Interceptors.Any()) + { +#if ABP_VERSION_8_2_0 + var disableAbpFeaturesAttribute = serviceRegistredArgs.ImplementationType.GetCustomAttribute(true); + if (disableAbpFeaturesAttribute == null || !disableAbpFeaturesAttribute.DisableInterceptors) + { + registrationBuilder = registrationBuilder.AddInterceptors( + registrationActionList, + serviceType, + serviceRegistredArgs.Interceptors + ); + } +#else + registrationBuilder = registrationBuilder.AddInterceptors( + registrationActionList, + serviceType, + serviceRegistredArgs.Interceptors + ); +#endif + } + + return registrationBuilder; + } + + private static IRegistrationBuilder EnablePropertyInjection( + this IRegistrationBuilder registrationBuilder, + IModuleContainer moduleContainer, + Type implementationType) + where TActivatorData : ReflectionActivatorData + { + // Enable Property Injection only for types in an assembly containing an AbpModule and without a DisablePropertyInjection attribute on class or properties. + if (moduleContainer.Modules.Any(m => m.AllAssemblies.Contains(implementationType.Assembly)) && + implementationType.GetCustomAttributes(typeof(DisablePropertyInjectionAttribute), true).IsNullOrEmpty()) + { + registrationBuilder = registrationBuilder.PropertiesAutowired(new AbpPropertySelector(false)); + } + + return registrationBuilder; + } + + private static IRegistrationBuilder + AddInterceptors( + this IRegistrationBuilder registrationBuilder, + ServiceRegistrationActionList serviceRegistrationActionList, + Type serviceType, + IEnumerable interceptors) + where TActivatorData : ReflectionActivatorData + { + if (serviceRegistrationActionList.IsClassInterceptorsDisabled) + { + return registrationBuilder; + } + + (registrationBuilder as IRegistrationBuilder)?.EnableClassInterceptors(); + + foreach (var interceptor in interceptors) + { + registrationBuilder.InterceptedBy( + typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(interceptor) + ); + } + + return registrationBuilder; + } +} diff --git a/AElf.ExceptionHandler.ABP/ExceptionHandlerHelper.cs b/AElf.ExceptionHandler.ABP/ExceptionHandlerHelper.cs new file mode 100644 index 0000000..a4ed509 --- /dev/null +++ b/AElf.ExceptionHandler.ABP/ExceptionHandlerHelper.cs @@ -0,0 +1,22 @@ +using System.Reflection; + +namespace AElf.ExceptionHandler.ABP; + +public static class ExceptionHandlerHelper +{ + public static bool IsExceptionHandlerType(TypeInfo implementationType) + { + return HasExceptionHandlerAttribute(implementationType) || AnyMethodHasExceptionHandlerAttribute(implementationType); + } + + private static bool HasExceptionHandlerAttribute(MemberInfo implementationType) + { + return implementationType.IsDefined(typeof(ExceptionHandlerAttribute), true); + } + + private static bool AnyMethodHasExceptionHandlerAttribute(TypeInfo implementationType) + { + return implementationType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) + .Any(HasExceptionHandlerAttribute); + } +} \ No newline at end of file diff --git a/AElf.ExceptionHandler.ABP/ExceptionHandlerInterceptor.cs b/AElf.ExceptionHandler.ABP/ExceptionHandlerInterceptor.cs index b67c9c9..3fe7a01 100644 --- a/AElf.ExceptionHandler.ABP/ExceptionHandlerInterceptor.cs +++ b/AElf.ExceptionHandler.ABP/ExceptionHandlerInterceptor.cs @@ -1,8 +1,9 @@ +using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; namespace AElf.ExceptionHandler.ABP; -public class ExceptionHandlerInterceptor : AbpInterceptor +public class ExceptionHandlerInterceptor : AbpInterceptor, ITransientDependency { private readonly IInterceptor _interceptor; @@ -19,10 +20,7 @@ public override async Task InterceptAsync(IAbpMethodInvocation invocation) MethodInfo = invocation.Method, Arguments = invocation.Arguments, ReturnValue = null, - Invocation = async () => - { - await invocation.ProceedAsync(); - } + Invocation = invocation.ProceedAsync }; await _interceptor.InterceptAsync(methodExecutionArgs);