Yesterday we started looking at multicasting. Today we’ll go even further with assembly level declarations and how to prevent aspects from being applied.
Assembly Level Declaration
At the highest level we have assembly declarations. This is a powerful feature that allows us to achieve many tasks. Before we explore the details, let’s see it in action. Remove any aspect declarations you have currently.
Note: Depending on your own tastes, you can apply this assembly declaration in any file. Commonly, these are put in the AssemblyInfo.cs file, but they can go anywhere. I recommend putting them in relevant class files to keep the visibility or creating an AspectInfo.cs file to keep them in. Putting them in the AssemblyInfo.cs could be a problem since it's a rarely used file.
[assembly: PostSharpDemo1.MethodTraceAspect()] namespace PostSharpDemo1 { … }
It can be as simple as that. With no other options this declaration will apply our aspect to every method in every class. Run it and check out the results.
Oops! There was a problem. We can't build the project because PostSharp is trying to apply our aspect to our aspect. The result of this, at least in this case, would be a stack overflow.
Preventing Aspect Application
So how do we prevent an aspect from being applied to certain targets? There are a few ways to achieve this but the easiest and most direct way to explicitly declare the aspect on the target and setting the AttributeExclude property to true. To fix the build errors we have to apply this technique to our aspect class.
[Serializable] [MethodTraceAspect(AttributeExclude = true)] public class MethodTraceAspect: OnMethodBoundaryAspect { … }
Do the same thing for DatabaseExceptionWrapper. Build and run. The output window is filled with every method, constructor and property call made.
The AttributeExclude will work at the class and assembly level as well. Since there are far too many properties in this project to manually add exclusions to each one, we can declare an assembly level exclude
[assembly: PostSharpDemo1.MethodTraceAspect(AttributeExclude = true, AttributePriority = 0, AttributeTargetMemberAttributes = MulticastAttributes.CompilerGenerated)]
The result is the exclusion of property getter and setter methods. When we run the application now, we no longer see them in the output window.
Note: Using the MulticastAttributes.CompilerGenerated will affect more than just properties; anything that gets transformed by the compiler during the build will be affected.
Aspect Priority
We’ve added a new parameter to this declaration called AttributePriority. Application of attributes is undeterministic so there is no guarantee of execution order. If you look at the Warnings when you build the demo project you’ll see warnings from PostSharp about this.
Aspects do different things and the order is which they are applied can affect how they operate. It’s very important to keep this in mind when applying multiple aspects to a target.
We can set the AttributePriority with an integer value to determine the priority. The lower the value is, the higher the priority of the aspect. Higher priority aspects get applied first. Example:
[MethodTraceAspect(AttributePriority = 10)] [DatabaseExceptionWrapper(AttributePriority = 20)] public IQueryableGetByName(string value) { … }
We're telling PostSharp to apply the MethodTraceAspect first and then apply the DatabaseExceptionWrapper second. I used 10 and 20 because it gives better flexibility when you need to apply more aspects. You won’t have to adjust the priority values for each aspect this way. Feel free to use your own system.
Note that using AttributePriority is not considered best practice, because it quickly becomes difficult to manage. We’ll see another day how to address this issue in a clean and robust way.
Assembly Level Part 2
To wrap up this post, let's cover some of the options when declaring assembly declarations. To limit the scope of aspect application we can use the AttributeTargetTypes property to declare a specific type or namespace.
Namespace and Types
[assembly: PostSharpDemo1.MethodTraceAspect( AttributeTargetTypes = "PostSharpDemo1.InMemoryDataStore")]
This declaration will apply the aspect only to the InMemoryDataStore type. The result is the same as decorating the class manually. This is useful when specifying an abstract class because the aspect is applied to all derived classes.
Wildcards
We can also declare wildcards. Specifying an asterisk on part of a namespace will apply the aspect to all types
under that namespace and all types in sub namespaces.
[assembly: PostSharpDemo1.MethodTraceAspect( AttributeTargetTypes = "PostSharpDemo1.Data.*")]
Any qualified type under PostSharpDemo1.Data namespace will receive the aspect.
Regular Expressions
PostSharp also lets us use regular expressions to declare target types.
[assembly: PostSharpDemo1 MethodTraceAspect(AttributeTargetTypes = " regex:.*Memory.*")]
Any qualified type with ‘Memory’ in the type name will receive the aspect.
By name
Last but not least, we can specify the exact name of the target. In this case we can declare
[assembly: PostSharpDemo1.MethodTraceAspect(AttributePriority = 10 AttributeTargetMembers="GetByName")] [assembly: PostSharpDemo1.MethodTraceAspect(AttributePriority = 10, AttributeTargetMembers="GetBy*")] [assembly: PostSharpDemo1.MethodTraceAspect(AttributePriority = 10, AttributeTargetMembers="regex:GetBy.*")]
AttributeTargetMembers is where we declare the name criteria for members.
For example, if we wanted to exclude all properties but using a more specific method than specifying MulticastAttributes.CompilerGenerated we can use the following regular expression to filter any method starting with get_ or set_ since these are what the compiler automatically prefixes getter/setter methods with
[assembly: PostSharpDemo1.MethodTraceAspect(AspectPriority = 10)] [assembly: PostSharpDemo1.MethodTraceAspect(AspectPriority = 0, AttributeExclude = true, AttributeTargetMembers = "regex:get_.*|set_.*")]
Conclusion
Not by any means did we cover the breadth of applying aspects but you should now have a good grasp on how to get up and running quickly. Being able to declare what will receive an aspect with one single line of code frees up our time and reduces clutter and complexity.