Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public static IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationSt
{
next(ctx);

EnsureInterfaceInterceptionApplies(ctx.Registration);
EnsureInterfaceInterceptionApplies(ctx.Service, ctx.Registration);

// The instance won't ever _practically_ be null by the time it gets here.
var proxiedInterfaces = ctx.Instance!
Expand Down Expand Up @@ -267,12 +267,15 @@ public static IRegistrationBuilder<TLimit, TActivatorData, TStyle> 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<IServiceWithType>()
.Select(s => new Tuple<Type, TypeInfo>(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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TCommand, TResult>
{
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<StringMethodInterceptor>();
builder
.RegisterAssemblyTypes(typeof(OpenGenericInterfaceInterceptionFixture).Assembly)
.AsClosedTypesOf(typeof(ICommandHandler<,>))
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(StringMethodInterceptor));
var container = builder.Build();

var handler = container.Resolve<ICommandHandler<string, string>>();

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<StringMethodInterceptor>();
builder
.RegisterType<StringCommandHandler>()
.AsSelf()
.As<ICommandHandler<string, string>>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(StringMethodInterceptor));
var container = builder.Build();

var handler = container.Resolve<ICommandHandler<string, string>>();

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<StringMethodInterceptor>();
builder
.RegisterType<StringCommandHandler>()
.AsSelf()
.As<ICommandHandler<string, string>>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(StringMethodInterceptor));
var container = builder.Build();

var dx = Assert.Throws<DependencyResolutionException>(() => container.Resolve<StringCommandHandler>());
Assert.IsType<InvalidOperationException>(dx.InnerException);
}

public class StringCommandHandler : ICommandHandler<string, string>
{
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();
}
}
}
}
Loading