3

I have 2 classes, one for accessing the database and child class with caching. I can change the source code of both classes but there are many classes with different structure so I'm looking for a way to make generic solution that will help me to intercept only methods I marked with the Attribute or otherwise.

Here is an example

public class BaseClass
{
    [MyAttribute]
    public virtual MyEntity[] GetAll() {}
    [MyAttribute]
    public virtual MyEntity GetByKey(int key) {}
    [MyAttribute]
    public virtual void GetByName(string name) {}
}

public class ChildClass : BaseClass
{
    public override MyEntity GetByKey(int key) 
    {
        if(key > 100)
           return GetCachedEntity(key);
        return base.GetByKey(key);
    }
}

public class MyInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        // Here I can check any required conditions to decide on the way of processing
        var myCondition = invocation.Method.GetCustomAttributes(false).Any(a => a is MyAttribute);
        if(myCondition)
        {
            // extra logic for marked methods
        }

        invocation.Proceed();
    }
}

public static class MyProxyFactory
{
    private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator();

    // here is my generic proxy factory which I want to use for creating proxies for ChildClass objects and another objects that contains similar logic
    public static TInterface CreateProxy<TInterface>(TInterface concreteObject)
        where TInterface : class
    {
        var proxy = proxyGenerator.CreateInterfaceProxyWithTarget(concreteObject, ProxyGenerationOptions.Default, new MyInterceptor());
        return proxy;
    }
}

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class MyAttribute : Attribute {}

I'm trying to use invocation.Method.GetCustomAttributes() for myCondition and mark only base class methods but the problem is that when the ChildClass.GetByKey() invokes the base class method it does not intercept with MyInterceptor.Intercept() method.

I can replace the inheritance with decomposition for this example but then I'll need to implement GetAll and GetByName methods in ChildClass and this solution will not be generic.

How can I change ProxyGenerator settings or CreateProxy() method to solve my problem?

var realObject = new ChildClass();
var proxyObject = MyProxyFactory.CreateProxy(realObject);

// extra logic should be executed
proxyObject.GetAll();

// extra logic should be executed
proxyObject.GetByKey(99);

// extra logic should not be executed
proxyObject.GetByKey(101);
Vadim Martynov
  • 8,602
  • 5
  • 31
  • 43
  • 1
    maybe you should add some code that tests and proves what you're trying to say – NSGaga-mostly-inactive Jan 25 '18 at 16:38
  • @NSGaga I added the code at the end of my question. You can assume that extra logic in MyInterceptor contains `Console.WriteLine("query to database was executed")` then `proxyObject.GetByKey(99);` should write this message on console and `proxyObject.GetByKey(101);` should not. – Vadim Martynov Jan 25 '18 at 16:42

1 Answers1

4

A base.Method call deliberately doesn't try to find any method overload that's further down the inheritance relationship than the base class of this. The proxy doesn't perform fancy magic, it overrides the method in a subclass and relies on the virtual call to reach the proxy rather than its base implementations. Or if it is a wrapping proxy, it only intercepts external calls - I don't know the details of your used proxy type.

So basically, you can't expect any sub-class magic to happen once you write base.Method. You will need to re-visit your design approach if you need interception within hierarchy-internal child-parent interaction.

grek40
  • 13,113
  • 1
  • 24
  • 50
  • I can describe the solution which can solve my problem and I was just hoping that `Castle.DynamicProxy` already has ready-made solution via settings for it. On proxy creation the `ProxyGenerator` can iterate the inheritance hierarchy and make proxy for each class in hierarchy. If we creating proxy with imaginary `ProxyGenerationOptions.ProxyAll` parameter for `class B : a` the final hierarchy will be `A -> ProxyA -> B -> ProxyB`. If we call `base.Foo()` in `B` object we will call `ProxyA.Foo()` method with the interception. – Vadim Martynov Jan 29 '18 at 16:04
  • And I found that `ProxyGenerator` is not sealed then I can create my own generator based on it and implement required logic over existing methods. – Vadim Martynov Jan 29 '18 at 16:22
  • @VadimMartynov This is very interesting... I didn't know you could actually rewrite a class in a way that its ancestor hierarchy changes (from `A -> B` to `A -> Proxy -> B`). To be honest it's beyond my knowledge how to achieve this result without a pre-compilation code rewriter so maybe we get another answer on this matter. – grek40 Jan 29 '18 at 19:51
  • @VadimMartynov It is one thing to dynamically craft new types upon request. It is a completely different things to change already existing types such as B on the fly. For that, you would have to modify the CLR, so I am pretty sure this approach will not work. There is only one way to change an existing type in the CLR and that is by unloading its app domain and create a new app domain with the modified version of the type. – Georg Feb 05 '18 at 14:01