Ordering of Postsharp Aspects execution
Asked Answered
P

1

12

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?

Physique answered 20/6, 2014 at 15:14 Comment(5)
You also need to add the InterceptAspect on the TestImpl class to achieve your desired result.Yakut
@Yakut Although that would probably work, the TestImpl class isn't my class sometimes. I'd prefer a solution where I can leave TestImpl as it is.Physique
Alternativly you can put your [InterceptAspect(AttributeInheritance = MulticastInheritance.Multicast)] on the ITest interface itself. But this is the furthest what you can get. The problem is that Postsharp does the IL waving in one step and it only applies the InterceptAspect to the methods which are present at compile time so it does not see the new interface implementations added with the ComposeAspect. So the type what you add with the ComposeAspect should already contain the logging code provided by the InterceptAspect with putting it on the ITest or on the TestImpl class.Yakut
So, have you managed to solved this?Yakut
@Yakut Thanks for your help. Although you were right on how to make it work regardless of ordering, adding something to either the interface or the implementation in the background would defeat the purpose of the aspect to have it in one place only. So I solved our business case with another set of aspects and implementing the composition myself. If you want to post the workarounds as an answer, I'll be happy to accept it, as it seems that "it does not work the way you want it" is as close to an answer as I will get.Physique
Y
2

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();
}
Yakut answered 1/7, 2014 at 6:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.