Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ordering of Postsharp Aspects execution

Ok, this may get lengthy. I am trying to do two things:

  • I want to have a class that implements an interface by holding an instance of another class that every call is routed to.

  • I also want to intercept all method calls and do something.

Doing both on their own works great. Combining them seems to work only in one execution order and as Murphy has it, it's the wrong one (at least for me).

I'd like to inject the composition first so that the interception of all calls will also intercept those that were previously injected.

namespace ConsoleApplication13
{
  using System;
  using System.Reflection;

  using PostSharp;
  using PostSharp.Aspects;
  using PostSharp.Aspects.Dependencies;
  using PostSharp.Extensibility;

  [Serializable]
  [ProvideAspectRole("COMPOSER")]
  public sealed class ComposeAspectAttribute : CompositionAspect
  {
    [NonSerialized]
    private readonly Type interfaceType;

    private readonly Type implementationType;

    public ComposeAspectAttribute(Type interfaceType, Type implementationType)
    {
      this.interfaceType = interfaceType;
      this.implementationType = implementationType;
    }

    // Invoked at build time. We return the interface we want to implement. 
    protected override Type[] GetPublicInterfaces(Type targetType)
    {
      return new[] { this.interfaceType };
    }

    // Invoked at run time. 
    public override object CreateImplementationObject(AdviceArgs args)
    {
      return Activator.CreateInstance(this.implementationType);
    }
  }

  [Serializable]
  [ProvideAspectRole("INTERCEPTOR")]
  [MulticastAttributeUsage(MulticastTargets.Method)]
  [AspectRoleDependency(AspectDependencyAction.Order, AspectDependencyPosition.After, "COMPOSER")]
  public sealed class InterceptAspectAttribute : MethodInterceptionAspect
  {
    public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
    {
      base.CompileTimeInitialize(method, aspectInfo);

      // Warning in VS output
      Message.Write(method, SeverityType.Warning, "XXX", "Method: " + method.Name);
    }

    public override void OnInvoke(MethodInterceptionArgs args)
    {
      Console.WriteLine("Intercepted before");
      args.Proceed();
      Console.WriteLine("Intercepted after");
    }
  }

  interface ITest
  {
    void Call();
  }

  class TestImpl : ITest
  {
    public void Call()
    {
      Console.WriteLine("CALL remote implemented");
    }
  }

  [InterceptAspect(AspectPriority = 1)]
  [ComposeAspect(typeof(ITest), typeof(TestImpl), AspectPriority = 2)]
  class Test
  {
    // this should, after compilation, have all methods of ITest, implemented through an instance of TestImpl, which get intercepted before TestImpl is called

    public void CallLocalImplementedTest()
    {
      Console.WriteLine("CALL local implemented");
    }
  }


  class Program
  {
    static void Main()
    {
      var test = new Test();

      ITest t = Post.Cast<Test, ITest>(test);

      Console.WriteLine("TEST #1");
      t.Call();

      Console.WriteLine("TEST #2");
      test.CallLocalImplementedTest();

      Console.ReadLine();
    }
  }
}

I have tried to influence the execution order of the two aspects by

  • AspectRoleDependency, making the interceptor depend on the composer to run first

  • AspectPriority, also making the composer run first.

As the tests always yield

TEST #1
CALL remote implemented

TEST #2
Intercepted before
CALL local implemented
Intercepted after

it obviously doesn't work. Do you have a clue why my execution order has not changed? Did I do something wrong, did I miss a detail in the documentation? What can I do to intercept my composition-injected methods as well?

like image 891
nvoigt Avatar asked Jun 20 '14 15:06

nvoigt


1 Answers

With the current aspects and your current setup you cannot achive your desired result.

The problem is in how Postsharp work: it does the IL waving in one step and it only applies the InterceptAspect to the methods which are present at original compile time so it does not see the new interface implementations added with the ComposeAspect.

So no ordering of the accepts or providing roles, priorities or other configuration would help here.

One workaround would be to add the InterceptAspect on the injected TestImpl class:

[InterceptAspect]
class TestImpl : ITest
  {
    public void Call()
    {
      Console.WriteLine("CALL remote implemented");
    }
  }

In this case the logging logic will be added directly TestImpl so these method will contain the logging when it will be composed into your Test class.

Or if you don't mark every implementation you can put your aspect on the interface itself with:

[InterceptAspect(AttributeInheritance = MulticastInheritance.Multicast)]
interface ITest
{
   void Call();
}
like image 93
nemesv Avatar answered Sep 24 '22 03:09

nemesv