Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StructureMap proxy all instances or modify instances just before returning

In StructureMap we can proxy TInterface and TConcreteImpl with TProxy this this:

ConfigurationExpression config = ...

config.For<TInterface>().DecorateAllWith<TProxy>();

config.For<TInterface>().Use<TConcreteImpl>();

I wanted to use DispatchProxy (and globally log before method invocation and after invocation) and globally register it for all types being instantiated from StructureMap, I'm wondering how to accomplish this?

More specifically, I want to run the following for all the types being instantiated:

TConcreteImpl instance = ...

TInterface proxy = DispatchProxyGenerator.CreateProxyInstance(typeof (TInterface), typeof (TProxy))
     .SetParameters(instance);

I already experimented with IInstancePolicy of StructureMap but no success because Instance is not the actual object instance.

public class Policy : IInstancePolicy
{
    public void Apply(Type pluginType, Instance instance)
    {

    }
}

Thank you so much

like image 946
Node.JS Avatar asked Jul 17 '19 14:07

Node.JS


1 Answers

Looks like implementing a custom IInterceptorPolicy fits here. It will be called for all types in the container, and may produce decorators for some/all of them. An example of it with dummy logger to console:

public class CustomInterception : IInterceptorPolicy
{
    public string Description => "test interception Console.WriteLine each method' arguments and return value"; 

    public IEnumerable<IInterceptor> DetermineInterceptors(Type pluginType, StructureMap.Pipeline.Instance instance)
    {
        Type dispatchProxyType = DummyDispatchProxyDontUseAtWork.GenerateStructureMapCompatibleDispatchProxyType(pluginType);

        yield return new DecoratorInterceptor(pluginType, dispatchProxyType);
    }
}

Called as:

var container = new StructureMap.Container(cntnr =>
{
    cntnr.Policies.Interceptors(new CustomInterception());

    cntnr.For<IFoo>().Use<Foo>();
    cntnr.For<IBar>().Use<Bar>();
});


var foo = container.GetInstance<IFoo>();
foo.FooFoo("1", "2");

Produced output:

FooFoo(1,2)
BarBar(2,1)
   BarBar -> 21
   FooFoo -> 21

The rest of example is below, so it could be executed. Tricky thing with DispatchProxy is that it creates a new type which is hard to be constructed by StructureMap. In .Net Core 2.1 DispatchProxy makes constructor with Action... parameter, but StructureMap expects something it can create. You definitely need an alternative proxy generator which would work with StructureMap more smoothly.

public interface IBar
{
    string BarBar(string a1, string a2);
}

public class Bar : IBar
{
    public string BarBar(string a1, string a2) => a1 + a2;
}

public interface IFoo
{
    string FooFoo(string a1, string a2);
}

public class Foo : IFoo
{
    public IBar Bar { get; private set; }

    public Foo(IBar bar)
    {
        Bar = bar;
    }
    public string FooFoo(string a1, string a2) => Bar.BarBar(a2, a1);
}

public class DummyDispatchProxyDontUseAtWork : DispatchProxy
{
    public object Instance { get; protected set; }

    public DummyDispatchProxyDontUseAtWork() : base()
    {}

    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        Console.WriteLine($"{targetMethod.Name}({string.Join(',', args)})");
        var result = targetMethod.Invoke(this.Instance, args);
        Console.WriteLine($"   {targetMethod.Name} -> {result}");
        return result;
    }

    private static readonly ConcurrentDictionary<Type, Type> generatedProxyTypes = new ConcurrentDictionary<Type, Type>();
    protected static readonly ConcurrentDictionary<string, object> privateHackedState = new ConcurrentDictionary<string, object>();
    private static readonly AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.Run);
    private static readonly ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(Guid.NewGuid().ToString());

    private static Type EmitDispatchProxyType(Type interfaceType)
    {
        object dispatchProxyObj = typeof(DispatchProxy).GetMethod("Create", BindingFlags.Static | BindingFlags.Public)
            .MakeGenericMethod(interfaceType, typeof(DummyDispatchProxyDontUseAtWork))
            .Invoke(null, null);

        string typeId = "DummyDispatchProxyDontUseAtWork" + Guid.NewGuid().ToString("N");
        privateHackedState[typeId] =
            dispatchProxyObj.GetType().GetField("invoke", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(dispatchProxyObj);

        var resultTypeBuilder = moduleBuilder.DefineType(
            typeId,
            TypeAttributes.Public, 
            dispatchProxyObj.GetType());

        var baseCtor = dispatchProxyObj.GetType().GetConstructors().First();

        var ctor = resultTypeBuilder.DefineConstructor(
            MethodAttributes.Public,
            CallingConventions.Standard,
            new[] {interfaceType});

        var il = ctor.GetILGenerator();

        il.Emit(OpCodes.Ldarg_0);

        il.Emit(OpCodes.Ldsfld, typeof(DummyDispatchProxyDontUseAtWork).GetField(nameof(privateHackedState), BindingFlags.NonPublic | BindingFlags.Static));
        il.Emit(OpCodes.Ldstr, typeId);
        il.Emit(OpCodes.Callvirt, typeof(ConcurrentDictionary<,>).MakeGenericType(typeof(string), typeof(object)).GetMethod("get_Item"));
        il.Emit(OpCodes.Call, baseCtor);

        var setInstanceMethodInfo = dispatchProxyObj.GetType()
            .GetMethod("set_" + nameof(Instance),BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Call, setInstanceMethodInfo);

        il.Emit(OpCodes.Ret);

        return resultTypeBuilder.CreateType();
    }

    public static Type GenerateStructureMapCompatibleDispatchProxyType(Type interfaceType)
    {
        return generatedProxyTypes.GetOrAdd(interfaceType, EmitDispatchProxyType);
    }
}
like image 130
Renat Avatar answered Oct 27 '22 07:10

Renat