How to use T4 to generate Decorator classes


This article demonstrates how to use Introspection and T4 templates to generate C# Decorator classes in Visual Studio.

Overview

Decorator is a design pattern that allows adding or modifying behavior of an object dynamically (at run-time). Also known as Wrapper, this design pattern offers an alternative to subclassing (or modifying behavior of an object statically, at compile-time). It is best described in the Design Patterns: Elements of Reusable Object-Oriented Software by Eric Gamma, Richard Helm, Ralph Johnson and John Vlissides. Current description of this pattern on Wikipedia is incorrect; a better description can be found here.

While in most cases Decorators contain unique code, they sometimes need to perform mundane and repetitive tasks such as logging, security, exception handling, etc. If you have a large number of different components (such as WCF service contracts) that need decorators performing the same task (such as security/authorization), generating decorator code may be beneficial. This can be accomplished using .NET metadata describing component types.

An obvious first choice for accessing .NET metadata is Reflection. Built into .NET base class library, this API provides access to metadata of .NET types. Unfortunately, Reflection is optimized for code execution. One particular limitation makes it ill-suited for code generation - an assembly loaded using Reflection can only be unloaded with its AppDomain. Because T4 templates are compiled into .NET assemblies and cached to improve performance of code generation, using Reflection to access the component assembly causes T4 to lock it. This prevents you from changing/recompiling the component until you close and reopen the solution. While you can certainly live with this annoyance, it would be nice to avoid it.

Faced with similar challenges, FxCop team developed Introspection engine as an alternative to Reflection. This API was optimized for code analysis and doesn’t lock the assemblies. Included with FxCop starting with version 1.30, this API resides in FxCopSdk.dll and Microsoft.Cci.dll assemblies located in FxCop installation directory. Here is an example of using Introspection engine in FxCop 1.35 to access metadata for IMyComponent interface defined in ComponentLibrary.dll.

using System;
using Microsoft.Cci; // Microsoft.FxCop.Sdk in FxCop 1.36
// …  

AssemblyNode assembly = AssemblyNode.GetAssembly(“ComponentLibrary.dll”);
TypeNode interface = assembly.GetType(
    Identifier.For(“ComponentLibrary”), // namespace
    Identifier.For(“IMyComponent”));    // type name

Having access to the interface metadata, writing a T4 template to generate decorator class that implements it is straightforward.

public class Decorator: <#= interface.FullName #>
{    // …
<#
    foreach(Member member in interfaceType.Members)
        WriteMember(member);
#>
}

Main disadvantage of using Introspection is its lack of documentation. I wouldn’t be able to figure this out without Lutz Roeder’s Reflector. Jason Kresowaty’s web site and John Robbins’ article in MSDN Magazine are also helpful, even though they focus on using Introspection to develop FxCop rules.

Implementation

In order to run this code, you will need Visual Studio 2005 Standard (or higher) Edition, FxCop 1.35 and DSL Tools installed on your computer. You may also want to install T4 Editor by Tangible Engineering, which adds IntelliSense and syntax highlighting to Visual Studio text editor for T4 templates.

DecoratorPotSolution Decorator.tt is a T4 template that generates a decorator class. It defines the following parameters:

  • InterfaceAssemblyName - path and file name of the assembly that contains component interface definition.
  • InterfaceTypeName - full name (including namespace) of the component interface.
  • ClassName - name of the decorator class to be generated.
  • BaseClassName - name of the decorator’s base class.
  • NamespaceName - namespace of the decorator class.

Generated decorator has a public constructor that takes a component instance (defined by InterfaceTypeName) as a parameter. The template uses Introspection to generate a decorator class that implements all interface methods by calling the component. Template contains placeholders marked by TODO comments where you can place place actual decorator code to be called before and after the component call in each method.

The template does not allow the decorator class and the interface to be in the same assembly. Interface must be already compiled before decorator class can be generated.

Usage

In order to use the attached template in your project, follow the steps below.

  • Add a copy of Decorator.tt to your project and modify it to produce the desired decorator code. In particular, replace TODO comments with actual code, modify constructor if necessary, etc. Verify template output by looking at Decorator.cs, which will be automatically generated each time you save the template. When done, clear Custom Tool property of Decorator.tt to avoid generating Decorator.cs.
  • Add a new text file with .tt extension to the project. Name of the file should match the name of the actual decorator class. Modify this file to use Decorator.tt and provide values for template parameters. For example, TestDecorator.tt contains the following code:
    <#
      NamespaceName = “ConsoleApplication”;
      ClassName = “TestDecorator”;
      BaseClassName = “Object”;
      InterfaceAssemblyName = @”..\ComponentLibrary\bin\Debug\ComponentLibrary.dll”;
      InterfaceTypeName = “ComponentLibrary.IMyComponent”;
    #>
    <#@ include file=Decorator.tt#>
  • Note that InterfaceAssemblyName specifies a path relative to the location of the template file. This template generates the following code in TestDecorator.cs:
    // <autogenerated>
    //   This file was generated from T4 template TestDecorator.tt.
    // </autogenerated>
    using System; 
    
    namespace ConsoleApplication
    {
        public class TestDecorator: Object, ComponentLibrary.IMyComponent
        {
            private readonly ComponentLibrary.IMyComponent _component; 
    
            public TestDecorator(ComponentLibrary.IMyComponent component)
            {
                if (component == null)
                    throw new ArgumentNullException(“component”);
                _component = component;
            } 
    
            #region IMyComponent methods 
    
            public bool TestBooleanMethod()
            {
                bool result;
                // TODO: your code before method call
                result = _component.TestBooleanMethod();
                // TODO: your code after method call
                return result;
            } 
    
            public void TestVoidMethod(string s, ref string r, out string o)
            {
                // TODO: your code before method call
                _component.TestVoidMethod(s, ref r, out o);
                // TODO: your code after method call
            } 
    
            #endregion
        }
    }

  • Repeat previous step for each concrete decorator you need to generate.
  • After changing/recompiling the component interface, you can regenerate a particular decorator by right-clicking it in Solution Explorer and selecting Run Custom Tool from the context menu. You can regenerate all decorators in a project by clicking Transform All Templates button in Solution Explorer.

Known issues

Current version of the attached template contains the following limitations.

  • Decoration of properties and events is not supported.
  • Decorator boilerplate is embedded Decorator.tt file with the code. To reduce code duplication, consider extracting code into a separate .tt file if you need to have multiple decorator templates. This is not necessary if you simply need to generate multiple decorators from the same template.

About T4

T4 (Text Template Transformation Toolkit) is a template-based code generation engine. It is available in Visual Studio 2008 and as a download in DSL and GAT toolkits for Visual Studio 2005. T4 engine allows you to use ASP.NET-like template syntax to generate C#, T-SQL, XML or any other text files.

For more information about T4, check out my previous article.

Download

T4 templates and source code used in this article are available for download here.


Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

Fantastic example of T4 templates. I have used a modified version of your T4 text template to generate WCF proxy classes which I wrote about on my blog at:
http://www.hexadecimal.se/2009/03/09/WritingRockSolidCodeWithCodeContracts.aspx

Credits and a reference to this blog post is mentioned in my template.

What is (was) wrong with the description on wikipedia? Just being curious ;-)

I don’t remember exactly, only that it didn’t match with the GoF definition. It looks good now.

Thanks for the information you provided.

Is it possible to obtain the intellisense engine to read the properties from an interface?
Your solution requires the assembly to be build which is less desirable

Yes, you can use CodeModel to read the interface directly from a C# file. EnumSqlView is a code generator built using this approach.

Hi,
You talk about using Microsoft.Cci.dll (Microsoft.FxCop.Sdk) which comes with FxCop 1.36.
Does the license of FxCop allow to use this library out of FxCop context ?
(we may use it for code generation on a build server…)

Thank you per advance for your response,
Sylvain.

I’m not suggesting redistributing it. If FxCop is installed on your build server, I don’t see why you would not be allowed to use the dll.

[…] some tinkering, I managed (with the help of Google and the T4 guru Oleg Sych) create a text template that would take in the shared class library housing the interface and code […]

I using your example to generate DTO objects based on a Model assembly. I loop the Types for a given Namespace (from assembly.GetNamespaceList()) and then loop Members of NodeType.Property for the given TypeNode of NodeType.Class. It appears the calling Property.GetPropertyInfo() is locking the assembly with reflection. I don’t see how (largely do to a lack of documentation) on how to get the info I need with a PropertyInfo object. You?

When using reflection in T4, what are your thoughts about reading the raw bytes of the assembly into memory in order to load and reflect on it. For example:

byte[] bytes = null;
using(FileStream fs = File.OpenRead(”foo.dll”))
{
bytes = new byte[fs.Length];
fs.Read(bytes, 0, bytes.Length);
}

Assembly assembly = Assembly.ReflectionOnlyLoad(bytes);

This *seems* to side-step the “locking” issue, but I’d love to get your thoughts on the approach.

Hi,

Is there some nice way how to get around this limitation? “Interface must be already compiled before decorator class can be generated.”

I’d like to avoid need to do 2 operations when I change something in interface (compile and rerun template).

I’m thinking about invoking project build somewhere at the beginning of the template. Another approach can be execute template in some way from the post build action.

Which approach do you think will be better or should I do something completely different?

Thanks in advance for response,
Marian

Marian,

Is this what you are looking for?
http://www.codeproject.com/KB/codegen/WCFWrapperUsingT4.aspx#

[…] some tinkering, I managed (with the help of Google and the T4 guru Oleg Sych) create a text template that would take in the shared class library housing the interface and code […]