Aspect-Oriented Programming with PostSharp
Aspect-oriented programming has the potential to help every developer write higher-quality code in less time. Here's how to declaratively apply custom methods implementing common functionality to your code.
In a recent discussion with the applications development manager of a major global company that uses the Microsoft .NET Framework as its development platform, I was asked how his department could increase productivity. We discussed several approaches, including increased code reuse. That discussion led me to take a deeper look at aspect-oriented programming (AOP). What I found is that AOP can increase code reuse within targeted functional areas, while also improving code maintainability and readability. AOP has the potential to help every developer write higher-quality code in less time.
What Is AOP?
At its core, AOP is the practice of encapsulating functionality common among multiple code elements yet independent of the business logic, and applying that common functionality to the target source code elements declaratively.
The common functionality is referred to as a crosscutting concern because it "cuts across" the entire application as a capability that's useful, independent of the business logic. Common examples include error handling, logging, tracing, instrumentation, transaction handling, security, caching, data binding, undo/redo and even multithreading. None of these 10 examples involves business logic, but they're all common and often necessary capabilities in any high-quality application.
Benefits of AOP
The code that implements one of these crosscutting concerns is known as an "aspect," and thus gives rise to the term "aspect-oriented programming." What benefit does it provide the developer if the functionality for these aspects can be removed from the primary source code and declaratively applied as needed to the application?
I'm truly excited by the benefits of AOP:
- The business logic source code is much easier to read and understand, without the clutter of the code needed to support crosscutting concerns.
- Less repetition in the code of the business logic means shorter developer times, fewer defects and a less-tedious coding experience.
- Developers can better focus on implementing the proper business logic.
- Less-experienced developers won't have to learn to weave these crosscutting concerns throughout their code, and experienced developers will appreciate more fluid coding by focusing primarily on business logic.
To be clear, the code for these crosscutting concerns is still present in the application; it has simply been moved into aspects. Writing these aspects generally requires a more experienced developer, so they tend to be high-quality code. These aspects are added to the application by the AOP framework at specific execution points known as "join points," such as at the time of object instantiation, method initiation, method exit, property access and so on. A set of join points is known as a pointcut.
History of AOP
AOP isn't something recently created to help .NET developers. Work on AOP started in the Xerox Palo Alto Research Center (PARC) in 1996, which is the same group credited with giving us laser printing; the Ethernet network protocol; the modern PC GUI using windows, icons and a mouse; object-oriented programming (OOP) via Smalltalk; and very-large-scale integration (VLSI) for semiconductors. PARC research into AOP resulted in the design of what is now AspectJ for the Java language.
Since 1996, AOP extensions have been developed for multiple languages, including C++, Ruby, PHP, SAP ABAP and even COBOL. Visual Studio 2012 and earlier versions do not directly support AOP. The aspect-like capability to declaratively apply custom code to methods can be found in Windows Communication Foundation (WCF) declarative filters and ASP.NET MVC dynamic filters.
The Path to AOP
In order to fully implement AOP in a .NET application, you want the ability to move crosscutting concerns into separate aspects, apply them declaratively to one or more source code locations and -- without changing the application architecture -- enable the use of aspects. While the source code modified by the aspects may be aware of the aspects by leaving certain tasks to them, the source code should be ignorant of the internal implementation of the aspects.
One way to add aspects into an application is to wrap each class with a dynamic proxy (outer shell) to provide additional functionality around the class, but the benefits of this approach are limited. Most dependency-injection frameworks in .NET provide some support for proxy-based AOP, which can be useful for logging, instrumentation and caching. This approach is only able to inject behaviors into the communication between a service and its consumer -- not inside the service itself -- so it can have the disadvantage of altering the application architecture to support aspects.
Another way to add aspects to an application is through code generation, but this, too, is limited. This approach allows for more powerful code transformation, but requires recompilation of the application each time any of the aspects change.
A third way to add aspects is through metadata-driven programming, utilizing dynamic languages such as IronPython or IronRuby. This can allow additional functionality to be added at runtime via aspects, but the complexity of implementing your own AOP framework is prohibitive.
To see how to implement AOP in .NET using a full-featured AOP framework, I looked at PostSharp, which claims to be the most comprehensive aspect-oriented software available for .NET.
PostSharp is available from SharpCrafters s.r.o.. A free Starter Edition providing the most commonly used aspect types is available, and supports commercial applications with a royalty-free runtime. The Starter Edition can be downloaded from the SharpCrafters site. Installation via the PostSharp NuGet package is recommended for those interested in the Pro version. All editions of Visual Studio 2010 are supported.
PostSharp Pro extends the Starter Edition by adding the ability to implement more complex aspect scenarios, plus enhancements to the Visual Studio 2010 IDE with an Aspect Browser window and visual indicators in the code editor. PostSharp is installed into the folder C:\Program Files\PostSharp 2.1 on 32-bit Windows.
PostSharp began as an open source project by Gael Fraiteur in 2004, with the first stable release in 2007. As PostSharp grew in popularity, Fraiteur founded SharpCrafters in 2009 to allow him to devote his efforts to PostSharp full-time. The commercial launch of PostSharp Pro came in March 2010. SharpCrafters is headquartered in Prague, Czech Republic.
How Does PostSharp Work?
Once PostSharp is installed, you encapsulate the desired crosscutting functionality into an aspect method and then apply that aspect individually to the desired classes or methods within the application, either declaratively using attributes, or to a namespace using a multicast attribute.
PostSharp uses a post-compilation approach by modifying the Microsoft Intermediate Language (MSIL) to include the aspect behaviors. This makes PostSharp .NET language-independent, as all .NET languages ultimately produce MSIL output. MSIL is extensively documented in the ECMA-335 standard specification. MSIL transformation is also used by Microsoft in code contracts and is fully supported.
Aspects must be marked Serializable, because the aspect code is serialized and added to the .NET assembly as a managed resource so it can be available at runtime.
PostSharp is build server friendly. As part of the build process, PostSharp can and should be integrated with the build server process; if PostSharp is deployed to the source code repository, it doesn't need to be installed on the build server. If installed on the build server, PostSharp has a minimal UI, which can be suppressed with registry settings. PostSharp doesn't require a separate license for the build server. PostSharp supports code obfuscation to discourage reverse engineering the source code (see SharpCrafters.com for the current list of supported obfuscator products).
Coding an Exception Handler Aspect in Visual Basic
The aspect base class OnExceptionAspect is one of the commonly used aspects, and is available in the free PostSharp Starter Edition. This aspect wraps around the target method with a try-catch construct and provides the OnEntry, OnSuccess, OnException and OnExit advices. An advice is anything that adds a behavior or structural element to source code. In this case, an advice can be thought of as one of the overrideable methods in a PostSharp aspect.
Listing 1 shows a console application implementation of the aspect OnExceptionAspect. It provides the useful behavior of logging technical details of an exception for later investigation, while preventing potentially undesirable technical details, such as a connection string or resource path, from being shown to the user.
The aspect ExceptionWrapper generates a series of technical messages identified by a GUID to the Debug output window when an exception occurs. This simulates what would be logged when an exception occurs. It then throws a new exception with a user-friendly message to contact support for this Error ID. The original exception is carried as the innerException so that the calling routine retains full flexibility in how to handle the exception. The code in Listing 1 plus a reference to the PostSharp assembly was all that was needed to create this example in Visual Studio 2010 after PostSharp was installed.
Looking at Listing 1, the aspect is applied to the CreateError method using an attribute. No error-handling code is present within the method, yet it receives the full benefit of the custom error-handling logic in the ExecutionWrapper aspect. Coding the aspect involves creating a serializable class that inherits from the desired base class aspect, OnExceptionAspect.
The base class provides a series of methods that do nothing until overridden. In order to write trace messages in the case of an exception, ExceptionWrapper overrides the OnException advice and adds the desired behavior. The MethodExecutionArgs parameter provides the needed information about the exception that was thrown.
Figure 1 shows the output of the example. The trace messages in the first four lines were written by PostSharp because an error occurred in a method decorated by the ExceptionWrapper attribute. The last line shows the suggested non-technical error message shown to the user.
[Click on image for larger view.]
|Figure 1. Output of the ExceptionWrapper aspect.|
What if the ExceptionAspect should handle only certain exception types for a particular target? How are these exception types specified, and how does the aspect respond to only those exception types? Listing 2 shows how the GetExceptionType method can be overridden in the ExceptionWrapper class to filter the exception types for the aspect. The commented attributes show the various ways the attribute can decorate the target method with parameters, for either a single exception type or multiple exception types. If the GetExceptionType method returns an exception type matching the current exception during application execution, then it's handled; otherwise, it's ignored.
The PostSharp Starter Edition supports OnMethodBoundaryAspect. It's invoked each time the method is called, and it, too, supports the OnEntry, OnSuccess, OnException and OnExit advices. This aspect is appropriate for implementing tracing or execution instrumentation by tracking entry and exit of the desired target methods or classes.
Decorating each method with an attribute isn't practical for the large number of target methods that might be involved in tracing or instrumentation. For this you need the PostSharp multicasting capability, which allows an aspect to be applied to multiple targets by adding a directive to the AssemblyInfo.vb file. Multicast is how an aspect can be applied to an assembly without directly modifying the target source code.
The first Assembly directive in the following code applies the TracingWrapper aspect to all classes and methods in the MyApp.BusinessLayer namespace, including constructors. If you wish to limit the aspect to only methods of a certain name, you could use an Assembly directive similar to the second, which limits the instrumentation to only those methods that contain "InsertRecord" in the method name:
<Assembly: MyApp.TracingWrapper(AttributeTargetTypes:="MyApp.BusinessLayer") >
<Assembly: MyApp.InstrumentationWrapper(AttributeTargetMembers:="*InsertRecord*") >
When attempting to multicast an aspect, be sure to test it as an attribute on a single method first, to ensure it works properly.
Enhancing Role-Based Security
Declarative role-based security is supported in standard .NET applications, such as restricting a method to members of a list of roles. What if the business requirement is to restrict the users to performing functions for only their own business unit? Let's use the PostSharp Starter Edition to create a realistic purchasing scenario where employees can only requisition items for their own business unit.
Listing 3 demonstrates how to use the OnMethodBoundaryAspect to validate that only authenticated users can execute the method, and to only authenticate users whose UserSecurityInfo.BusinessUnit property matches the BusinessUnit parameter passed into the method. The assumption is that the application will prompt for the desired business unit before displaying the purchasing requisition form, and pass that in as a parameter to the CreateRequisition method.
Listing 3 overrides the OnEntry advice to determine if the user is authorized for that business unit. It demonstrates the ability to view the parameter values of the target method for use in the aspect's decision logic. The BusinessUnit parameter can appear anywhere in the target method parameter list, but must be called exactly "BusinessUnit." By overriding the RuntimeInitialize method, the aspect ensures that the user security table has been initialized prior to its use. The aspect doesn't check security if the target method is the constructor.
As you can see, some rather advanced security was applied to a method simply by decorating it with the ValidateBusinessUnit attribute.
Compatibility and Platforms
PostSharp is compatible with Visual Studio 2008 and Visual Studio 2010. The IDE enhancements are available only with the Pro version, and are not compatible with the Express version, as it doesn't support any IDE extensions. SharpCrafters has confirmed that Windows 8 (including the Windows Runtime) and Visual Studio 2012 will be supported in PostSharp 3.0, which is currently under development.
PostSharp can be used with Silverlight, the .NET Compact Framework and Windows Phone. Separate reference assemblies are provided for these environments. PostSharp 3.0 will use the Portable Class Library so that a common codebase can be used across the .NET platforms.
A New Aspect to Your Coding
I hope this article has helped you understand what AOP is and how it can simplify your coding experience without sacrificing desired functionality. I only scratched the surface of what can be done with AOP as implemented by PostSharp. I plan to incorporate AOP as a routine part of my coding efforts so that I can focus more on business logic and less on routine repetitive code, and gain some significant time savings. Maybe I'll even gain enough time for a vacation away from my computer!