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 is a special attribute that will apply other attributes throughout your codebase. Here's
how we would use it.
- Open the
AssemblyInfo.cs, or create a new file
GlobalAspects.csif you prefer to keep things separate (the name of this file does not matter).
- Add an
[assembly:]attribute that references the aspect you want applied.
- Add the
AttributeTargetTypesproperty 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.
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:" 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
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
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.
- Add the
AttributeTargetTypeAttributesproperty to the
- Set the
MulticastAttributes.Public[assembly: OurLoggingAspect(AttributeTargetTypes="OurCompany.OurApplication.Controllers.*",AttributeTargetTypeAttributes = MulticastAttributes.Public)]
AttributeTargetTypeAttributes values you are able to create many combinations that are appropriate
for your needs.
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.
- Add the
AttributeTargetMemberAttributesproperty to the
- Set the
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.
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)]
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