Part 3: Detour hacking parameters for T4 Templates (VS 2008)
I decided to use T4 Templates instead of my home grown code generation techniques. I didn’t want to spend several pages on my way; besides, I probably need to get with the times and play with the same toys as the cool kids.
This post is to describe a punt. I couldn’t find a way to pass parameters to a VS 2008 .tt file. I know that VS 2010 has the parameter directive, but I don’t have VS 2010, so thus the hack below.
TTCommucator.GetParams
Since I can reference .NET assemblies, I am storing my parameters as a Dictionary to a file outside of the template and then reading it in from inside the template.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace TTCommucator { /// <summary> /// Used to pass parameters to a T4 Template /// </summary> public class GetParams { private const char SEPERATOR_CHAR = '|'; private string _fileName; /// <summary> /// The Dictionary containing the Parameters /// </summary> public Dictionary<string, string> CurrentParameters { get; set; } /// <summary> /// Creates a GetParmas with a Filename /// </summary> /// <param name="fileName">The name of the file to store the parameters</param> /// <remarks>Both sides of the transaction must agree on file name</remarks> public GetParams(string fileName) { CurrentParameters = new Dictionary<string, string>(); this._fileName = fileName; } /// <summary> /// Loads the data from the file into the Dictionary CurrentParameters /// </summary> public void Load() { CurrentParameters = new Dictionary<string, string>(); string line; using (TextReader tr = new StreamReader(_fileName)) { while ((line = tr.ReadLine()) != null) { string[] parts = line.Split(new char[] { SEPERATOR_CHAR }, 2); if (parts.Count() > 1) CurrentParameters.Add(parts[0], parts[1]); } tr.Close(); } } /// <summary> /// Saves the data in the Dictionary CurrentParameters to the file /// </summary> public void Save() { using (TextWriter tw = new StreamWriter(_fileName)) { foreach (var item in CurrentParameters) { tw.WriteLine(item.Key + SEPERATOR_CHAR + item.Value); } tw.Close(); } } } }
Test Template
I created a simple template to demonstrate that I can receive parameters using my GetParams class. Note that I use VB.NET for code in my templates. Since I usually generate C# code, I find it easier to read templates when the template code isn’t C#, besides “<#}#>” is downright nasty looking.
<#@ template debug="true" hostspecific="true" language="VB" #> <#@ assembly name="C:\Projects\TTCommucator\TTCommucator.dll" #> <#@ import namespace="TTCommucator" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".txt" #> <# ' Load the Parameters here: Dim parms As new GetParams(Host.ResolvePath("setting.txt")) parms.Load() #> Values in parms: <#For Each item As KeyValuePair(Of String, String) In parms.CurrentSettings#> <#=item.Key#> = <#=item.Value#> <#Next#>
Test Console Application
Here is a simple program to test with. It doesn’t do anything more than necessary. NOTE: You will need to install Visual Studio 2008 SDK v1.1 to run this code:
using System; using System.IO; using Microsoft.VisualStudio.TextTemplating; using TTCommucator; namespace TTCommunicatorConsole { class Program { static void Main(string[] args) { // Set up some parameters: var parms = new GetParams("setting.txt"); parms.CurrentParameters.Add("Greeting", "Hello World!"); parms.CurrentParameters.Add("Run Time", DateTime.Now.ToString()); parms.Save(); string templateFileName = "SimpleTemplate.tt"; // Set up host and engine CustomCmdLineHost host = new CustomCmdLineHost(); Engine engine = new Engine(); host.SetTemplateFile(templateFileName); //Read the text template. string input = File.ReadAllText(templateFileName); //Transform the text template. string output = engine.ProcessTemplate(input, host); // Show your work Console.WriteLine(output); // Wait for me Console.ReadKey(); } } }
Host Class
I lifted this class from MSDN and removed the comments to save space. You can learn more about it by going here. NOTE: The MSDN code doesn't include the Serializable attribute, but I needed to add it to avoid errors. You will need to include a reference to Microsoft.VisualStudio.TextTemplating.
using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Text; using Microsoft.VisualStudio.TextTemplating; namespace TTCommucator { [Serializable] public class CustomCmdLineHost : ITextTemplatingEngineHost { internal string _templateFile; public string TemplateFile { get { return _templateFile; } } private string _fileExtension = ".txt"; public string FileExtension { get { return _fileExtension; } } private Encoding _fileEncoding = Encoding.UTF8; public Encoding FileEncoding { get { return _fileEncoding; } } private CompilerErrorCollection _errors; public CompilerErrorCollection Errors { get { return _errors; } } public IListStandardAssemblyReferences { get { return new string[] { typeof(System.Uri).Assembly.Location }; } } public IList StandardImports { get { return new string[] { "System" }; } } public bool LoadIncludeText(string requestFileName, out string content, out string location) { content = string.Empty; location = string.Empty; if (File.Exists(requestFileName)) { content = File.ReadAllText(requestFileName); return true; } else return false; } public object GetHostOption(string optionName) { object returnObject; switch (optionName) { case "CacheAssemblies": returnObject = true; break; default: returnObject = null; break; } return returnObject; } public string ResolveAssemblyReference(string assemblyReference) { if (File.Exists(assemblyReference)) return assemblyReference; string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), assemblyReference); if (File.Exists(candidate)) return candidate; return string.Empty; } public Type ResolveDirectiveProcessor(string processorName) { if (string.Compare(processorName, "XYZ", StringComparison.OrdinalIgnoreCase) == 0) { } throw new Exception("Directive Processor not found"); } public string ResolvePath(string fileName) { if (fileName == null) throw new ArgumentNullException("the file name cannot be null"); if (File.Exists(fileName)) return fileName; string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), fileName); if (File.Exists(candidate)) return candidate; return fileName; } public string ResolveParameterValue(string directiveId, string processorName, string parameterName) { if (directiveId == null) throw new ArgumentNullException("the directiveId cannot be null"); if (processorName == null) throw new ArgumentNullException("the processorName cannot be null"); if (parameterName == null) throw new ArgumentNullException("the parameterName cannot be null"); return string.Empty; } public void SetFileExtension(string extension) { _fileExtension = extension; } public void SetTemplateFile(string templateFile) { _templateFile = templateFile; } public void SetOutputEncoding(Encoding encoding, bool fromOutputDirective) { _fileEncoding = encoding; } public void LogErrors(CompilerErrorCollection errors) { _errors = errors; } public AppDomain ProvideTemplatingAppDomain(string content) { return AppDomain.CreateDomain("Generation App Domain"); } } }
Generating a Nullable ADO.NET DataSet Wrapper: The Series
- Part 1: The Target Code
- Part 2: Describing the DataSet
- Part 3: Detour hacking parameters for T4 Templates (VS 2008)
- Part 4: The Template and putting it all together
No comments:
Post a Comment