From b5448dfbc51f6a14b70be7bffc2c182ce34c00f0 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Wed, 4 Nov 2020 16:38:02 -0300 Subject: [PATCH] Add BehaviorPipelineFactory support to DynamicProxy avatars too Added also a test that shows that constructor interception will not work (couldn't find a supported way of doing that) in dynamic avatars. Only the static ones will work for that. --- .../DynamicAvatarInterceptor.cs | 31 ++++++++++++++----- .../DynamicAvatarFactoryTests.cs | 21 +++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/Avatar.DynamicProxy/DynamicAvatarInterceptor.cs b/src/Avatar.DynamicProxy/DynamicAvatarInterceptor.cs index 528b01d..7061194 100644 --- a/src/Avatar.DynamicProxy/DynamicAvatarInterceptor.cs +++ b/src/Avatar.DynamicProxy/DynamicAvatarInterceptor.cs @@ -1,24 +1,36 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; using Castle.DynamicProxy; namespace Avatars { internal class DynamicAvatarInterceptor : IInterceptor, IAvatar // Implemented to detect breaking changes in Avatar { + static readonly MethodInfo expressionFactory = typeof(DynamicAvatarInterceptor).GetMethod("CreatePipeline", BindingFlags.Static | BindingFlags.NonPublic); + static readonly ConcurrentDictionary> createPipelineFactories = new(); + readonly bool notImplemented; - readonly BehaviorPipeline pipeline; + BehaviorPipeline? pipeline; - internal DynamicAvatarInterceptor(bool notImplemented) - { - this.notImplemented = notImplemented; - pipeline = new BehaviorPipeline(); - } + internal DynamicAvatarInterceptor(bool notImplemented) => this.notImplemented = notImplemented; - public IList Behaviors => pipeline.Behaviors; + public IList Behaviors => pipeline!.Behaviors; public virtual void Intercept(IInvocation invocation) { + if (pipeline == null) + pipeline = createPipelineFactories.GetOrAdd(invocation.Proxy.GetType(), type => + { + var expression = (Expression>)expressionFactory + .MakeGenericMethod(type) + .Invoke(null, null); + + return expression.Compile(); + }).Invoke(); + if (invocation.Method.DeclaringType == typeof(IAvatar)) { invocation.ReturnValue = Behaviors; @@ -26,7 +38,8 @@ public virtual void Intercept(IInvocation invocation) } var input = new MethodInvocation(invocation.Proxy, invocation.Method, invocation.Arguments); - var returns = pipeline.Invoke(input, (i, next) => { + var returns = pipeline.Invoke(input, (i, next) => + { try { if (notImplemented) @@ -54,5 +67,7 @@ public virtual void Intercept(IInvocation invocation) invocation.SetArgumentValue(index, returns.Outputs[i]); } } + + static Expression> CreatePipeline() => () => BehaviorPipelineFactory.Default.CreatePipeline(); } } diff --git a/src/Avatar.UnitTests/DynamicAvatarFactoryTests.cs b/src/Avatar.UnitTests/DynamicAvatarFactoryTests.cs index 855d0d9..5ee2188 100644 --- a/src/Avatar.UnitTests/DynamicAvatarFactoryTests.cs +++ b/src/Avatar.UnitTests/DynamicAvatarFactoryTests.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Threading.Tasks; using Sample; using Xunit; @@ -43,5 +44,25 @@ public void TestFactory() Assert.Equal(4, recorder.Invocations.Count); } + + [Fact] + public void ConstructorInterceptionNotSupported() + { + BehaviorPipelineFactory.LocalDefault = new RecordingBehaviorPipelineFactory(); + AvatarFactory.LocalDefault = new DynamicAvatarFactory(); + + var calculator = Avatar.Of(); + var avatar = calculator as IAvatar; + + Assert.NotNull(avatar); + Assert.Single(avatar.Behaviors); + // Cannot record ctor call + Assert.Empty(((RecordingBehavior)avatar.Behaviors[0]).Invocations); + } + + class RecordingBehaviorPipelineFactory : IBehaviorPipelineFactory + { + public BehaviorPipeline CreatePipeline() => new BehaviorPipeline(new RecordingBehavior()); + } } }