Skip to content
This repository has been archived by the owner on Jun 30, 2023. It is now read-only.

Commit

Permalink
Add BehaviorPipelineFactory support to DynamicProxy avatars too
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kzu committed Nov 4, 2020
1 parent 944e35d commit b5448df
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 8 deletions.
31 changes: 23 additions & 8 deletions src/Avatar.DynamicProxy/DynamicAvatarInterceptor.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
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<Type, Func<BehaviorPipeline>> 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<IAvatarBehavior> Behaviors => pipeline.Behaviors;
public IList<IAvatarBehavior> Behaviors => pipeline!.Behaviors;

public virtual void Intercept(IInvocation invocation)
{
if (pipeline == null)
pipeline = createPipelineFactories.GetOrAdd(invocation.Proxy.GetType(), type =>
{
var expression = (Expression<Func<BehaviorPipeline>>)expressionFactory
.MakeGenericMethod(type)
.Invoke(null, null);
return expression.Compile();
}).Invoke();

if (invocation.Method.DeclaringType == typeof(IAvatar))
{
invocation.ReturnValue = Behaviors;
return;
}

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)
Expand Down Expand Up @@ -54,5 +67,7 @@ public virtual void Intercept(IInvocation invocation)
invocation.SetArgumentValue(index, returns.Outputs[i]);
}
}

static Expression<Func<BehaviorPipeline>> CreatePipeline<TAvatar>() => () => BehaviorPipelineFactory.Default.CreatePipeline<TAvatar>();
}
}
21 changes: 21 additions & 0 deletions src/Avatar.UnitTests/DynamicAvatarFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using Sample;
using Xunit;

Expand Down Expand Up @@ -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<ICalculator>();
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<TAvatar>() => new BehaviorPipeline(new RecordingBehavior());
}
}
}

0 comments on commit b5448df

Please sign in to comment.