Applying aspects to large codebases using attribute multicasting

Once have written an aspect we have to apply it to the application code so that it will be used. There are a number of ways to do this so let's take a look at one of them: custom attribute multicasting. Other ways include XML Multicating and dynamic aspect providers.

Applying to all members of a class

When we are trying to apply a method level aspect we can place an attribute each of the methods.

[OurLoggingAspect] 
public class CustomerServices 

As our codebase grows this approach becomes tedious. We need to remember to add the attribute to all of the methods on the class. If you have hundreds of classes you may have thousands of methods you need to manually add the aspect attribute to. It's an unsustainable proposition. Thankfully there is a way to make this easier. Instead of applying your aspect on each method you can add that attribute to the class and PostSharp will ensure that the aspect is applied to all of the methods on that class.

Applying an aspect to all types in a namespace.

Even though we don't have to apply an aspect to all methods in all classes in our application, adding the aspect attribute to every class could still be an overwhelming task. If we want to apply our aspect in a broad stroke we can make use of PostSharp's MulticastAttribute.

The MulticastAttribute is a special attribute that will apply other attributes throughout your codebase. Here's how we would use it.

  1. Open the AssemblyInfo.cs, or create a new file GlobalAspects.cs if you prefer to keep things separate (the name of this file does not matter).
  2. Add an [assembly:] attribute that references the aspect you want applied.
  3. Add the AttributeTargetTypes property to the aspects's constructor and define the namespace that you would like the aspect applied to.
    [assembly: OurLoggingAspect(AttributeTargetTypes= 
    "OurCompany.OurApplication.Controllers.*")] 

This one line of code is the equivalent of adding the aspect attribute to every class in the desired namespace.

Note

When setting the AttributeTargeTypes you can use wildcards to indicate that all sub-namespaces should have the aspect applied to them. It is also possible to indicate the targets of the aspect using regex. Add "regex:" as a prefix to the pattern you wish to use for matching.

Excluding an aspect from some members

Multicasting an attribute can apply the aspect with a very broad brush. It is possible to use AttributeExclude to restrict where the aspect is attached.

[assembly: OurLoggingAspect(AttributeTargetTypes="OurCompany.OurApplication.Controllers.*"
AttributePriority = 1)] 
[assembly: OurLoggingAspect(AttributeTargetMembers="Dispose", AttributeExclude = true
AttributePriority = 2)] 

In the example above the first multicast line indicates that the OurLoggingAspect should be attached to all methods in the Controllers namespace. The second multicast line indicates that the OurLoggingAspect should should not be applied to any method named Dispose.

Note

Notice the AttributePriority property that is set in both of the multicast lines. Since there is no guarantee that the compiler will apply the attributes in the order you have specified in the code, it is necessary to declare an order to ensure processing is completed as desired.

In this case, the OurLoggingAspect will be applied to all method in the Controllers namespace first. After that is completed, the second multicast of OurLoggingAspect is performed which then excludes the aspect from methods named Dispose.

Filtering by class visibility

Now that you've been able to apply our aspect to all classes in a namespace and it's sub-namespaces, you may be faced with the need to restrict that broad stroke. For example, you may want to apply your aspect only to classes defined as being public.

  1. Add the AttributeTargetTypeAttributes property to the Multicast attribute's constructor.
  2. Set the AttributeTargetTypeAttributes value to MulticastAttributes.Public
    [assembly: OurLoggingAspect 
    (AttributeTargetTypes="OurCompany.OurApplication.Controllers.*",  
    AttributeTargetTypeAttributes = MulticastAttributes.Public)] 

By combining AttributeTargetTypeAttributes values you are able to create many combinations that are appropriate for your needs.

Note

When specifying attributes of target members or types, do not forget to provide all categories of flags, not only the category on which you want to put a restriction.

Filtering by method modifiers

Filtering at a class level may not be granular enough for your needs. Aspects can be attached at the method level and you will want to control filtering on these aspects as well. Let's look at an example of how to apply aspects only to methods marked as virtual.

  1. Add the AttributeTargetMemberAttributes property to the Multicast attribute's constructor.
  2. Set the AttributeTargetMemberAttributes value to MulticastAttributes.Virtual
    [assembly: OurLoggingAspect 
    (AttributeTargetTypes="OurCompany.OurApplication.Controllers.*",  
    AttributeTargetMemberAttributes = MulticastAttributes.Virtual)] 

Using this technique you can apply a method level aspect, or stop it from being applied, based on the existence or non-existence of things like the static, abstract, and virtual keywords.

Programmatic filtering

There are situations where you will want to filter in a way that isn't based on class or method declarations. You may want to apply an aspect only if a class inherits from a specific class or implements a certain interface. There needs to be a way for you to accomplish this.

The easiest way is to override the CompileTimeValidate method of your aspect class, where you can perform your custom filtering. This is the opt-out approach. Have the CompileTimeValidate method return false without emitting any error, and the candidate target will be ignored. See Validate Aspect Usage for details.

The second approach is opt-in. See Dynamic Aspect Provider for details.

Reflecting custom attributes

There may be situations where you need to use reflection in your code to determine if an aspect is attached to the target code. By default PostSharp removes the aspect attributes after it has performed the IL Weaving during the post-compile process. If you need to retain those attributes it is possible to do so when multicasting the attribute

[assembly: OurLoggingAspect(AttributeTargetTypes="OurCompany.OurApplication.Controllers.*",  
PersistMetadata = true)] 

Note

Multicasting of attributes is not limited only to PostSharp aspects. You can multicast any custom attribute in your codebase in the same way as shown here. If a custom attribute is multicast with the PersistMetadata property set to true, when relfected on the compiled code will look as if you had manually added the custom attribute in all of the locations.



What to read next?

Dynamic Aspect Provider or back to Aspects.