From f731a36c923fe1cad14e64aa130ee3167de2167d Mon Sep 17 00:00:00 2001 From: Fei Xu Date: Tue, 2 Apr 2024 20:26:27 -0400 Subject: [PATCH 1/5] refactor RegistrationExtensions.cs Created a new ProxyHelpers class to improve code structure and readability. This class centralizes proxy operations that were previously distributed across multiple classes. Refactored RegistrationExtensions.cs to replace direct method calls with references to the newly created ProxyHelpers. --- .../ProxyHelpers.cs | 145 ++++++++++++++++++ .../RegistrationExtensions.cs | 97 +----------- 2 files changed, 151 insertions(+), 91 deletions(-) create mode 100644 src/Autofac.Extras.DynamicProxy/ProxyHelpers.cs diff --git a/src/Autofac.Extras.DynamicProxy/ProxyHelpers.cs b/src/Autofac.Extras.DynamicProxy/ProxyHelpers.cs new file mode 100644 index 0000000..7ae0969 --- /dev/null +++ b/src/Autofac.Extras.DynamicProxy/ProxyHelpers.cs @@ -0,0 +1,145 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Globalization; +using System.Reflection; +using Autofac.Core; +using Autofac.Core.Resolving.Pipeline; +using Castle.DynamicProxy; + +namespace Autofac.Extras.DynamicProxy; + +/// +/// A helper class for proxy operations in an Autofac Interceptors context. +/// +public static class ProxyHelpers +{ + /// + /// The property name for the interceptors in Autofac registration extensions. + /// + internal const string InterceptorsPropertyName = "Autofac.Extras.DynamicProxy.RegistrationExtensions.InterceptorsPropertyName"; + + /// + /// The property name for the attribute interceptors in Autofac registration extensions. + /// + internal const string AttributeInterceptorsPropertyName = "Autofac.Extras.DynamicProxy.RegistrationExtensions.AttributeInterceptorsPropertyName"; + + /// + /// An empty set of services for when no interceptors are registered. + /// + private static readonly IEnumerable EmptyServices = Enumerable.Empty(); + + /// + /// The global proxy generator for Castle's DynamicProxy library. + /// This is used to create new proxy instances when interceptors are applied. + /// + internal static readonly ProxyGenerator ProxyGenerator = new(); + + /// + /// Applies the proxy to the given ResolveRequestContext. + /// + /// The ResolveRequestContext to which the proxy should be applied. + /// Optional configurations for the proxy generation. If left null, default configurations will be used. + /// Thrown when the provided context is null. + /// Thrown if interface proxying is attempted on a type that is not interface or is not accessible. + public static void ApplyProxy(ResolveRequestContext ctx, ProxyGenerationOptions? options = null) + { + if (ctx is null) + { + throw new ArgumentNullException(nameof(ctx)); + } + + EnsureInterfaceInterceptionApplies(ctx.Registration); + + // The instance won't ever _practically_ be null by the time it gets here. + var proxiedInterfaces = ctx.Instance! + .GetType() + .GetInterfaces() + .Where(ProxyUtil.IsAccessible) + .ToArray(); + + if (!proxiedInterfaces.Any()) + { + return; + } + + var theInterface = proxiedInterfaces.First(); + var interfaces = proxiedInterfaces.Skip(1).ToArray(); + + var interceptors = GetInterceptorServices(ctx.Registration, ctx.Instance.GetType()) + .Select(s => ctx.ResolveService(s)) + .Cast() + .ToArray(); + + ctx.Instance = options == null + ? ProxyGenerator.CreateInterfaceProxyWithTarget(theInterface, interfaces, ctx.Instance, interceptors) + : ProxyGenerator.CreateInterfaceProxyWithTarget(theInterface, interfaces, ctx.Instance, options, interceptors); + } + + /// + /// Checks if the component registration can be used with interface proxying. + /// + /// The component registration to check. + /// Thrown if interface proxying is attemped on a type that is not an interface or is not accessible. + internal static void EnsureInterfaceInterceptionApplies(IComponentRegistration componentRegistration) + { + if (componentRegistration.Services + .OfType() + .Select(s => new Tuple(s.ServiceType, s.ServiceType.GetTypeInfo())) + .Any(s => !s.Item2.IsInterface || !ProxyUtil.IsAccessible(s.Item1))) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.CurrentCulture, + RegistrationExtensionsResources.InterfaceProxyingOnlySupportsInterfaceServices, + componentRegistration)); + } + } + + /// + /// Retrieves the interceptor services from a component registration and implementation type. + /// + /// The component registration where the interceptor services are stored. + /// The implementation type which potentially has interceptor attributes. + /// A sequence of services that represent the interceptors. + internal static IEnumerable GetInterceptorServices(IComponentRegistration registration, Type implType) + { + var result = EmptyServices; + + if (registration.Metadata.TryGetValue(InterceptorsPropertyName, out object? services) && services is IEnumerable existingPropertyServices) + { + result = result.Concat(existingPropertyServices); + } + + return (registration.Metadata.TryGetValue(AttributeInterceptorsPropertyName, out services) && services is IEnumerable existingAttributeServices) + ? result.Concat(existingAttributeServices) + : result.Concat(GetInterceptorServicesFromAttributes(implType)); + } + + /// + /// Retrieves the interceptor services from attributes on a class and its implemented interfaces. + /// + /// The implementation type which potentially has interceptor attributes. + /// A sequence of services that represent the interceptors. + internal static IEnumerable GetInterceptorServicesFromAttributes(Type implType) + { + var implTypeInfo = implType.GetTypeInfo(); + if (!implTypeInfo.IsClass) + { + return Enumerable.Empty(); + } + + var classAttributeServices = implTypeInfo + .GetCustomAttributes(typeof(InterceptAttribute), true) + .Cast() + .Select(att => att.InterceptorService); + + var interfaceAttributeServices = implType + .GetInterfaces() + .SelectMany(i => i.GetTypeInfo().GetCustomAttributes(typeof(InterceptAttribute), true)) + .Cast() + .Select(att => att.InterceptorService); + + return classAttributeServices.Concat(interfaceAttributeServices); + } +} diff --git a/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs b/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs index 6d3b9f9..71540c2 100644 --- a/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs +++ b/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using System.Globalization; -using System.Reflection; using Autofac.Builder; using Autofac.Core; using Autofac.Core.Resolving.Pipeline; @@ -16,14 +14,6 @@ namespace Autofac.Extras.DynamicProxy; /// public static class RegistrationExtensions { - private const string InterceptorsPropertyName = "Autofac.Extras.DynamicProxy.RegistrationExtensions.InterceptorsPropertyName"; - - private const string AttributeInterceptorsPropertyName = "Autofac.Extras.DynamicProxy.RegistrationExtensions.AttributeInterceptorsPropertyName"; - - private static readonly IEnumerable EmptyServices = Enumerable.Empty(); - - private static readonly ProxyGenerator ProxyGenerator = new(); - /// /// Enable class interception on the target type. Interceptors will be determined /// via Intercept attributes on the class or added with InterceptedBy(). @@ -105,13 +95,13 @@ public static IRegistrationBuilder { @@ -126,7 +116,7 @@ public static IRegistrationBuilder e.Context.ResolveService(s)) .Cast() .ToArray())); @@ -164,31 +154,7 @@ public static IRegistrationBuilder ctx.ResolveService(s)) - .Cast() - .ToArray(); - - ctx.Instance = options == null - ? ProxyGenerator.CreateInterfaceProxyWithTarget(theInterface, interfaces, ctx.Instance, interceptors) - : ProxyGenerator.CreateInterfaceProxyWithTarget(theInterface, interfaces, ctx.Instance, options, interceptors); + ProxyHelpers.ApplyProxy(ctx, options); })); return registration; @@ -218,7 +184,7 @@ public static IRegistrationBuilder InterceptedBy throw new ArgumentNullException(nameof(interceptorServices)); } - AddInterceptorServicesToMetadata(builder, interceptorServices, InterceptorsPropertyName); + AddInterceptorServicesToMetadata(builder, interceptorServices, ProxyHelpers.InterceptorsPropertyName); return builder; } @@ -267,21 +233,6 @@ public static IRegistrationBuilder InterceptedBy return InterceptedBy(builder, interceptorServiceTypes.Select(t => new TypedService(t)).ToArray()); } - private static void EnsureInterfaceInterceptionApplies(IComponentRegistration componentRegistration) - { - if (componentRegistration.Services - .OfType() - .Select(s => new Tuple(s.ServiceType, s.ServiceType.GetTypeInfo())) - .Any(s => !s.Item2.IsInterface || !ProxyUtil.IsAccessible(s.Item1))) - { - throw new InvalidOperationException( - string.Format( - CultureInfo.CurrentCulture, - RegistrationExtensionsResources.InterfaceProxyingOnlySupportsInterfaceServices, - componentRegistration)); - } - } - private static void AddInterceptorServicesToMetadata( IRegistrationBuilder builder, IEnumerable interceptorServices, @@ -297,40 +248,4 @@ private static void AddInterceptorServicesToMetadata GetInterceptorServices(IComponentRegistration registration, Type implType) - { - var result = EmptyServices; - - if (registration.Metadata.TryGetValue(InterceptorsPropertyName, out object? services) && services is IEnumerable existingPropertyServices) - { - result = result.Concat(existingPropertyServices); - } - - return (registration.Metadata.TryGetValue(AttributeInterceptorsPropertyName, out services) && services is IEnumerable existingAttributeServices) - ? result.Concat(existingAttributeServices) - : result.Concat(GetInterceptorServicesFromAttributes(implType)); - } - - private static IEnumerable GetInterceptorServicesFromAttributes(Type implType) - { - var implTypeInfo = implType.GetTypeInfo(); - if (!implTypeInfo.IsClass) - { - return Enumerable.Empty(); - } - - var classAttributeServices = implTypeInfo - .GetCustomAttributes(typeof(InterceptAttribute), true) - .Cast() - .Select(att => att.InterceptorService); - - var interfaceAttributeServices = implType - .GetInterfaces() - .SelectMany(i => i.GetTypeInfo().GetCustomAttributes(typeof(InterceptAttribute), true)) - .Cast() - .Select(att => att.InterceptorService); - - return classAttributeServices.Concat(interfaceAttributeServices); - } } From b183fbd9cb38c1165c4b05da43a5f615d1475143 Mon Sep 17 00:00:00 2001 From: Fei Xu Date: Tue, 2 Apr 2024 20:55:27 -0400 Subject: [PATCH 2/5] Add service middleware and associated tests for DynamicProxy DelegateMiddleware class is copied from autofac, after autofac add the overload method, remove this. Additionally, ServiceMiddlewareRegistrationExtensions has been added to provide static methods for registering middleware services. Tests have been provided to ensure correct interception of public interfaces. The Moq testing library has been added as a package reference. --- .../Autofac.Extras.DynamicProxy.csproj | 2 +- ...ServiceMiddlewareRegistrationExtensions.cs | 43 ++++++++ .../Autofac.Extras.DynamicProxy.Test.csproj | 1 + ...eMiddlewareInterfaceInterceptorsFixture.cs | 97 +++++++++++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/Autofac.Extras.DynamicProxy/ServiceMiddlewareRegistrationExtensions.cs create mode 100644 test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs diff --git a/src/Autofac.Extras.DynamicProxy/Autofac.Extras.DynamicProxy.csproj b/src/Autofac.Extras.DynamicProxy/Autofac.Extras.DynamicProxy.csproj index 1330b98..07b6acb 100644 --- a/src/Autofac.Extras.DynamicProxy/Autofac.Extras.DynamicProxy.csproj +++ b/src/Autofac.Extras.DynamicProxy/Autofac.Extras.DynamicProxy.csproj @@ -62,7 +62,7 @@ - + all diff --git a/src/Autofac.Extras.DynamicProxy/ServiceMiddlewareRegistrationExtensions.cs b/src/Autofac.Extras.DynamicProxy/ServiceMiddlewareRegistrationExtensions.cs new file mode 100644 index 0000000..3298b05 --- /dev/null +++ b/src/Autofac.Extras.DynamicProxy/ServiceMiddlewareRegistrationExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Autofac.Core.Resolving.Pipeline; +using Castle.DynamicProxy; + +namespace Autofac.Extras.DynamicProxy; + +/// +/// Provides a set of static methods for registering middleware services. +/// +public static class ServiceMiddlewareRegistrationExtensions +{ + /// + /// Represents an extension method on the for enabling interface interceptors. + /// + /// The type of the service to enable interceptors for. + /// The container builder. + /// The proxy generation options. + public static void EnableInterfaceInterceptors(this ContainerBuilder builder, ProxyGenerationOptions? options = null) + { + builder.RegisterServiceMiddleware(PipelinePhase.ScopeSelection, (context, next) => + { + next(context); + ProxyHelpers.ApplyProxy(context, options); + }); + } + + /// + /// Represents an extension method on the for enabling interface interceptors. + /// + /// The container builder. + /// The type of the service to enable interceptors for. + /// The proxy generation options. + public static void EnableInterfaceInterceptors(this ContainerBuilder builder, Type serviceType, ProxyGenerationOptions? options = null) + { + builder.RegisterServiceMiddleware(serviceType, PipelinePhase.ScopeSelection, (context, next) => + { + next(context); + ProxyHelpers.ApplyProxy(context, options); + }); + } +} diff --git a/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj b/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj index d510886..9b5107c 100644 --- a/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj +++ b/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj @@ -44,6 +44,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs b/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs new file mode 100644 index 0000000..99543af --- /dev/null +++ b/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs @@ -0,0 +1,97 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Castle.DynamicProxy; +using Moq; +using IInvocation = Castle.DynamicProxy.IInvocation; + +namespace Autofac.Extras.DynamicProxy.Test; + +public class ServiceMiddlewareInterfaceInterceptorsFixture +{ + public interface IPublicInterface + { + string PublicMethod(); + } + + public interface IDecoratorInterface + { + void Decorate(); + } + + [Fact] + public void InterceptsPublicInterfacesUseGenericMethod() + { + Mock mockDecorator = new(); + ContainerBuilder builder = new(); + builder.RegisterType(); + builder.RegisterDecorator(); + builder.RegisterInstance(mockDecorator.Object); + builder + .RegisterType() + .InterceptedBy(typeof(StringMethodInterceptor)) + .As(); + builder.EnableInterfaceInterceptors(); + IContainer container = builder.Build(); + IPublicInterface obj = container.Resolve(); + Assert.Equal("intercepted-PublicMethod", obj.PublicMethod()); + mockDecorator.Verify(e => e.Decorate(), Times.Never); + } + + [Fact] + public void InterceptsPublicInterfacesUseNoneGenericMethod() + { + Mock mockDecorator = new(); + ContainerBuilder builder = new(); + builder.RegisterType(); + builder.RegisterDecorator(); + builder.RegisterInstance(mockDecorator.Object); + builder + .RegisterType() + .InterceptedBy(typeof(StringMethodInterceptor)) + .As(); + builder.EnableInterfaceInterceptors(typeof(IPublicInterface)); + IContainer container = builder.Build(); + IPublicInterface obj = container.Resolve(); + Assert.Equal("intercepted-PublicMethod", obj.PublicMethod()); + mockDecorator.Verify(e => e.Decorate(), Times.Never); + } + + public class Decorator : IPublicInterface + { + private readonly IPublicInterface _decoratedService; + private readonly IDecoratorInterface _decoratorInterface; + + public Decorator(IPublicInterface decoratedService, IDecoratorInterface decoratorInterface) + { + _decoratedService = decoratedService; + _decoratorInterface = decoratorInterface; + } + + public string PublicMethod() + { + _decoratorInterface.Decorate(); + return _decoratedService.PublicMethod(); + } + } + + public class Interceptable : IPublicInterface + { + public string PublicMethod() => throw new NotImplementedException(); + } + + private class StringMethodInterceptor : IInterceptor + { + public void Intercept(IInvocation invocation) + { + if (invocation.Method.ReturnType == typeof(string)) + { + invocation.ReturnValue = "intercepted-" + invocation.Method.Name; + } + else + { + invocation.Proceed(); + } + } + } +} From 0d48a7c5c8c428234b5c19f32f9ce44aebb5e7f5 Mon Sep 17 00:00:00 2001 From: Fei Xu Date: Fri, 20 Dec 2024 20:26:04 -0500 Subject: [PATCH 3/5] Switch from Moq to NSubstitute in unit tests. Replaced Moq with NSubstitute in the test project to handle mocking. Change RegisterType style in test. --- .../Autofac.Extras.DynamicProxy.Test.csproj | 2 +- ...eMiddlewareInterfaceInterceptorsFixture.cs | 29 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj b/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj index 9b5107c..db7cc4a 100644 --- a/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj +++ b/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj @@ -44,7 +44,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all diff --git a/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs b/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs index 99543af..18d98fc 100644 --- a/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs +++ b/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Castle.DynamicProxy; -using Moq; +using NSubstitute; using IInvocation = Castle.DynamicProxy.IInvocation; namespace Autofac.Extras.DynamicProxy.Test; @@ -20,41 +20,38 @@ public interface IDecoratorInterface } [Fact] - public void InterceptsPublicInterfacesUseGenericMethod() + public void InterceptsPublicInterfacesUseGenericMethod1() { - Mock mockDecorator = new(); + IDecoratorInterface mockDecorator = Substitute.For(); ContainerBuilder builder = new(); + builder.RegisterType(); builder.RegisterDecorator(); - builder.RegisterInstance(mockDecorator.Object); - builder - .RegisterType() - .InterceptedBy(typeof(StringMethodInterceptor)) - .As(); + builder.RegisterInstance(mockDecorator); + builder.RegisterType().InterceptedBy(typeof(StringMethodInterceptor)).As(); builder.EnableInterfaceInterceptors(); + IContainer container = builder.Build(); IPublicInterface obj = container.Resolve(); + Assert.Equal("intercepted-PublicMethod", obj.PublicMethod()); - mockDecorator.Verify(e => e.Decorate(), Times.Never); + mockDecorator.DidNotReceive().Decorate(); } [Fact] public void InterceptsPublicInterfacesUseNoneGenericMethod() { - Mock mockDecorator = new(); + IDecoratorInterface mockDecorator = Substitute.For(); ContainerBuilder builder = new(); builder.RegisterType(); builder.RegisterDecorator(); - builder.RegisterInstance(mockDecorator.Object); - builder - .RegisterType() - .InterceptedBy(typeof(StringMethodInterceptor)) - .As(); + builder.RegisterInstance(mockDecorator); + builder.RegisterType().InterceptedBy(typeof(StringMethodInterceptor)).As(); builder.EnableInterfaceInterceptors(typeof(IPublicInterface)); IContainer container = builder.Build(); IPublicInterface obj = container.Resolve(); Assert.Equal("intercepted-PublicMethod", obj.PublicMethod()); - mockDecorator.Verify(e => e.Decorate(), Times.Never); + mockDecorator.DidNotReceive().Decorate(); } public class Decorator : IPublicInterface From 5fca3c7397173b3f212469a22a2b4c15bda30fcc Mon Sep 17 00:00:00 2001 From: Fei Xu Date: Tue, 24 Dec 2024 16:30:54 -0500 Subject: [PATCH 4/5] Refactor tests to remove NSubstitute and improve assertions Removed the use of NSubstitute from the test project and simplified the testing structure. Adjusted assertions to account for the modified interceptor and decorator behavior, ensuring accurate validation of method outputs. Fix ServiceMiddlewareInterfaceInterceptorsFixture test cases --- .../Autofac.Extras.DynamicProxy.Test.csproj | 1 - .../ProxyHelperFixture.cs | 13 ++++++ ...eMiddlewareInterfaceInterceptorsFixture.cs | 46 ++++--------------- 3 files changed, 21 insertions(+), 39 deletions(-) create mode 100644 test/Autofac.Extras.DynamicProxy.Test/ProxyHelperFixture.cs diff --git a/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj b/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj index db7cc4a..d510886 100644 --- a/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj +++ b/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj @@ -44,7 +44,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - all diff --git a/test/Autofac.Extras.DynamicProxy.Test/ProxyHelperFixture.cs b/test/Autofac.Extras.DynamicProxy.Test/ProxyHelperFixture.cs new file mode 100644 index 0000000..11d8396 --- /dev/null +++ b/test/Autofac.Extras.DynamicProxy.Test/ProxyHelperFixture.cs @@ -0,0 +1,13 @@ +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Extras.DynamicProxy.Test; + +public class ProxyHelperFixture +{ + [Fact] + public void ApplyProxy_NullResolveRequestContext() + { + const ResolveRequestContext ctx = null; + Assert.Throws(() => ProxyHelpers.ApplyProxy(ctx!)); + } +} diff --git a/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs b/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs index 18d98fc..d2f54c1 100644 --- a/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs +++ b/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Castle.DynamicProxy; -using NSubstitute; using IInvocation = Castle.DynamicProxy.IInvocation; namespace Autofac.Extras.DynamicProxy.Test; @@ -14,81 +13,52 @@ public interface IPublicInterface string PublicMethod(); } - public interface IDecoratorInterface - { - void Decorate(); - } - [Fact] - public void InterceptsPublicInterfacesUseGenericMethod1() + public void InterceptsPublicInterfacesUseGenericMethod() { - IDecoratorInterface mockDecorator = Substitute.For(); ContainerBuilder builder = new(); - builder.RegisterType(); builder.RegisterDecorator(); - builder.RegisterInstance(mockDecorator); builder.RegisterType().InterceptedBy(typeof(StringMethodInterceptor)).As(); builder.EnableInterfaceInterceptors(); - IContainer container = builder.Build(); IPublicInterface obj = container.Resolve(); - - Assert.Equal("intercepted-PublicMethod", obj.PublicMethod()); - mockDecorator.DidNotReceive().Decorate(); + Assert.Equal("intercepted-decorated-PublicMethod", obj.PublicMethod()); } [Fact] public void InterceptsPublicInterfacesUseNoneGenericMethod() { - IDecoratorInterface mockDecorator = Substitute.For(); ContainerBuilder builder = new(); builder.RegisterType(); builder.RegisterDecorator(); - builder.RegisterInstance(mockDecorator); builder.RegisterType().InterceptedBy(typeof(StringMethodInterceptor)).As(); builder.EnableInterfaceInterceptors(typeof(IPublicInterface)); IContainer container = builder.Build(); IPublicInterface obj = container.Resolve(); - Assert.Equal("intercepted-PublicMethod", obj.PublicMethod()); - mockDecorator.DidNotReceive().Decorate(); + Assert.Equal("intercepted-decorated-PublicMethod", obj.PublicMethod()); } public class Decorator : IPublicInterface { private readonly IPublicInterface _decoratedService; - private readonly IDecoratorInterface _decoratorInterface; - public Decorator(IPublicInterface decoratedService, IDecoratorInterface decoratorInterface) - { - _decoratedService = decoratedService; - _decoratorInterface = decoratorInterface; - } + public Decorator(IPublicInterface decoratedService) => _decoratedService = decoratedService; - public string PublicMethod() - { - _decoratorInterface.Decorate(); - return _decoratedService.PublicMethod(); - } + public string PublicMethod() => $"decorated-{_decoratedService.PublicMethod()}"; } public class Interceptable : IPublicInterface { - public string PublicMethod() => throw new NotImplementedException(); + public string PublicMethod() => "PublicMethod"; } private class StringMethodInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { - if (invocation.Method.ReturnType == typeof(string)) - { - invocation.ReturnValue = "intercepted-" + invocation.Method.Name; - } - else - { - invocation.Proceed(); - } + invocation.Proceed(); + invocation.ReturnValue = $"intercepted-{invocation.ReturnValue}"; } } } From b0cc325e0232bd7594283bfa9166dce60caac91c Mon Sep 17 00:00:00 2001 From: Fei Xu Date: Wed, 25 Dec 2024 14:33:15 -0500 Subject: [PATCH 5/5] Make `ProxyHelpers` internal and improve code clarity. Refactored `ProxyHelpers` to be `internal` for better encapsulation. Fixed a typo in exception documentation and removed unreachable code in `GetInterceptorServicesFromAttributes()`. Added `InternalsVisibleTo` for unit testing support. --- .../Properties/AssemblyInfo.cs | 2 ++ src/Autofac.Extras.DynamicProxy/ProxyHelpers.cs | 12 ++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Autofac.Extras.DynamicProxy/Properties/AssemblyInfo.cs b/src/Autofac.Extras.DynamicProxy/Properties/AssemblyInfo.cs index 25fcca5..d064db5 100644 --- a/src/Autofac.Extras.DynamicProxy/Properties/AssemblyInfo.cs +++ b/src/Autofac.Extras.DynamicProxy/Properties/AssemblyInfo.cs @@ -1,7 +1,9 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +[assembly: InternalsVisibleTo("Autofac.Extras.DynamicProxy.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001008728425885ef385e049261b18878327dfaaf0d666dea3bd2b0e4f18b33929ad4e5fbc9087e7eda3c1291d2de579206d9b4292456abffbe8be6c7060b36da0c33b883e3878eaf7c89fddf29e6e27d24588e81e86f3a22dd7b1a296b5f06fbfb500bbd7410faa7213ef4e2ce7622aefc03169b0324bcd30ccfe9ac8204e4960be6")] [assembly: CLSCompliant(false)] [assembly: ComVisible(false)] diff --git a/src/Autofac.Extras.DynamicProxy/ProxyHelpers.cs b/src/Autofac.Extras.DynamicProxy/ProxyHelpers.cs index 7ae0969..03f3991 100644 --- a/src/Autofac.Extras.DynamicProxy/ProxyHelpers.cs +++ b/src/Autofac.Extras.DynamicProxy/ProxyHelpers.cs @@ -12,7 +12,7 @@ namespace Autofac.Extras.DynamicProxy; /// /// A helper class for proxy operations in an Autofac Interceptors context. /// -public static class ProxyHelpers +internal static class ProxyHelpers { /// /// The property name for the interceptors in Autofac registration extensions. @@ -67,7 +67,7 @@ public static void ApplyProxy(ResolveRequestContext ctx, ProxyGenerationOptions? var interfaces = proxiedInterfaces.Skip(1).ToArray(); var interceptors = GetInterceptorServices(ctx.Registration, ctx.Instance.GetType()) - .Select(s => ctx.ResolveService(s)) + .Select(ctx.ResolveService) .Cast() .ToArray(); @@ -80,7 +80,7 @@ public static void ApplyProxy(ResolveRequestContext ctx, ProxyGenerationOptions? /// Checks if the component registration can be used with interface proxying. /// /// The component registration to check. - /// Thrown if interface proxying is attemped on a type that is not an interface or is not accessible. + /// Thrown if interface proxying is attempted on a type that is not an interface or is not accessible. internal static void EnsureInterfaceInterceptionApplies(IComponentRegistration componentRegistration) { if (componentRegistration.Services @@ -111,7 +111,7 @@ internal static IEnumerable GetInterceptorServices(IComponentRegistrati result = result.Concat(existingPropertyServices); } - return (registration.Metadata.TryGetValue(AttributeInterceptorsPropertyName, out services) && services is IEnumerable existingAttributeServices) + return registration.Metadata.TryGetValue(AttributeInterceptorsPropertyName, out services) && services is IEnumerable existingAttributeServices ? result.Concat(existingAttributeServices) : result.Concat(GetInterceptorServicesFromAttributes(implType)); } @@ -124,10 +124,6 @@ internal static IEnumerable GetInterceptorServices(IComponentRegistrati internal static IEnumerable GetInterceptorServicesFromAttributes(Type implType) { var implTypeInfo = implType.GetTypeInfo(); - if (!implTypeInfo.IsClass) - { - return Enumerable.Empty(); - } var classAttributeServices = implTypeInfo .GetCustomAttributes(typeof(InterceptAttribute), true)