diff --git a/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs b/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs index 68989f2..222ed48 100644 --- a/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs +++ b/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs @@ -164,7 +164,7 @@ public static IRegistrationBuilder InterceptedBy return InterceptedBy(builder, interceptorServiceTypes.Select(t => new TypedService(t)).ToArray()); } - private static void EnsureInterfaceInterceptionApplies(IComponentRegistration componentRegistration) + private static void EnsureInterfaceInterceptionApplies(Service service, IComponentRegistration componentRegistration) { - if (componentRegistration.Services - .OfType() - .Select(s => new Tuple(s.ServiceType, s.ServiceType.GetTypeInfo())) - .Any(s => !s.Item2.IsInterface || !ProxyUtil.IsAccessible(s.Item1))) + // Only the service actually being resolved needs to be a public interface. + // A registration may expose additional services (for example, the concrete + // type alongside an interface when scanning with AsClosedTypesOf) that are + // not interfaces; those are irrelevant when interception is applied to an + // interface service. See issue #27. + if (service is IServiceWithType serviceWithType && + (!serviceWithType.ServiceType.GetTypeInfo().IsInterface || !ProxyUtil.IsAccessible(serviceWithType.ServiceType))) { throw new InvalidOperationException( string.Format( diff --git a/test/Autofac.Extras.DynamicProxy.Test/OpenGenericInterfaceInterceptionFixture.cs b/test/Autofac.Extras.DynamicProxy.Test/OpenGenericInterfaceInterceptionFixture.cs new file mode 100644 index 0000000..72ed67e --- /dev/null +++ b/test/Autofac.Extras.DynamicProxy.Test/OpenGenericInterfaceInterceptionFixture.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 Autofac.Core; +using Castle.DynamicProxy; +using IInvocation = Castle.DynamicProxy.IInvocation; + +namespace Autofac.Extras.DynamicProxy.Test; + +public class OpenGenericInterfaceInterceptionFixture +{ + public interface ICommandHandler + { + TResult Handle(TCommand command); + } + + [Fact] + public void InterceptsClosedTypesOfScannedOpenGenericInterface() + { + // Issue #27: When scanning an assembly and registering closed types of an + // open generic interface, interface interception should still work even + // though the scan also registers the concrete type as a service. + var builder = new ContainerBuilder(); + builder.RegisterType(); + builder + .RegisterAssemblyTypes(typeof(OpenGenericInterfaceInterceptionFixture).Assembly) + .AsClosedTypesOf(typeof(ICommandHandler<,>)) + .EnableInterfaceInterceptors() + .InterceptedBy(typeof(StringMethodInterceptor)); + var container = builder.Build(); + + var handler = container.Resolve>(); + + Assert.Equal("intercepted-Handle", handler.Handle("input")); + } + + [Fact] + public void InterceptsInterfaceServiceWhenRegistrationAlsoExposesConcreteType() + { + // A registration can expose both the concrete type and an interface. When + // resolved by the interface, interception should apply even though the + // concrete type is not an interface. + var builder = new ContainerBuilder(); + builder.RegisterType(); + builder + .RegisterType() + .AsSelf() + .As>() + .EnableInterfaceInterceptors() + .InterceptedBy(typeof(StringMethodInterceptor)); + var container = builder.Build(); + + var handler = container.Resolve>(); + + Assert.Equal("intercepted-Handle", handler.Handle("input")); + } + + [Fact] + public void ThrowsWhenResolvingConcreteServiceWithInterfaceInterception() + { + // Resolving the non-interface service directly cannot be proxied via + // interface interception and must still throw, even when the registration + // also exposes an interface service. + var builder = new ContainerBuilder(); + builder.RegisterType(); + builder + .RegisterType() + .AsSelf() + .As>() + .EnableInterfaceInterceptors() + .InterceptedBy(typeof(StringMethodInterceptor)); + var container = builder.Build(); + + var dx = Assert.Throws(() => container.Resolve()); + Assert.IsType(dx.InnerException); + } + + public class StringCommandHandler : ICommandHandler + { + public string Handle(string command) => command; + } + + private class StringMethodInterceptor : IInterceptor + { + public void Intercept(IInvocation invocation) + { + if (invocation.Method.ReturnType == typeof(string)) + { + invocation.ReturnValue = "intercepted-" + invocation.Method.Name; + } + else + { + invocation.Proceed(); + } + } + } +}