DotNetCurry Logo

Aspect Oriented Programming (AOP) in C# via T4

Posted by: Yacoub Massad , on 11/12/2016, in Category Patterns & Practices
Views: 13083
Abstract: C# examples on how we can use the Text Template Transformation Toolkit (T4) to create aspects.

Aspect Oriented Programming (AOP) allows us to remove code duplication and code tangling that we get when we address cross-cutting concerns (e.g. logging). This allows us to focus on the real problem that we are trying to solve, as supporting functions and logic for cross-cutting concerns are isolated. In the article Aspect Oriented Programming in C# with SOLID, I discussed some options for doing AOP in SOLID code bases. For more details on what AOP is, and the problems we face when we do AOP in SOLID code bases, please refer to that article.

This article is published from the DNC Magazine for Developers and Architects. Download this magazine from here [PDF] or Subscribe to this magazine for FREE and download all previous and current editions.

One of the options for doing AOP is via Text Template Transformation Toolkit a.k.a. T4. This article will discuss T4 in more details, provide examples for creating a retry aspect as well as a logging aspect via T4, and discuss the benefits of such an approach.

The example code is available on GitHub at http://bit.ly/dnc-m27-aopviat4.

Text Template Transformation Toolkit (T4)

The Text Template Transformation Toolkit (T4) is a framework in Visual Studio that allows developers to generate text, based on templates. Developers write templates that can contain static text, and also code blocks that allow for the generation of dynamic text. Consider the following T4 example:

T4 Example

I have highlighted the static text in the template. The other part of the template is code written in C# that controls the dynamic generation of text. This code has to be inside a control block. In this example, we use two types of control blocks.

The first type is the standard control block. The code in this block is surrounded by the <# and #> brackets, and it allows us to write code statements.

The second type is the expression control block. The code in this block is surrounded by the <#= and #> brackets, and it allows us to specify a single expression.

This particular template will generate an XML document that looks like this:

<People>
	<Person Age="20"/>
	<Person Age="21"/>
	<Person Age="22"/>
	<Person Age="23"/>
	<Person Age="24"/>
	<Person Age="25"/>
	<Person Age="26"/>
	<Person Age="27"/>
	<Person Age="28"/>
	<Person Age="29"/>
</People>

Although the <person> tag is static in the template, it is surrounded by a standard control block that loops from 20 to 29. This will generate the static text multiple times. Notice how we have the <#= i #> expression control block to vary the value of the Age attribute in each iteration.

There is a third type of control block too (which we aren’t using in this example) called the class feature control block. This code block has the <#+ and #> brackets and allows us to write methods, fields, properties and even new interfaces and classes inside it.

T4 supports two kinds of templates; runtime templates and design time templates.

Runtime templates allow the generation of text during the execution of our applications. For example, one could use a runtime template to generate some report based on data from the database.

Design time templates allows the generation of text during design time. For example, one could use a design-time template to generate some C# code for a logging decorator for some class at design time.

Since in this article we are interested to use T4 to create aspects at design time, we are going to use design time templates.

This article is about AOP via T4, and not an introduction to T4. Further information about T4 can be obtained from this article on MSDN: Code Generation and T4 Text Templates (bit.ly/dnc-t4-msdn). I will be explaining more about relevant T4 features in later sections.

C# examples on GitHub

I have created C# examples on GitHub to demonstrate the ideas in this article. The solution contains 5 projects:

Project Description
T4Aspects This project contains RetryAspect.tt, LoggingAspect.tt, and other .tt files. These two files represent the Retry, and the Logging aspects respectively. These aspects are generic and do not apply to specific types. Many applications can use these aspects to apply them to their own types.
Library Contains the IDocumentSource interface and a sample implementation of this interface, the DocumentSource class. This represents a library where one would put the application code.
App

This is the project that contains the Composition Root of the application. This is where the different classes are wired together. This is also where we apply the aspects to objects. This project also contains the RetryAspectLocal.tt and LoggingAspectLocal.tt files. These files are not the real aspects, instead they consume the aspect files found in the T4Aspects project to generate the decorators relevant to the application. Under these files, you can find RetryAspectLocal.cs and LoggingAspectLocal.cs respectively. These files contain the decorators generated by the T4 engine.

Make sure that this project is set as the startup project when you want to run the application.

LoggingAOP This project contains some attributes that can be used to decorate methods and parameters so that the logging aspect knows which data needs to be logged.
Logging This project contains the ILogger interface and the basic ConsoleLogger implementation.

A simple example: a retry aspect

Sometimes, we want a certain operation to retry in case of failure. We can create an aspect that causes method invocations to retry if an exception is thrown. One way to do this is to create a T4 template that generates decorators for our interfaces that retries the execution of the decorated methods.

Let’s consider the following interface that represents a document source from which we can obtain documents of some format:

public interface IDocumentSource
{
    Document[] GetDocuments(string format);
}

We can manually write a decorator for this interface to support retrying the GetDocuments() method like this:

public class DocumentSourceRetryDecorator : IDocumentSource
{
    private readonly IDocumentSource decorated;
    private readonly int numberOfRetries;
    private readonly TimeSpan waitTimeBetweenRetries;
    //...
    public Document[] GetDocuments(string format)
    {
        int retries = 0;
        
        while(true)
        {
            try
            {
                return decorated.GetDocuments(format);
            }
            catch
            {
                retries++;
        
                if(retries == numberOfRetries)
                    throw;
        
                Thread.Sleep(waitTimeBetweenRetries);
            }
        }
    }
}

However, we don’t want to repeat the same thing for all interfaces in our code base. Instead, we create a T4 template that can generate a similar decorator for any number of interfaces that we like.

Let’s start by considering what would change in this code for different interfaces:

1. The class name needs to change. More specifically, “DocumentSource” at the beginning of the class name needs to change.

2. The implemented interface name (after the column) needs to change.

3. The type of the “decorated” field needs to change.

4. The method signature (return type, method name, and parameters) needs to change.

5. The method call inside the try block needs to change to reflect the method signature. If the target method has no return value (its return type is void), then the “return” keyword before the method call should be removed, and instead, a “return;” statement should be put after the method call.

6. If the other interface has multiple methods, we need to generate a method in the decorator for each of the methods in the interface.

We have basically identified the pieces that need to be generated dynamically. The rest of the code is static and doesn’t change from interface to interface.

Let’s discuss the retry aspect example. Please take a look at the RetryAspect.tt file. At the top of the file, we have some T4 directives. T4 directives provide instructions to the T4 engine. For example,

· the assembly directive in the first line informs the engine to load the EnvDTE assembly because the code inside the template is going to consume types inside that assembly.

· the import directive works like the using directive in C# in that it allows us to reference certain types in some namespace without typing the full name of the type.

· the include directive allows us to include the content of another .tt file inside the current template file. This allows us to create helper methods or classes and use them from multiple templates.

Please note that these .tt files that contain helper methods/classes are usually not meant to be used directly by the T4 engine to generate text/code. Therefore, it is best to let Visual Studio know never to execute the T4 engine on these files. To do so, we can click on the .tt file, and then in the properties window clear the value of the Custom Tool property. For example, the RetryAspect.tt file is not meant to be executed directly by the T4 engine. As you will see later in this article, a template file called RetryAspectLocal.tt that includes RetryAspect.tt is the template that the T4 engine will execute.

For more information on T4 directives, you might want to check the T4 Text Template Directives (bit.ly/dnc-t4dir-msdn) reference from MSDN.

The Visual Studio Automation Object Model

The first two assemblies referenced from our template are EnvDTE and EnvDTE80. These assemblies contain types that allow us to interact with Visual Studio. We will use this in our aspect to search for some interfaces in the current solution to create the decorators for. We will be able to enumerate methods inside any interface, get the signature of these methods, and read any attributes on the methods if we want.

This looks a lot like Reflection. However, the difference is that Reflection works only with code that is compiled into assemblies, while the Visual Studio Automation Object Model can work with code in the open solution in Visual Studio, even if it is not yet compiled.

The GenerateRetryDecoratorForInterface method

Let us continue exploring the RetryAspect.tt file. After the T4 directives, we have a class feature control block that contains a C# method called GenerateRetryDecoratorForInterface. This method takes an object of type CodeInterface2 (which is a type from the EnvDTE80 assembly) and generates a retry decorator for the interface represented by this object. We use this object to obtain information about this interface like its full name (e.g. Library.IDocumentSource), and the namespace where it resides. We also generate the name of the decorator that we want to create. The FormatInterfaceName method (found in the same file) removes the “I” from the interface name so that it becomes “prettier” when we use it to generate the name of the decorator.

Next, we have some static text that represents the retry decorator that will be generated. Notice how we use expression code blocks (the ones surrounded by <#= and #>) to insert some dynamic content (e.g. the namespace, the name of the decorator, and the implemented interface) between the static text.

We then have a class feature control block that calls the GenerateMethodsForInterface method. This method is going to generate the methods of the decorator. We use the PushIndent and PopIndent T4 methods to control the indentation of the generated code. We pass eight spaces to the PushIndent method so that any code generated inside the GenerateMethodsForInterface method is pushed eight spaces to the right.

Before we move on to the GenerateMethodsForInterface method, note that after the decorator class is generated, we generate another static class whose name starts with ExtentionMethodsForRetryAspectFor and ends with the formatted interface name. This static class contains a single extension method that allows us to decorate objects in a more readable way. Take a look at the Composition Root of the application to see how the generated ApplyRetryAspect method is used.

The GenerateMethodsForInterface method

This method will loop through the methods of the interface, and for each one, it will generate a method inside the decorator. As we did before, we first obtain some information about each method from inside the control block. For example, we determine whether the method has a “void” return type, and we store this information in the isVoid variable. Then we have the code of the decorator method as static text that includes some control blocks to dynamically generate code relevant to the current method. For example, note how the method call inside the try block is generated differently based on the isVoid variable.

The RetryAspectLocal.tt file

This file lives inside the App project. It searches for some interfaces (currently the IDocumentSource interface only) using the InterfaceFinder.FindInterfacesInSolution helper method (found in the InterfaceFinder.tt file in the T4Aspects project). It then loops through the interfaces found and invokes the GenerateRetryDecoratorForInterface method from the RetryAspect.tt file to generate a retry decorator for each interface.

To see the power of what we have done so far, let’s create a new interface inside the Library project, and then have T4 generate a retry decorator for the new interface.

1. Create the following interface inside the Library project (feel free to create any other interface if you want):

public interface IDocumentProcessor
{
    void ProcessDocument(Document document);
}

2. In the RetryAspectLocal.tt file, include “IDocumentProcessor” in the list of interfaces to search for like this:

var interfaces = InterfaceFinder.FindInterfacesInSolution(dte, new[] {"IDocumentSource", "IDocumentProcessor"});

1. Right-click the RetryAspectLocal.tt file in the Solution Explorer window, and click Run Custom Tool. This will run the T4 engine and generate the decorators.

2. Go to the RetryAspectLocal.cs file to see the generated DocumentProcessorRetryDecorator class and the ExtentionMethodsForRetryAspectForDocumentProcessor class.

A challenge for the reader

For asynchronous methods that return Task or Task <TResult> , we need to await the tasks returned from the decorated methods. Also, it makes sense to asynchronously wait (before retrying to execute the method again) via the Task.Delay method instead of using Thread.Sleep.

What changes need to be made to support such asynchronous methods?

A more advanced example: a logging aspect

The example solution also contains a logging aspect. The purpose of this aspect is to separate the logging logic of any class into its own decorator. To see how the end result would look like, take a look at the DocumentSourceLoggingDecorator class in the generated LoggingAspectLocal.cs file.

As with the retry aspect, the logging aspect lives inside the T4Aspects project in the LoggingAspect.tt file. However, there are some dependencies for this aspect. Take a look at the GenerateLoggingDecoratorForClass method inside the LoggingAspect.tt file. It takes in a CodeClass2 object representing the class that we are going to generate the decorator for, a CodeInterface object representing the interface that will be implemented by the decorator object, a IPreInvocationLoggingDataCodeGenerator object, and a IPostInvocationLoggingDataCodeGenerator object.

The last two parameters for this method represent seams that allow us to customize the logging aspect. The first one of these two parameters represent an object that knows how to generate logging data code prior to executing the decorated methods.

To explain this, let’s first take a look at the IPreInvocationLoggingDataCodeGenerator interface in the LoggingAspect.Core.tt file. This interface has a method called Extract that takes in a CodeFunction object that represents a method, and returns an array of LoggingDataCode objects that contain the code (as strings) that knows how to access the name and value of the data to be logged.

For an example implementation of this interface, let’s look at the LoggingDataCodeGeneratorForArgumentsBasedOnTheLogAttribute class. This class extracts logging data code based on the Log attribute that developers can use to decorate method parameters in their classes. Take a look at the GetDocuments method in the DocumentSource class:

public Document[] GetDocuments([Log("Document Format")] string format)
{
    return
        Enumerable
            .Range(0, 10)
            .Select(x => new Document("document" + x, "content" + x))
            .ToArray();
}

The format parameter is decorated with the Log attribute. The LoggingDataCodeGeneratorForArgumentsBasedOnTheLogAttribute class would in this case generate a LoggingDataCode object that contains the following code (as strings):

NameCode: "Document Format"

ValueCode: format

When a decorator is generated (in LoggingAspectLocal.cs), the following code will be included in the generated class as a result of this:

new LoggingData{ Name = "Document Format", Value = format},

So the content of NameCode is actually C# code that will be put after “Name = ”, and the content of ValueCode is C# code that would be put after “Value = “.

The other LoggingDataCode generator classes work in a similar way. Here is a list of these classes:

LoggingDataCodeGeneratorForArgumentsBasedOnTheLogAttribute, LoggingDataCodeGeneratorForArgumentsBasedOnTheLogCountAttribute, LoggingDataCodeGeneratorForReturnValueBasedOnTheLogAttribute, LoggingDataCodeGeneratorForReturnValueBasedOnTheLogCountAttribute, MethodDescriptionLoggingDataCodeGenerator.

The structure of the rest of the code in LoggingAspect.tt is somewhat similar to that of RetryAspect.tt. We basically loop through each of the methods and we have a mix of static code and control blocks to generate a try/catch block that invokes the decorated method. If there is an error, we invoke the LogError method on the ILogger dependency (in the decorator class). If on the other hand, the call is successful, we invoke LogSuccess.

Notice how we use the LoggingDataCode generator dependencies to extract the preInvocationLoggingDataCodeList list that contains LoggingDataCode items for pre-invocation data like arguments, and the postInvocationLoggingDataCodeList list that contains LoggingDataCode items for the return value.

As we did with the retry aspect, feel free to:

· create new interfaces and classes in the Library project

· decorate the class methods with Log, LogCount, and MethodDescription attributes

· inform our aspect system about them inside LoggingAspectLocal.tt

· see the logging decorator generated for them in LoggingAspectLocal.cs

· then use the decorators inside the Composition Root to see the logged messages printed to the console.

T4 versus Reflection

In the previous article, we used Reflection at runtime to extract logging data. I can see two advantages to the T4 approach in this article:

Performance

Consider the logging aspect for example. T4 will generate code at design time that accesses the source of the logging data (e.g. method parameters) directly. Therefore, performance is enhanced.

In the previous article’s example, LoggingData extractors were executed every time a method is invoked to extract logging data via Reflection. With T4, most of this complexity is moved to design time, and therefore no extractors/generators need to execute at runtime.

Debugging Experience

With T4, we generate decorators at design time. So at runtime, when we debug, we will debug code that is specific to a certain interface/class, and therefore is simpler. This is true because generic code is always more complex than concrete/specific code.

Consider for example the logic that extracts logging data in the previous article’s example. Such code does not exist in the T4 generated decorators so we don’t have to debug it at runtime.

Please note however, that similar code (e.g. the LoggingDataCode generators) can be debugged at design time. For example, you can right-click on the RetryAspectLocal.tt file in the Solution Explorer window and click Debug T4 Template. This allows us to debug the T4 template itself.

Conclusion:

T4 allows us to create templates that contain static code and code blocks that can generate code dynamically. This article provided examples on how to use T4 to create aspects: a retry aspect and a logging aspect. Using T4 to generate decorators at design time has some advantages over using Reflection at runtime. These advantages are better performance, and better runtime debugging experience.

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
Yacoub Massad is a software developer that works mainly with Microsoft technologies. Currently, he works at Zeva International where he uses C#, .NET, and other technologies to create eDiscovery solutions. He is interested in learning and writing about software design principles that aim at creating maintainable software. You can view his blog posts at criticalsoftwareblog.com


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!