Design Principles and Patterns for Software Engineering with Microsoft .NET

  • 10/15/2008

From Objects to Aspects

No doubt that OOP is currently a mainstream programming paradigm. When you design a system, you decompose it into components and map the components to classes. Classes hold data and deliver a behavior. Classes can be reused and used in a polymorphic manner, although you must do so with the care we discussed earlier in the chapter.

Even with all of its undisputed positive qualities, though, OOP is not the perfect programming paradigm.

The OO paradigm excels when it comes to breaking a system down into components and describing processes through components. The OO paradigm also excels when you deal with the concerns of a component. However, the OO paradigm is not as effective when it comes to dealing with cross-cutting concerns.

A cross-cutting concern is a concern that affects multiple components in a system, such as logging, security, and exception handling. Not being a specific responsibility of a given component or family of components, a cross-cutting concern looks like an aspect of the system that must be dealt with at a different logical level, a level beyond application classes. Enter a new programming paradigm: aspect-oriented programming (AOP).

Aspect-Oriented Programming

The inherent limitations of the OO paradigm were identified quite a few years ago, not many years after the introduction of OOP. However, today AOP still is not widely implemented even though everybody agrees on the benefits it produces. The main reason for such a limited adoption is essentially the lack of proper tools. We are pretty sure the day that AOP is (even only partially) supported by the .NET platform will represent a watershed in the history of AOP.

The concept of AOP was developed at Xerox PARC laboratories in the 1990s. The team also developed the first (and still most popular) AOP language: AspectJ. Let’s discover more about AOP by exploring its key concepts.

Cross-Cutting Concerns

AOP is about separating the implementation of cross-cutting concerns from the implementation of core concerns. For example, AOP is about separating a logger class from a task class so that multiple task classes can use the same logger and in different ways.

We have seen that dependency injection techniques allow you to inject—and quite easily, indeed—external dependencies in a class. A cross-cutting concern (for example, logging) can certainly be seen as an external dependency. So where’s the problem?

Dependency injection requires up-front design or refactoring, which is not always entirely possible in a large project or during the update of a legacy system.

In AOP, you wrap up a cross-cutting concern in a new component called an aspect. An aspect is a reusable component that encapsulates the behavior that multiple classes in your project require.

Processing Aspects

In a classic OOP scenario, your project is made of a number of source files, each implementing one or more classes, including those representing a cross-cutting concern such as logging. As shown in Figure 3-5, these classes are then processed by a compiler to produce executable code.

Figure 3-5

Figure 3-5. The classic OOP model of processing source code

In an AOP scenario, on the other hand, aspects are not directly processed by the compiler. Aspects are in some way merged into the regular source code up to the point of producing code that can be processed by the compiler. If you are inclined to employ AspectJ, you use the Java programming language to write your classes and the AspectJ language to write aspects. AspectJ supports a custom syntax through which you indicate the expected behavior for the aspect. For example, a logging aspect might specify that it will log before and after a certain method is invoked and will validate input data, throwing an exception in case of invalid data.

In other words, an aspect describes a piece of standard and reusable code that you might want to inject in existing classes without touching the source code of these classes. (See Figure 3-6.)

Figure 3-6

Figure 3-6. The AOP model of processing source code

In the AspectJ jargon, the weaver is a sort of preprocessor that takes aspects and weaves their content with classes. It produces output that the compiler can render to an executable.

In other AOP-like frameworks, you might not find an explicit weaver tool. However, in any case, the content of an aspect is always processed by the framework and results in some form of code injection. This is radically different from dependency injection. We mean that the code declared in an aspect will be invoked at some specific points in the body of classes that require that aspect.

Before we discuss an example in .NET, we need to introduce a few specific terms and clarify their intended meaning. These concepts and terms come from the original definition of AOP. We suggest that you do not try to map them literally to a specific AOP framework. We suggest, instead, that you try to understand the concepts—the pillars of AOP—and then use this knowledge to better and more quickly understand the details of a particular framework.

Inside AOP Aspects

As mentioned, an aspect is the implementation of a cross-cutting concern. In the definition of an aspect, you need to specify advice to apply at specific join points.

A join point represents a point in the class that requires the aspect. It can be the invocation of a method, the body of a method or the getter/setter of a property, or an exception handler. In general, a join point indicates the point where you want to inject the aspect’s code.

A pointcut represents a collection of join points. In AspectJ, pointcuts are defined by criteria using method names and wildcards. A sample pointcut might indicate that you group all calls to methods whose name begins with Get.

An advice refers to the code to inject in the target class. The code can be injected before, after, and around the join point. An advice is associated with a pointcut.

Here’s a quick example of an aspect defined using AspectJ:

public aspect MyAspect
{
  // Define a pointcut matched by all methods in the application whose name begins with
  // Get and accepting no arguments. (There are many other ways to define criteria.)
  public pointcut allGetMethods ():
         call (* Get*() );

  // Define an advice to run before any join points that matches the specified pointcut.
  before(): allGetMethods()
  {
    // Do your cross-cutting concern stuff here
    // for example, log about the method being executed
    .
    .
    .
  }
}

The weaver processes the aspect along with the source code (regular class-based source code) and generates raw material for the compiler. The code actually compiled ensures that an advice is invoked automatically by the AOP runtime whenever the execution flow reaches a join point in the matching pointcut.

AOP in .NET

When we turn to AOP, we essentially want our existing code to do extra things. And we want to achieve that without modifying the source code. We need to specify such extra things (advice) and where we want to execute them (join points). Let’s briefly go through these points from the perspective of the .NET Framework.

How can you express the semantic of aspects?

The ideal option is to create a custom language a là AspectJ. In this way, you can create an ad hoc aspect tailor-made to express advice at its configured pointcuts. If you have a custom language, though, you also need a tool to parse it—like a weaver.

A very cost-effective alternative is using an external file (for example, an XML file) where you write all the things you want to do and how to do it. An XML file is not ideal for defining source code; in such a file, you likely store mapping between types so that when a given type is assigned an aspect, another type is loaded that contains advice and instructions about how to join it to the execution flow. This is the approach taken by Microsoft’s Policy Injection Application Block (PIAB) that we’ll look at in a moment.

How can you inject an aspect’s advice into executable code?

There are two ways to weave a .NET executable. You can do that at compile time or at run time. Compile-time weaving is preferable, but in our opinion, it requires a strong commitment from a vendor. It can be accomplished by writing a weaver tool that reads the content of the aspect, parses the source code of the language (C#, Visual Basic .NET, and all of the other languages based on the .NET common type system), and produces modified source code, but source code that can still be compiled. If you want to be language independent, write a weaver tool that works on MSIL and apply that past the compilation step. Alternatively, you can write a brand new compiler that understands an extended syntax with ad hoc AOP keywords.

If you want to weave a .NET executable at run time, you have to review all known techniques to inject code dynamically. One is emitting JIT classes through Reflection.Emit; another one is based on the CLR’s Profiling API. The simplest of all is perhaps managing to have a proxy sitting in between the class’s aspects and its caller. In this case, the caller transparently invokes a proxy for the class’s aspects. The proxy, in turn, interweaves advice with regular code. This is the same mechanism used in .NET Remoting and Windows Communication Foundation (WCF) services.

Using a transparent proxy has the drawback of requiring that to apply AOP to the class, the class must derive from ContextBoundObject or MarshalByRefObject. This solution is employed by PIAB.

AOP in Action

To finish off our AOP overview, let’s proceed with a full example that demonstrates how to achieve AOP benefits in .NET applications. We’ll use Microsoft’s Policy Injection Application Block in Enterprise Library 3.0 and higher to add aspects to our demo. For more information on PIAB, see http://msdn.microsoft.com/en-us/library/cc511729.aspx.

Enabling Policies

The following code demonstrates a simple console application that uses the Unity IoC container to obtain a reference to a class that exposes a given interface—ICustomerServices:

public interface ICustomerServices
{
    void Delete(string customerID);
}

static void Main(string[] args)
{
    // Set up the IoC container
    UnityConfigurationSection section;
    section = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
    IUnityContainer container = new UnityContainer();
    section.Containers.Default.Configure(container);
    // Resolve a reference to ICustomerServices. The actual class returned depends
    // on the content of the configuration section.
    ICustomerServices obj = container.Resolve<ICustomerServices>();

    // Enable policies on the object (for example, enable aspects)
    ICustomerServices svc = PolicyInjection.Wrap<ICustomerServices>(obj);

     // Invoke the object
     svc.Delete("ALFKI");

    // Wait until the user presses any key
    Console.ReadLine();
}

After you have resolved the dependency on the ICustomerServices interface, you pass the object to the PIAB layer so that it can wrap the object in a policy-enabled proxy. What PIAB refers to here as a policy is really like what many others call, instead, an aspect.

In the end, the Wrap static method wraps a given object in a proxy that is driven by the content of a new section in the configuration file. The section policyInjection defines the semantics of the aspect. Let’s have a look at the configuration file.

Defining Policies

PIAB is driven by the content of an ad hoc configuration section. There you find listed the policies that drive the behavior of generated proxies and that ultimately define aspects to be applied to the object within the proxy.

<policyInjection>
  <policies>
    <add name="Policy">
      <matchingRules>
        <add type="EnterpriseLibrary.PolicyInjection.MatchingRules.TypeMatchingRule ..."
             name="Type Matching Rule">
          <matches>
             <add match="ArchNet.Services.ICustomerServices" ignoreCase="false" />
          </matches>
        </add>
      </matchingRules>
      <handlers>
        <add order="0"
             type="ManagedDesign.Tools.DbLogger, mdTools"
             name="Logging Aspect" />
      </handlers>
    </add>
  </policies>
</policyInjection>

The matchingRules section expresses type-based criteria for a pointcut. It states that whenever the proxy wraps an object of type ICustomerServices it has to load and execute all listed handlers. The attribute order indicates the order in which the particular handler has to be invoked.

From this XML snippet, the result of this is that ICustomerServices is now a log-enabled type.

Defining Handlers

All that remains to be done—and it is the key step, indeed—is to take a look at the code for a sample handler. In this case, it is the DbLogger class:

public interface ILogger
{
    void LogMessage(string message);
    void LogMessage(string category, string message);
}

public class DbLogger : ILogger, ICallHandler
{
    // ILogger implementation
    public void LogMessage(string message)
    {
        Console.WriteLine(message);
    }
    public void LogMessage(string category, string message)
    {
        Console.WriteLine(string.Format("{0} - {1}", category, message));
    }

    // ICallHandler implementation
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        // Advice that runs BEFORE
        this.LogMessage("Begin ...");

        // Original method invoked on ICustomerServices
        IMethodReturn msg = getNext()(input, getNext);

        // Advice that runs AFTER
        this.LogMessage("End ...");

        return msg;
     }
     public int Order{ get; set; }
}

The class DbLogger implements two interfaces. One is its business-specific interface ILogger; the other (ICallHandler) is a PIAB-specific interface through which advice code is injected into the class’s aspect list. The implementation of ICallHandler is fairly standard. In the Invoke method, you basically redefine the flow you want for any aspect-ed methods.

In summary, whenever a method is invoked on a type that implements ICustomerServices, the execution is delegated to a PIAB proxy. The PIAB proxy recognizes a few handlers and invokes them in a pipeline. Each handler does the things it needs to do before the method executes. When done, it yields to the next handler delegate in the pipeline. The last handler in the chain yields to the object that executes its method. After that, the pipeline is retraced and each registered handler has its own chance to execute its postexecution code. Figure 3-7 shows the overall pipeline supported by PIAB.

Figure 3-7

Figure 3-7. The PIAB handler pipeline