Breaking changes to T4 Text Templating in the September DSL Tools CTP

We've made a couple of breaking changes to the interfaces exposed by our text templating engine in this CTP in the area of directive processors.

 

These won't affect regular DSL Tools users, as after going through the May-September migration process your code will just work.  (Note, all May CTP DSL Tools users MUST migrate their previous projects to use the September CTP successfully with them).

 

However, for those adventurous souls who've written their own directive processors (yes, you DuncanP), there may well be a little work to do.

 

The problem we've addressed occurs when you use multiple custom directives in a single template, processed by one or more directive processors.

 

Each of these directives typically needs a way to contribute some code to the generated transformation class that T4 produces and executes in order to spit out the final text stream.
For example, multiple directive processors need a way to add one method call for each directive and for this method to be called at a predictable time.  This wasn't previously easy to achieve.

 

We've added two abstract methods to the DirectiveProcessor base class...

 

  /// <summary>
  /// Get the code to contribute to the body of the initialize method of the generated
  /// template processing class as a consequence of the most recent run.
  /// This code will run before the base class' Initialize method
  /// </summary>
  public abstract string GetPreInitializationCodeForProcessingRun();

 

  /// <summary>
  /// Get the code to contribute to the body of the initialize method of the generated
  /// template processing class as a consequence of the most recent run.
  /// This code will run after the base class' Initialize method
  /// </summary>
  public abstract string GetPostInitializationCodeForProcessingRun();

 

These methods supply code to be added to the body of the Transformation class' Initialize method.  As you'd expect from the names, code from the first method get added before the call to base.Initialize() and code from the second gets added after the call.  This allows you to set up some data in the "Pre" code, process that data in a custom TextTransformation base class and then do something else with it in the "Post" code.

 

All this is a bit abstract.  To pick a concrete example, here's pseudo-code for how the DSL Tools' supplied directive processors use this functionality:

 

class CustomTransformBase
{
   protected List<SubStore> domainModelsToLoad = new List<SubStore>();

 

   public void Initialize()
   {
      Store.LoadSubStores(MakeUnique(this.domainModelsToLoad));
   }
}

 

class GeneratedTransformationClass
{
   public void Initialize()
   {
        domainModelsToLoad.Add("RequiredDomainModel1forDirective1");  // Added by GetPreInitializationCode
        domainModelsToLoad.Add("RequiredDomainModel2forDirective1");  // Added by GetPreInitializationCode
        domainModelsToLoad.Add("RequiredDomainModel1forDirective2");  // Added by GetPreInitializationCode
        domainModelsToLoad.Add("RequiredDomainModel2forDirective2");  // Added by GetPreInitializationCode
        base.Initialize();
        Store.LoadModel("ModelSpecifiedByDirective1");  // Added by GetPostInitializationCode
        Store.LoadModel("ModelSpecifiedByDirective2");  // Added by GetPostInitializationCode
}

 

For those of you who build on top of the RequiresProvidesDirectiveProcessor, this cascades through to two new methods there to do the same sort of thing…

 

  /// <summary>
  /// Method for derived classes to contribute additively to initialization code for the TextTransformation generated class.
  /// </summary>
  /// <remarks>
  /// Additive code is useful where there are multiple directive processor instances each needing to have some instance-specific initialization.
  /// As GenerateTransformCode can add methods, matching initialization code is often required to call those methods.
  /// This code will be added before the call to the base class.
  /// </remarks>
  protected abstract void GeneratePreInitializationCode(string directiveName, StringBuilder codeBuffer, CodeDomProvider languageProvider, IDictionary<string, string> requiresArguments, IDictionary<string, string> providesArguments);

 

  /// <summary>
  /// Method for derived classes to contribute additively to initialization code for the TextTransformation generated class.
  /// </summary>
  /// <remarks>
  /// Additive code is useful where there are multiple directive processor instances each needing to have some instance-specific initialization.
  /// As GenerateTransformCode can add methods, matching initialization code is often required to call those methods.
  /// This code will be added after the call to the base class.
  /// </remarks>
  protected abstract void GeneratePostInitializationCode(string directiveName, StringBuilder codeBuffer, CodeDomProvider languageProvider, IDictionary<string, string> requiresArguments, IDictionary<string, string> providesArguments);

 

Next time, I'll discuss how this affects the ModelingTextTransformation base class which has changed somewhat because of these changes.