PostSharp Principles: Day 12 – Aspect Providers, Part 1

by Dustin Davis on 12 Jul 2011

When it comes to building aspects, using the provided base classes such as OnMethodBoundaryAspect and LocationInterceptionAspect are quick and easy ways to implement a simple aspect. But they have their limitations: each aspect can only implement one transformation. But what if you need to encapsulate a design pattern made of several transformations? We’re going to look at two ways of building complex aspects: aspect providers today and advices tomorrow.

Base Aspect Classes

There are different base classes in which an aspect can derive from. For example, OnMethodBoundaryAspect is derived from MethodLevelAspect class because it deals with methods. These base classes are pre-configured for MulticastAttributeUsage and pre-implemented interfaces. These classes are just containers for behaviors, they do not implement any behavior themselves, but it’s important to choose the correct class when developing aspects.

· AssemblyLevelAspect – Base class for all aspects applied on assemblies

· TypeLevelAspect – Base class for all aspects applied on types

· MethodLevelAspect – Base class for all aspects applied on methods

· LocationLevelAspect – Base class for all aspects applied on fields, properties or parameters

· EventLevelAspect – Base class for all aspects applied on events

PostSharp does not rely on these classes, but on the interfaces they implement. You can create your own aspect classes by implementing the right interface – for instance IOnMethodBoundaryAspect.

IAspectProvider

When an aspect implements the IAspectProvider interface, it can provide additional aspects dynamically. Simply put, an aspect can tell PostSharp to apply other aspects to a target at compile time. Let’s take a look at an example

[Serializable]
public class Aspect1 : IOnMethodBoundaryAspect
{
    public void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine("Aspect1: OnEntry for " + args.Method.Name);
    }

    public void OnException(MethodExecutionArgs args) { }
    public void OnExit(MethodExecutionArgs args) { }
    public void OnSuccess(MethodExecutionArgs args) { }
    public void RuntimeInitialize(MethodBase method) { }
}

[Serializable]
public class Aspect2 : IMethodInterceptionAspect
{
    public void OnInvoke(MethodInterceptionArgs args)
    {
        Console.WriteLine("Aspect2: OnInvoke for " + args.Method.Name);
        args.Proceed();
    }

    public void RuntimeInitialize(MethodBase method) { }
}

[Serializable]
public class ComplexAspect : MethodLevelAspect, IAspectProvider
{
    private readonly Aspect1 _aspect1 = new Aspect1();
    private readonly Aspect2 _aspect2 = new Aspect2();

    #region IAspectProvider Members

    public IEnumerable ProvideAspects(object targetElement)
    {
        MemberInfo nfo = (MemberInfo)targetElement;

        yield return new AspectInstance(targetElement, _aspect1);
            
        if (nfo.ReflectedType.IsPublic && !nfo.Name.Equals(".ctor"))
        {
            yield return new AspectInstance(targetElement, _aspect2);
        }
    }

    #endregion
}

We have three aspects here. Aspect1 and Aspect2 are just simple aspects that write their status to the console. The ComplexAspect however has some work happening. ComplexAspect derives from MethodLevelAspect because we’re going to be dealing with methods and it implements the IAspectProvider interface because we want to dynamically determine which aspects to apply to each target method.

We start by defining two instances one for Aspect1 and another for Aspect2. We’ll use these inside of ProvideAspects method when telling PostSharp to apply them to a target.

Inside the ProvideAspect method we cast targetElement as a MemberInfo structure. If we were using a LocationLevelAspect class instead of MethodLevelAspect class, we would cast it as a LocationInfo instead of MemberInfo because we’re working on a different level.

Since the return type of ProvideAspects method is an IEnumerable, we can use a yield return to provide aspects for PostSharp to apply. We return the instance of Aspect1 for all targets and then we check to see if the target method is public and is not a constructor. If it meets the criteria then we return the instance of Aspect2.

public class Program
{
    static void Main(string[] args)
    {
        ExampleClass ec = new ExampleClass();
        ec.DoSomething();

        Console.ReadKey();
    }
}

[ComplexAspect]
public class ExampleClass
{
    public void DoSomething()
    {
        Console.WriteLine("Did something");
    }
}

Our test code is a simple class with the ComplexAspect applied to it and we’re just making a call to DoSomething. The output is

Aspect1: OnEntry for .ctor
Aspect1: OnEntry for DoSomething
Aspect2: OnInvoke for DoSomething
Did something

Aspect1 was applied to both the constructor and the DoSomething method while Aspect2 was only applied to the DoSomething method.

CustomAttributeIntroductionAspect

PostSharp provides us with a very nice aspect that we can use to apply custom attributes to targets. For example, we can apply attributes to a DTO for XmlSerialization. Let’s look at an example of that

[AddXmlIgnoreAttribute]
public class ExampleClass
{
    [XmlElement]
    public int ID { get; set; }
    public string First { get; set; }
    public string Last { get; set; }
}

[MulticastAttributeUsage(MulticastTargets.Field | MulticastTargets.Property,
TargetMemberAttributes = MulticastAttributes.Public | MulticastAttributes.Instance)] public sealed class AddXmlIgnoreAttribute : LocationLevelAspect, IAspectProvider { private static readonly CustomAttributeIntroductionAspect
customAttributeIntroductionAspect = new CustomAttributeIntroductionAspect( new ObjectConstruction(typeof(XmlIgnoreAttribute)
.GetConstructor(Type.EmptyTypes))); public IEnumerable ProvideAspects(object targetElement) { LocationInfo memberInfo = (LocationInfo)targetElement; if (memberInfo.PropertyInfo.IsDefined(typeof(XmlElementAttribute), false) || memberInfo.PropertyInfo.IsDefined(typeof(XmlAttributeAttribute), false)) yield break; yield return new AspectInstance(memberInfo.PropertyInfo,
customAttributeIntroductionAspect); } }

We define a simple DTO class with four public properties. The DTO class is decorated with the AddXmlIgnoreAttribute.

Our aspect is going to apply the [XmlIgnore] attribute to all members of the DTO. If a member is already marked for inclusion using the [XmlElement] or [XmlAttribute] attributes then it will not receive the [XmlIgnore] attribute.

We start by defining the multicast options for our aspect. We tell PostSharp to only apply the aspect to fields or properties that are public and not static. Our aspect derives from LocationLevelAspect so we receive information structures specific to fields, properties and parameters.

We declare a new instance of CustomAttributeIntroductionAspect and passing in the ObjectConstruction data for XmlIgnoreAttribute type using the default empty constructor for it. We’ll cover how CustomAttributeIntroductionAspect works internally at another time.

In the ProvideAspects method, we cast targetElement as LocationInfo so we can work with the reflection info for the target. We use the LocationInfo to check if the property has either of the xml attributes defined on it and if it does we don’t yield any results for that target. Otherwise, we pass back to PostSharp the instance of CustomAttributeIntroductionAspect for application on the target.

Using ILSpy to see the end result

image

Except for ID, all of the properties now have XmlIgnore attributes.

Conclusion

IAspectProvider is a great way to dynamically determine which aspects to apply to a target at compile time but because aspects are still independent of each other, it has its limitations. Tomorrow we’re going to look at another way to build complex aspects.
self573_thumb[1]Dustin Davis Davis is an enterprise solutions developer and regularly speaks at user groups and code camps. He can be followed on Twitter @PrgrmrsUnlmtd or his blog Programmers-Unlimited.com