diff --git a/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs b/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs
index 222ed48..966f875 100644
--- a/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs
+++ b/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs
@@ -25,14 +25,24 @@ public static class RegistrationExtensions
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().
+ /// Enable class interception on the target type. Interceptors will be
+ /// determined via on the class or added
+ /// with
+ /// .
/// Only virtual methods can be intercepted this way.
///
- /// Registration limit type.
- /// Registration style.
- /// Registration to apply interception to.
- /// Registration builder allowing the registration to be configured.
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
public static IRegistrationBuilder EnableClassInterceptors(
this IRegistrationBuilder registration)
{
@@ -40,15 +50,59 @@ public static IRegistrationBuilder
- /// Enable class interception on the target type. Interceptors will be determined
- /// via Intercept attributes on the class or added with InterceptedBy().
+ /// Enable class interception on the target type, conditionally based on the
+ /// implementation type. Interceptors will be determined via
+ /// on the class or added with
+ /// .
/// Only virtual methods can be intercepted this way.
///
- /// Registration limit type.
- /// Activator data type.
- /// Registration style.
- /// Registration to apply interception to.
- /// Registration builder allowing the registration to be configured.
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// A predicate, evaluated against each candidate implementation type, that
+ /// determines whether interception is applied. Types for which the
+ /// predicate returns are registered without
+ /// interception.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
+ public static IRegistrationBuilder EnableClassInterceptors(
+ this IRegistrationBuilder registration,
+ Func shouldIntercept)
+ {
+ return EnableClassInterceptors(registration, ProxyGenerationOptions.Default, shouldIntercept);
+ }
+
+ ///
+ /// Enable class interception on the target type. Interceptors will be
+ /// determined via on the class or added
+ /// with
+ /// .
+ /// Only virtual methods can be intercepted this way.
+ ///
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Activator data type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
public static IRegistrationBuilder EnableClassInterceptors(
this IRegistrationBuilder registration)
where TConcreteReflectionActivatorData : ConcreteReflectionActivatorData
@@ -57,19 +111,105 @@ public static IRegistrationBuilder
- /// Enable class interception on the target type. Interceptors will be determined
- /// via Intercept attributes on the class or added with InterceptedBy().
+ /// Enable class interception on the target type, conditionally based on the
+ /// implementation type. Interceptors will be determined via
+ /// on the class or added with
+ /// .
+ /// Only virtual methods can be intercepted this way.
+ ///
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Activator data type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// A predicate, evaluated against the implementation type, that determines
+ /// whether interception is applied. When the predicate returns
+ /// the type is registered without interception.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
+ public static IRegistrationBuilder EnableClassInterceptors(
+ this IRegistrationBuilder registration,
+ Func shouldIntercept)
+ where TConcreteReflectionActivatorData : ConcreteReflectionActivatorData
+ {
+ return EnableClassInterceptors(registration, ProxyGenerationOptions.Default, shouldIntercept);
+ }
+
+ ///
+ /// Enable class interception on the target type with specific options and
+ /// additional interfaces. Interceptors will be determined via
+ /// on the class or added with
+ /// .
/// Only virtual methods can be intercepted this way.
///
- /// Registration limit type.
- /// Registration style.
- /// Registration to apply interception to.
- /// Proxy generation options to apply.
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// Proxy generation options to apply.
+ ///
+ ///
+ /// Additional interface types. Calls to their members will be proxied as well.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
+ public static IRegistrationBuilder EnableClassInterceptors(
+ this IRegistrationBuilder registration,
+ ProxyGenerationOptions options,
+ params Type[] additionalInterfaces)
+ {
+ return EnableClassInterceptors(registration, options, null, additionalInterfaces);
+ }
+
+ ///
+ /// Enable class interception on the target type, conditionally based on the
+ /// implementation type, with specific options and additional interfaces.
+ /// Interceptors will be determined via on
+ /// the class or added with
+ /// .
+ /// Only virtual methods can be intercepted this way.
+ ///
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// Proxy generation options to apply.
+ ///
+ ///
+ /// An optional predicate, evaluated against each candidate implementation type,
+ /// that determines whether interception is applied. Types for which the predicate
+ /// returns are registered without interception. When
+ /// all types are intercepted.
+ ///
/// Additional interface types. Calls to their members will be proxied as well.
/// Registration builder allowing the registration to be configured.
public static IRegistrationBuilder EnableClassInterceptors(
this IRegistrationBuilder registration,
ProxyGenerationOptions options,
+ Func? shouldIntercept,
params Type[] additionalInterfaces)
{
if (registration == null)
@@ -77,33 +217,100 @@ public static IRegistrationBuilder rb.EnableClassInterceptors(options, additionalInterfaces));
+ registration.ActivatorData.ConfigurationActions.Add((t, rb) => rb.EnableClassInterceptors(options, shouldIntercept, additionalInterfaces));
return registration;
}
///
- /// Enable class interception on the target type. Interceptors will be determined
- /// via Intercept attributes on the class or added with InterceptedBy().
+ /// Enable class interception on the target type with specific options and
+ /// additional interfaces. Interceptors will be determined via
+ /// on the class or added with
+ /// .
/// Only virtual methods can be intercepted this way.
///
- /// Registration limit type.
- /// Activator data type.
- /// Registration style.
- /// Registration to apply interception to.
- /// Proxy generation options to apply.
- /// Additional interface types. Calls to their members will be proxied as well.
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Activator data type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// Proxy generation options to apply.
+ ///
+ ///
+ /// Additional interface types. Calls to their members will be proxied as well.
+ ///
/// Registration builder allowing the registration to be configured.
public static IRegistrationBuilder EnableClassInterceptors(
this IRegistrationBuilder registration,
ProxyGenerationOptions options,
params Type[] additionalInterfaces)
where TConcreteReflectionActivatorData : ConcreteReflectionActivatorData
+ {
+ return EnableClassInterceptors(registration, options, null, additionalInterfaces);
+ }
+
+ ///
+ /// Enable class interception on the target type, conditionally based on the
+ /// implementation type, with specific options and additional interfaces.
+ /// Interceptors will be determined via on
+ /// the class or added with
+ /// .
+ /// Only virtual methods can be intercepted this way.
+ ///
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Activator data type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// Proxy generation options to apply.
+ ///
+ ///
+ /// An optional predicate, evaluated against the implementation type, that
+ /// determines whether interception is applied. When the predicate returns
+ /// the type is registered without interception. When
+ /// the type is intercepted.
+ ///
+ ///
+ /// Additional interface types. Calls to their members will be proxied as well.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
+ public static IRegistrationBuilder EnableClassInterceptors(
+ this IRegistrationBuilder registration,
+ ProxyGenerationOptions options,
+ Func? shouldIntercept,
+ params Type[] additionalInterfaces)
+ where TConcreteReflectionActivatorData : ConcreteReflectionActivatorData
{
if (registration == null)
{
throw new ArgumentNullException(nameof(registration));
}
+ // Class interception rewrites the implementation type to a proxy subclass
+ // at registration time, so the decision to intercept is made here, per type.
+ // When the predicate rejects the type the registration is left untouched.
+ if (shouldIntercept != null && !shouldIntercept(registration.ActivatorData.ImplementationType))
+ {
+ return registration;
+ }
+
registration.ActivatorData.ImplementationType =
_proxyGenerator.ProxyBuilder.CreateClassProxyType(
registration.ActivatorData.ImplementationType,
@@ -143,17 +350,99 @@ public static IRegistrationBuilder
- /// Enable interface interception on the target type. Interceptors will be determined
- /// via Intercept attributes on the class or interface, or added with InterceptedBy() calls.
+ /// Enable interface interception on the target type. Interceptors will be
+ /// determined via on the class or
+ /// interface, or added with
+ /// .
///
- /// Registration limit type.
- /// Activator data type.
- /// Registration style.
- /// Registration to apply interception to.
- /// Proxy generation options to apply.
- /// Registration builder allowing the registration to be configured.
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Activator data type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// Proxy generation options to apply.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
public static IRegistrationBuilder EnableInterfaceInterceptors(
this IRegistrationBuilder registration, ProxyGenerationOptions? options = null)
+ {
+ return EnableInterfaceInterceptors(registration, options, null);
+ }
+
+ ///
+ /// Enable interface interception on the target type, conditionally based on
+ /// the resolved implementation type. Interceptors will be determined via
+ /// on the class or interface, or added with
+ /// .
+ ///
+ ///
+ /// Registration limit type.
+ ///
+ /// Activator data type.
+ ///
+ /// Registration style.
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// A predicate, evaluated against the resolved implementation type, that
+ /// determines whether interception is applied. When the predicate returns
+ /// the resolved instance is returned without a proxy.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
+ public static IRegistrationBuilder EnableInterfaceInterceptors(
+ this IRegistrationBuilder registration,
+ Func shouldIntercept)
+ {
+ return EnableInterfaceInterceptors(registration, null, shouldIntercept);
+ }
+
+ ///
+ /// Enable interface interception on the target type, conditionally based on
+ /// the resolved implementation type, with specific options. Interceptors
+ /// will be determined via on the class or
+ /// interface, or added with
+ /// .
+ ///
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Activator data type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// Proxy generation options to apply.
+ ///
+ ///
+ /// A predicate, evaluated against the resolved implementation type, that
+ /// determines whether interception is applied. When the predicate returns
+ /// the resolved instance is returned without a proxy.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
+ public static IRegistrationBuilder EnableInterfaceInterceptors(
+ this IRegistrationBuilder registration,
+ ProxyGenerationOptions? options,
+ Func? shouldIntercept)
{
if (registration == null)
{
@@ -164,11 +453,21 @@ public static IRegistrationBuilder ctx.ResolveService(s))
.Cast()
.ToArray();
@@ -195,15 +494,29 @@ public static IRegistrationBuilder
- /// Allows a list of interceptor services to be assigned to the registration.
+ /// Assigns a list of interceptor services to a registration by service.
///
- /// Registration limit type.
- /// Activator data type.
- /// Registration style.
- /// Registration to apply interception to.
- /// The interceptor services.
- /// Registration builder allowing the registration to be configured.
- /// or .
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Activator data type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// The interceptor services.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
+ ///
+ /// Thrown when or is .
+ ///
public static IRegistrationBuilder InterceptedBy(
this IRegistrationBuilder builder,
params Service[] interceptorServices)
@@ -224,15 +537,29 @@ public static IRegistrationBuilder InterceptedBy
}
///
- /// Allows a list of interceptor services to be assigned to the registration.
+ /// Assigns a list of interceptor services to a registration by name.
///
- /// Registration limit type.
- /// Activator data type.
- /// Registration style.
- /// Registration to apply interception to.
- /// The names of the interceptor services.
- /// Registration builder allowing the registration to be configured.
- /// or .
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Activator data type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// The names of the interceptor services.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
+ ///
+ /// Thrown when or is .
+ ///
public static IRegistrationBuilder InterceptedBy(
this IRegistrationBuilder builder,
params string[] interceptorServiceNames)
@@ -246,15 +573,30 @@ public static IRegistrationBuilder InterceptedBy
}
///
- /// Allows a list of interceptor services to be assigned to the registration.
+ /// Assigns a list of interceptor services to a registration by interceptor
+ /// type.
///
- /// Registration limit type.
- /// Activator data type.
- /// Registration style.
- /// Registration to apply interception to.
- /// The types of the interceptor services.
- /// Registration builder allowing the registration to be configured.
- /// or .
+ ///
+ /// Registration limit type.
+ ///
+ ///
+ /// Activator data type.
+ ///
+ ///
+ /// Registration style.
+ ///
+ ///
+ /// Registration to apply interception to.
+ ///
+ ///
+ /// The types of the interceptor services.
+ ///
+ ///
+ /// Registration builder allowing the registration to be configured.
+ ///
+ ///
+ /// Thrown when or is .
+ ///
public static IRegistrationBuilder InterceptedBy(
this IRegistrationBuilder builder,
params Type[] interceptorServiceTypes)
diff --git a/test/Autofac.Extras.DynamicProxy.Test/ConditionalInterceptionFixture.cs b/test/Autofac.Extras.DynamicProxy.Test/ConditionalInterceptionFixture.cs
new file mode 100644
index 0000000..acfed34
--- /dev/null
+++ b/test/Autofac.Extras.DynamicProxy.Test/ConditionalInterceptionFixture.cs
@@ -0,0 +1,177 @@
+// Copyright (c) Autofac Project. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using Autofac.Builder;
+using Autofac.Features.Scanning;
+using Castle.DynamicProxy;
+using IInvocation = Castle.DynamicProxy.IInvocation;
+
+namespace Autofac.Extras.DynamicProxy.Test;
+
+public class ConditionalInterceptionFixture
+{
+ [Fact]
+ public void NullRegistration_Throws()
+ {
+ IRegistrationBuilder concrete = null!;
+ IRegistrationBuilder scanning = null!;
+ Func predicate = _ => true;
+
+ Assert.Throws(() => concrete.EnableInterfaceInterceptors(predicate));
+ Assert.Throws(() => concrete.EnableClassInterceptors(predicate));
+ Assert.Throws(() => scanning.EnableClassInterceptors(predicate));
+ }
+
+ [AttributeUsage(AttributeTargets.Class)]
+ private sealed class InterceptMeAttribute : Attribute
+ {
+ }
+
+ public interface IPublicInterface
+ {
+ string PublicMethod();
+ }
+
+ [Fact]
+ public void InterfaceInterception_AppliesProxyWhenPredicateMatches()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder
+ .RegisterType()
+ .EnableInterfaceInterceptors(t => t.IsDefined(typeof(InterceptMeAttribute), false))
+ .InterceptedBy(typeof(StringMethodInterceptor))
+ .As();
+ var container = builder.Build();
+
+ var obj = container.Resolve();
+
+ Assert.Equal("intercepted-PublicMethod", obj.PublicMethod());
+ }
+
+ [Fact]
+ public void InterfaceInterception_SkipsProxyWhenPredicateDoesNotMatch()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder
+ .RegisterType()
+ .EnableInterfaceInterceptors(t => t.IsDefined(typeof(InterceptMeAttribute), false))
+ .InterceptedBy(typeof(StringMethodInterceptor))
+ .As();
+ var container = builder.Build();
+
+ var obj = container.Resolve();
+
+ // Predicate returns false, so the raw instance is returned without a proxy.
+ Assert.Equal("PublicMethod", obj.PublicMethod());
+ Assert.IsType(obj);
+ }
+
+ [Fact]
+ public void InterfaceInterception_PredicateAppliesPerScannedType()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder
+ .RegisterAssemblyTypes(typeof(ConditionalInterceptionFixture).Assembly)
+ .Where(t => t == typeof(Intercepted) || t == typeof(NotIntercepted))
+ .As()
+ .EnableInterfaceInterceptors(t => t.IsDefined(typeof(InterceptMeAttribute), false))
+ .InterceptedBy(typeof(StringMethodInterceptor));
+ var container = builder.Build();
+
+ var all = container.Resolve>().ToList();
+
+ // The attributed type is proxied; the other is returned untouched.
+ Assert.Contains(all, o => o.PublicMethod() == "intercepted-PublicMethod");
+ Assert.Contains(all, o => o.PublicMethod() == "PublicMethod");
+ }
+
+ [Fact]
+ public void ClassInterception_AppliesProxyWhenPredicateMatches()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder
+ .RegisterType()
+ .EnableClassInterceptors(t => t.IsDefined(typeof(InterceptMeAttribute), false))
+ .InterceptedBy(typeof(StringMethodInterceptor));
+ var container = builder.Build();
+
+ var obj = container.Resolve();
+
+ Assert.Equal("intercepted-VirtualMethod", obj.VirtualMethod());
+ }
+
+ [Fact]
+ public void ClassInterception_SkipsProxyWhenPredicateDoesNotMatch()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder
+ .RegisterType()
+ .EnableClassInterceptors(t => t.IsDefined(typeof(InterceptMeAttribute), false))
+ .InterceptedBy(typeof(StringMethodInterceptor));
+ var container = builder.Build();
+
+ var obj = container.Resolve();
+
+ // Predicate returns false, so the type is registered without a proxy.
+ Assert.Equal("VirtualMethod", obj.VirtualMethod());
+ Assert.Same(typeof(NotInterceptedClass), obj.GetType());
+ }
+
+ [Fact]
+ public void ClassInterception_PredicateAppliesPerScannedType()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder
+ .RegisterAssemblyTypes(typeof(ConditionalInterceptionFixture).Assembly)
+ .Where(t => t == typeof(InterceptedClass) || t == typeof(NotInterceptedClass))
+ .EnableClassInterceptors(t => t.IsDefined(typeof(InterceptMeAttribute), false))
+ .InterceptedBy(typeof(StringMethodInterceptor));
+ var container = builder.Build();
+
+ Assert.Equal("intercepted-VirtualMethod", container.Resolve().VirtualMethod());
+ Assert.Equal("VirtualMethod", container.Resolve().VirtualMethod());
+ }
+
+ [InterceptMe]
+ public class Intercepted : IPublicInterface
+ {
+ public string PublicMethod() => "PublicMethod";
+ }
+
+ public class NotIntercepted : IPublicInterface
+ {
+ public string PublicMethod() => "PublicMethod";
+ }
+
+ [InterceptMe]
+ public class InterceptedClass
+ {
+ public virtual string VirtualMethod() => "VirtualMethod";
+ }
+
+ public class NotInterceptedClass
+ {
+ public virtual string VirtualMethod() => "VirtualMethod";
+ }
+
+ private class StringMethodInterceptor : IInterceptor
+ {
+ public void Intercept(IInvocation invocation)
+ {
+ if (invocation.Method.ReturnType == typeof(string))
+ {
+ invocation.ReturnValue = "intercepted-" + invocation.Method.Name;
+ }
+ else
+ {
+ invocation.Proceed();
+ }
+ }
+ }
+}