从T4模板引擎到自己的代码生成器(1)

T4模板才接触半个月,但最不满的是它不能方便的脱离vs stuido。如果我要自己写代码生成器,怎么办?

园子里讲T4模板的不少,但讲如何在自己的程序中使用引擎的很少。查了很久的资料才知道,这东西叫:Text Template Host.

知道名字,资料就好了。接下来就可以试着建立一个自己的Host.

(1)写一个最简单的tt文件:

 

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>

using System;public class HelloWord{   
 static void Main()    {   
 
  }

}

 

(2)新建一个项目,放个按钮,写点击代码:

 TextCode host = new TextCode();
            //host.Namespace = txtNamespace.Text;
            
//host.Classname = txtTablemame.Text;
            host.FileExtension = ".cs";
            host.TemplateFile = "Test.tt";

            Microsoft.VisualStudio.TextTemplating.Engine engine = new Microsoft.VisualStudio.TextTemplating.Engine();
            string content = "";
            string input = "";
            string output = "";
            input = System.IO.File.ReadAllText(host.TemplateFile);
            output = engine.ProcessTemplate(input, host);

            string outputname ="newTest" + host.FileExtension;

            System.IO.File.WriteAllText(outputname, output);
           
            string strError="";
            foreach (CompilerError error in host.Errors)
            {
                strError += error.ToString();
            }
            MessageBox.Show(strError);

 

等等,一堆错误。。。那个TextCode 是什么东西?

(3) TextCode是自己写的一个类,它就是一个 host,需要实现一个TextTemplating.ITextTemplatingEngineHost接口。这个接口非常复杂,我找了一个比较完整的代码来解释。

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Data;
using System.CodeDom.Compiler;

namespace TestForT4.Code
{
    [Serializable]
    class TextCode : Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost
    {

        //the path and file name of the text template that is being processed
        
//---------------------------------------------------------------------
        internal string TemplateFileValue;
        public string TemplateFile
        {
            get { return TemplateFileValue; }
        }


        //This will be the extension of the generated text output file.
        
//The host can provide a default by setting the value of the field here.
        
//The engine can change this value based on the optional output directive
        
//if the user specifies it in the text template.
        
//---------------------------------------------------------------------
        private string fileExtensionValue = ".txt";
        public string FileExtension
        {
            get { return fileExtensionValue; }
        }


        //This will be the encoding of the generated text output file.
        
//The host can provide a default by setting the value of the field here.
        
//The engine can change this value based on the optional output directive
        
//if the user specifies it in the text template.
        
//---------------------------------------------------------------------
        private Encoding fileEncodingValue = Encoding.UTF8;
        public Encoding FileEncoding
        {
            get { return fileEncodingValue; }
        }


        //These are the errors that occur when the engine processes a template.
        
//The engine passes the errors to the host when it is done processing,
        
//and the host can decide how to display them. For example, the host 
        
//can display the errors in the UI or write them to a file.
        
//---------------------------------------------------------------------
        private CompilerErrorCollection errorsValue;
        public CompilerErrorCollection Errors
        {
            get { return errorsValue; }
        }


        //The host can provide standard assembly references.
        
//The engine will use these references when compiling and
        
//executing the generated transformation class.
        
//--------------------------------------------------------------
        public IList<string> StandardAssemblyReferences
        {
            get
            {
                return new string[]
                {
                    //If this host searches standard paths and the GAC,
                    
//we can specify the assembly name like this.
                    
//---------------------------------------------------------
                    
//"System"

                    
//Because this host only resolves assemblies from the 
                    
//fully qualified path and name of the assembly,
                    
//this is a quick way to get the code to give us the
                    
//fully qualified path and name of the System assembly.
                    
//---------------------------------------------------------
                    typeof(System.Uri).Assembly.Location
                };
            }
        }


        //The host can provide standard imports or using statements.
        
//The engine will add these statements to the generated 
        
//transformation class.
        
//--------------------------------------------------------------
        public IList<string> StandardImports
        {
            get
            {
                return new string[]
                {
                    "System"
                };
            }
        }


        //The engine calls this method based on the optional include directive
        
//if the user has specified it in the text template.
        
//This method can be called 0, 1, or more times.
        
//---------------------------------------------------------------------
        
//The included text is returned in the context parameter.
        
//If the host searches the registry for the location of include files,
        
//or if the host searches multiple locations by default, the host can
        
//return the final path of the include file in the location parameter.
        
//---------------------------------------------------------------------
        public bool LoadIncludeText(string requestFileName, out string content, out string location)
        {
            content = System.String.Empty;
            location = System.String.Empty;
       
            //If the argument is the fully qualified path of an existing file,
            
//then we are done.
            
//----------------------------------------------------------------
            if (File.Exists(requestFileName))
            {
                content = File.ReadAllText(requestFileName);
                return true;
            }

            //This can be customized to search specific paths for the file.
            
//This can be customized to accept paths to search as command line
            
//arguments.
            
//----------------------------------------------------------------
            else
            {
                return false;
            }
        }


        //Passes in the name of a service. If you have that service, you need to 
        
//pass back a pointer to it. Otherwise, you need to pass back NULL to 
        
//indicate that you have no knowledge of that service.
        
//--------------------------------------------------------------------
        public object GetHostOption(string optionName)
        {
        object returnObject;
        switch (optionName)
        {
        case "CacheAssemblies":
                    returnObject = true;
     break;
        default:
        returnObject = null;
        break;
        }
        return returnObject;
        }


        //The engine calls this method to resolve assembly references used in
        
//the generated transformation class project and for the optional 
        
//assembly directive if the user has specified it in the text template.
        
//This method can be called 0, 1, or more times.
        
//---------------------------------------------------------------------
        public string ResolveAssemblyReference(string assemblyReference)
        {
            //If the argument is the fully qualified path of an existing file,
            
//then we are done. (This does not do any work.)
            
//----------------------------------------------------------------
            if (File.Exists(assemblyReference))
            {
                return assemblyReference;
            }

            //Maybe the assembly is in the same folder as the text template that 
            
//called the directive.
            
//----------------------------------------------------------------
            string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), assemblyReference);
            if (File.Exists(candidate))
            {
                return candidate;
            }

            //This can be customized to search specific paths for the file
            
//or to search the GAC.
            
//----------------------------------------------------------------

            
//This can be customized to accept paths to search as command line
            
//arguments.
            
//----------------------------------------------------------------

            
//If we cannot do better, return the original file name.
            return "";
        }


        //The engine calls this method based on the directives the user has 
        
//specified in the text template.
        
//This method can be called 0, 1, or more times.
        
//---------------------------------------------------------------------
        public Type ResolveDirectiveProcessor(string processorName)
        {
            //This host will not resolve any specific processors.

            
//Check the processor name, and if it is the name of a processor the 
            
//host wants to support, return the type of the processor.
            
//---------------------------------------------------------------------
            if (string.Compare(processorName, "XYZ", StringComparison.OrdinalIgnoreCase) == 0)
            {
                //return typeof();
            }

            //This can be customized to search specific paths for the file
            
//or to search the GAC

            
//If the directive processor cannot be found, throw an error.
            throw new Exception("Directive Processor not found");
        }


        //A directive processor can call this method if a file name does not 
        
//have a path.
        
//The host can attempt to provide path information by searching 
        
//specific paths for the file and returning the file and path if found.
        
//This method can be called 0, 1, or more times.
        
//---------------------------------------------------------------------
        public string ResolvePath(string fileName)
        {
            if (fileName == null)
            {
                throw new ArgumentNullException("the file name cannot be null");
            }

            //If the argument is the fully qualified path of an existing file,
            
//then we are done
            
//----------------------------------------------------------------
            if (File.Exists(fileName))
            {
                return fileName;
            }

            //Maybe the file is in the same folder as the text template that 
            
//called the directive.
            
//----------------------------------------------------------------
            string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), fileName);
            if (File.Exists(candidate))
            {
                return candidate;
            }

            //Look more places.
            
//----------------------------------------------------------------
            
//More code can go here...

            
//If we cannot do better, return the original file name.
            return fileName;
        }


        //If a call to a directive in a text template does not provide a value
        
//for a required parameter, the directive processor can try to get it
        
//from the host by calling this method.
        
//This method can be called 0, 1, or more times.
        
//---------------------------------------------------------------------
        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");
            }

            //Code to provide "hard-coded" parameter values goes here.
            
//This code depends on the directive processors this host will interact with.

            
//If we cannot do better, return the empty string.
            return String.Empty;
        }


        //The engine calls this method to change the extension of the 
        
//generated text output file based on the optional output directive 
        
//if the user specifies it in the text template.
        
//---------------------------------------------------------------------
        public void SetFileExtension(string extension)
        {
            //The parameter extension has a '.' in front of it already.
            
//--------------------------------------------------------
            fileExtensionValue = extension;
        }


        //The engine calls this method to change the encoding of the 
        
//generated text output file based on the optional output directive 
        
//if the user specifies it in the text template.
        
//----------------------------------------------------------------------
        public void SetOutputEncoding(System.Text.Encoding encoding, bool fromOutputDirective)
        {
            fileEncodingValue = encoding;
        }


        //The engine calls this method when it is done processing a text
        
//template to pass any errors that occurred to the host.
        
//The host can decide how to display them.
        
//---------------------------------------------------------------------
        public void LogErrors(CompilerErrorCollection errors)
        {
            errorsValue = errors;
        }


        //This is the application domain that is used to compile and run
        
//the generated transformation class to create the generated text output.
        
//----------------------------------------------------------------------
        public AppDomain ProvideTemplatingAppDomain(string content)
        {
            //This host will provide a new application domain each time the 
            
//engine processes a text template.
            
//-------------------------------------------------------------
            return AppDomain.CreateDomain("Generation App Domain");

            //This could be changed to return the current appdomain, but new 
            
//assemblies are loaded into this AppDomain on a regular basis.
            
//If the AppDomain lasts too long, it will grow indefintely, 
            
//which might be regarded as a leak.

            
//This could be customized to cache the application domain for 
            
//a certain number of text template generations (for example, 10).

            
//This could be customized based on the contents of the text 
            
//template, which are provided as a parameter for that purpose.
        }

    }
}

(4)最后呢。。。运行它!不过,如果你要想脱离vs。最好加上它的运行的dll.vs2008中叫Microsoft.VisualStudio.TextTemplating.dll。至于2010呢,两个文件,自己 gg一下吧。

运行,如果最后的提示是空白,那么就对啦,如果不对。请对着提示找错吧。

 

posted @ 2011-09-22 16:49  minttang  阅读(2851)  评论(5编辑  收藏  举报