Before all, let's see how the event publisher is used, without PostSharp:

public class Message
{

}

public class ViewModel : Screen
{
	public ViewModel()
	{
		IoC.Get<IEventPublisher>()
			.Subscribe<Message>(message => 
								{
									// Do some stuff
								});

		Deactivated += (s,e) => subscription.Dispose();
	}
	
	public void Publish()
	{
		IoC.Get<IEventPublisher>()
			.Publish(new Message());
	}
}

As you can see, that's very easy. You just have to call GetEvent to subscribe to an event, and call Publish to notify subscribers about a new event. And you don't have to forget to call Dispose on the observable when your view-model is deactivated, or it continues to be warned about new events.

With the aspect, here is how the class will become, for the same functionalities:

[UseEventPublisher]
public class ViewModel : Screen
{
        [Subscribe]
	public void Subscribe(Message message)
	{
		// Do some stuff
	}
	
	[Publish]
	public Message Publish()
	{
		return new Message();
	}
}

We will just have to declare with some attributes which methods will be used for the subscriptions and the publications, and let the aspect be the intermediary between the class and the event publisher, and do the disposal for us.

Let's start by defining the 2 attributes we will use to decorate the methods. They're very simple, as you can see:

[Serializable]
[AttributeUsage(AttributeTargets.Method)]
public class SubscribeAttribute : Attribute
{

}

[Serializable]
[AttributeUsage(AttributeTargets.Method)]
public class PublishAttribute : Attribute
{

}

They will just be used by the UseEventPublisher aspect to find the methods it is interested in.

The first aspect I have implemented wasn't very easy to write and to read, because IEventPublisher has generic methods. So I ended up with a lot of "ugly" reflection, to manually create the generic types, and to call the generic methods, with the correct arguments.

But thanks to the very reactive support on the forum of SharpCrafters, I finally had the clue: I should have an Aspect Provider, which will create generic aspects for Publish and Subscribe methods.

So now, we have 3 aspects to code:

  1. The aspect provider
  2. The subscribe aspect
  3. The publish aspect

For the last two aspects, there will be only one generic argument, which will be the type of the message the event publisher will manage. So, without the details, which will come after, here is what these aspects will look like:

public class PublishAspect<TMessage> : ...
{

}

public class SubscribeAspect<TMessage> : ...
{

}

But let's start by the first aspect. We know it must retrieve all the methods marked with the PublishAttribute or the SubscribeAttribute, and for each of these methods, must return an instance of the AspectInstance class, constructed with the correct generic aspect.

Let's begin by writing a helper function which will return an enumerable of the methods which have a custom attribute:

private static IEnumerable<MethodInfo> GetMethods<TAttributeType>(Type instanceType)
{
    const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;

    return instanceType.GetMethods(flags)
        .Where(mi => mi.GetCustomAttributes(typeof(TAttributeType), true).Any());
}

For the Publish methods, we must create an instance of the generic PublishAspect. If we recall how a Publish method is written:

[Publish]
public Message Publish()
{
	return new Message();
}

We know that the generic argument of the aspect definition, the type of the published message, is given to us by the returned type of the method, which can be obtained through the property ReturnType of the MethodInfo instance returned by GetMethods. So, we know the type to use for the construction of the PublishAspect, we just now have to create the aspect:

[Serializable]
[MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Multicast)]
public class UseEventPublisher : TypeLevelAspect, IAspectProvider
{
    public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
    {
        Type type = (Type)targetElement;

        foreach (var publishMethod in GetMethods<PublishAttribute>(type))
        {
            var publishType = typeof(PublishAspect<>);
            var genericPublishType = publishType.MakeGenericType(publishMethod.ReturnType);

            var instance = Activator.CreateInstance(genericPublishType) as IAspect;

            yield return new AspectInstance(publishMethod, instance);
        }
}

As you can see, nothing fancy. We construct the generic aspect using Reflection with the return type of the method, and yield a new AspectInstance.

Now comes the creation of the generic PublishAspect. I first tried to make this aspect derive from MethodInterceptionAspect, to put the code in the overridden OnInvoke method. But unfortunately, I had a compiler error:

A generic type cannot derive from 'MethodInterceptionAspect' because it is an attribute class

After a look in the documentation of PostSharp, I saw that MethodInterceptionAspect implements several interfaces. And the interface which concerns us is IMethodInterceptionAspect, as it declares the OnInvoke method:

[Serializable]
public class PublishAspect<TMessage> : IMethodInterceptionAspect
{
    public void RuntimeInitialize(MethodBase method)
    {
    }

    public void OnInvoke(MethodInterceptionArgs args)
    {
    }
}

We are not interested in the RuntimeInitialize method, so we will let it empty, and focus on the OnInvoke method, which must:

  1. Get the message to publish
  2. Publish the message

How does it get the message to publish? If you remember the pattern of the upper Publish method, the method returns the message to publish. So the OnInvoke method must first let the woven method construct the message, and obtain it. How? This is dead simple, thanks again to PostSharp.

We first construct the message by calling Proceed on the instance of MethodInterceptionArgs given in parameter, and get the constructed message by using the ReturnValue property of this same argument. Which gives:

[Serializable]
public class PublishAspect<TMessage> : IMethodInterceptionAspect
{
    public void RuntimeInitialize(MethodBase method)
    {
    }

    public void OnInvoke(MethodInterceptionArgs args)
    {
        args.Proceed();

        IoC.Get<IEventPublisher>().Publish((TMessage)args.ReturnValue);
    }
}

Easy, no?

So, if you want to publish a message from your woven class, you just have to call the woven published method, and PostSharp will do the stuff for us. Of course, if you need to give some parameters to the message to publish, you can add some parameters to the woven method, that will perfectly work as well.

We now have to implement the Subscribe part of the aspect. We have to modify the aspect provider first, to apply the Subscribe aspect to the methods. I will add this code in the ProvideAspects method:

foreach (var subscribeMethod in GetMethods<SubscribeAttribute>(type))
{
    var subscribeType = typeof(SubscribeAspect<>);
    var genericSubscribeType = subscribeType.MakeGenericType(subscribeMethod.GetParameters()[0].ParameterType);

    var instance = Activator.CreateInstance(genericSubscribeType) as IAspect;

    yield return new AspectInstance(subscribeMethod, instance);
}

Here, to get the type to use as the generic type argument for SubscribeAspect, I will use the unique parameter of the woven method. One more time, if we look at the pattern of the subscribe method given earlier, we can see that the type of this argument must be the type of the message we want to subscribe to. We can get this type with the GetParameters() method of MethodInfo.

The creation of the generic aspect remains the same as the creation of the PublishAspect.

The implementation of the SubscribeAspect is more tricky. Indeed, we don't have to manually call the woven methods, like the published methods. What need to done here in the aspect is:

  1. register each woven method to the event publisher when the class is instantiated
  2. execute the woven method when a new message of the correct type is published

As with PublishAspect, we can't inherit SubscribeAspect from a class, because once again it has to be generic. So we must find the correct interfaces. These are:

If you want to read more about these methods, you may find these links useful : Initialization of aspects, Forum post)

Before we code the aspect, here is what we should have, for the registration and the invocation of the woven method:

public void RuntimeInitializeInstance()
{
    var subscription = IoC.Get<IEventPublisher>().GetEvent<TMessage>()
        .Subscribe(message => INVOKE WOVEN METHOD ); 
}

So, we need to invoke the woven method in the Subscribe call. How do we get this method? In the RuntimeInitialize method, using the MethodBase argumen, as it has a Name property.

public void RuntimeInitialize(MethodBase method)
{
    methodName = method.Name;
}

We store in the methodName field the name of the woven method, so we can retrieve the MethodInfo properties of this method. As this field will only be used at runtime, we don't need it to be serialized. So we must place the NonSerializedAttribute on top of it.

We can now call Invoke on the MethodInfo when the event publisher will publish a matching message:

public void RuntimeInitializeInstance()
{
    var methodInfo = instance.GetType().GetMethod(methodName,
                                            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
                                            null,
                                            new[]
                                            {
                                                typeof (TMessage)
                                            },
                                            null);
            
    var subscription = IoC.Get<IEventPublisher>().GetEvent<TMessage>()
            .Subscribe(message => methodInfo.Invoke(XXX, new object[] { message })); 
}

To invoke our method, we need the object which the method belongs to. In other words, we need the instance of the class on which the aspect is applied. We will get this information in the CreateInstance method, using the Instance property of the AdviceArgs argument:

public object CreateInstance(AdviceArgs adviceArgs)
{
    instance = adviceArgs.Instance;

    return MemberwiseClone();
}

Again, we store this information in a non-serialized field.

We now have all the pieces we need to finish (for now) this aspect. So let's finish to code the RuntimeInitializeInstance method:

public void RuntimeInitializeInstance()
{
    var methodInfo = instance.GetType().GetMethod(methodName,
                                            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
                                            null,
                                            new[]
                                            {
                                                typeof (TMessage)
                                            },
                                            null);

    var subscription = IoC.Get<IEventPublisher>().GetEvent<TMessage>()
        .Subscribe(message => methodInfo.Invoke(instance, new object[] { message })); 
}

To completely finish this aspect, there is one last thing to be done: dispose the subscription to the event publisher when the view-model is closed. We just have to append this code to RuntimeInitializeInstance :

var screen = instance as IScreen;

if (screen != null)
    screen.Deactivated += (o, e) => subscription.Dispose();

In case you wonder why I don't store the method info of the woven method at compile time, using the interface IMethodLevelAspectBuildSemantics and its method CompileTimeInitialize, this is because of issues with generic classes on which the aspect could be applied.

Indeed, if you apply this aspect on generic abstract classes, you will end with exceptions like "Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true." This is because at compile time, you will construct the method info on the base class type, and not on its derived non-generic-anymore type. As I do this like above, the type, on runtime, is fully constructed, and then the reflection call to create the method info is done on the correct object.

But here we are at the end of this article, which I hope has filled its goal: to be a good introduction on how to use generic aspects with PostSharp. Event if this aspect is very useful (I use it everyday within my view-models), I am still not fully happy with it, because on the strong dependency it has with caliburn micro. Even if it is a great framework too, I would prefer this aspect not to rely on it. That is to say, I would prefer not to use the IoC class.

One solution to this may be to modify the UseEventPublisher aspect so that it automatically adds a new parameter of type IEventPublisher to the constructor of the view-model. This parameter would be resolved automatically when the view-model is created by caliburn micro. Then, the provider aspect would only need to store this parameter in a property, and would give it to the "child" aspect. But when I tried this solution, I entered in the holy low-level world of PostSharp, which only has the reference documentation available, and thus makes things very complicated to use.

Currently, I succeed to add the parameter to the constructors, I know how to pass the value of this parameter to the "child" aspects, but I have a problem with the communication between the aspects, because the aspect provider can't see there is a new parameter to the constructor.

But I plan to look again at that, when I will have some more time, and write a new article if I succeed to make all of this work.

Anyway, I hope you appreciated this article, and see you soon!

Edit: You will find the 2 small unit tests as an attachment