好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

基于T4模板引擎生成静态网站(CMS)

基于T4模板引擎生成静态网站(CMS)

缘由

用Net技术生成纯静态网站目前市面上的技术貌似不是很多,要么就是一些大公司的项目。相比于Php语言来说,基于Php语言的CMS系统就有很多了,并且模板解析技术也已经比较成熟了。模板解析引擎一直是一个核心的问题,曾经我也尝试了好多种办法来间接的实现模板解析,但都不能完美的解决面临的问题,相信很多使用Net做网站的朋友也希望有一套像Php那样的CMS系统。直到有一天公司组织微软的专家过来培训让我了解到了VS10在代码生成方面所呈现出的优越表现,让我联想到了这套引擎能不能用于其他的方面应用。。。。(写此文的目的为记录日志,所以大牛的话可以飘过了。)

一、所需准备:

本文介绍的实现方法将以C#语言为实现。 实验环境是VS10+sp1+VSsdk 需要引入程序集:Microsoft.VisualStudio.TextTemplating.10.0.dll  和 Microsoft.VisualStudio.TextTemplating.Interfaces.10.0.dll 所需的Net framework平台是 4,还在用2  || 3  ||  3.5的朋友赶紧的更新一下吧!

下载地址(vssdk): http://www.microsoft.com/en-us/download/details.aspx?id=2680

已知问题:如果你的VS已经打过了SP1,那么安装VSSDK时会出现一个错误,需要手工更改一下注册表,需要将注册表中的某个键值1更改为0。具体的详细设置办法Google一下就有答案了。

二、技术实现

2.1实现思路

用T4做为模板文件的解析引擎,将数据、解析引擎、静态文件模板、控制器分别单独出来,这样的话程序员只用写一套框架程序就行了,框架写好之后剩下的就是写静态文件模板了。关于T4模板解析引擎功能的强大之处,可以参考MSDN的官方资料。()像这些问题如:模板嵌套子模板、可编程等问题,早已被T4完美的解决了。

2.2实现代码

要实现我们的自定义主机解析引擎,首先要添加一个实现了ITextTemplatingEngineHost 和  ITextTemplatingSessionHost  接口的类,代码如下(时间长了,我也忘记是从哪里Copy过来的代码了,应该是MSDN吧):

首先创建一个CustomCmdLineHost类,添加如下应用:

    1:   using  System.IO;
    2:   using  System.CodeDom.Compiler;
    3:   using  Microsoft.VisualStudio.TextTemplating;

然后实现ITextTemplatingEngineHost 和  ITextTemplatingSessionHost  接口,代码如下:

    1:   using  System;
    2:   using  System.Collections.Generic;
    3:   using  System.Linq;
    4:   using  System.Text;
    5:   using  System.IO;
    6:   using  System.CodeDom.Compiler;
    7:   using  Microsoft.VisualStudio.TextTemplating;
    8:   
    9:   namespace  Smart.TextTemplating
   10:  {
   11:       public   class  CustomCmdLineHost : ITextTemplatingEngineHost, ITextTemplatingSessionHost
   12:      {
   13:           //the path and file name of the text template that is being processed 
   14:           //--------------------------------------------------------------------- 
   15:           public   string  TemplateFileValue;
   16:           public   string  TemplateFile
   17:          {
   18:              get {  return  TemplateFileValue; }
   19:          }
   20:           //This will be the extension of the generated text output file. 
   21:           //The host can provide a default by setting the value of the field here. 
   22:           //The engine can change this value based on the optional output directive 
   23:           //if the user specifies it in the text template. 
   24:           //--------------------------------------------------------------------- 
   25:           private   string  fileExtensionValue =  ".txt" ;
   26:           public   string  FileExtension
   27:          {
   28:              get {  return  fileExtensionValue; }
   29:          }
   30:           //This will be the encoding of the generated text output file. 
   31:           //The host can provide a default by setting the value of the field here. 
   32:           //The engine can change this value based on the optional output directive 
   33:           //if the user specifies it in the text template. 
   34:           //--------------------------------------------------------------------- 
   35:           private  Encoding fileEncodingValue = Encoding.UTF8;
   36:           public  Encoding FileEncoding
   37:          {
   38:              get {  return  fileEncodingValue; }
   39:          }
   40:           //These are the errors that occur when the engine processes a template. 
   41:           //The engine passes the errors to the host when it is done processing, 
   42:           //and the host can decide how to display them. For example, the host  
   43:           //can display the errors in the UI or write them to a file. 
   44:           //--------------------------------------------------------------------- 
   45:           private  CompilerErrorCollection errorsValue;
   46:           public  CompilerErrorCollection Errors
   47:          {
   48:              get {  return  errorsValue; }
   49:          }
   50:           //The host can provide standard assembly references. 
   51:           //The engine will use these references when compiling and 
   52:           //executing the generated transformation class. 
   53:           //-------------------------------------------------------------- 
   54:           public  IList< string > StandardAssemblyReferences
   55:          {
   56:              get
   57:              {
   58:                   return   new   string []
   59:                  {
   60:                       //If this host searches standard paths and the GAC, 
   61:                       //we can specify the assembly name like this. 
   62:                       //--------------------------------------------------------- 
   63:                       //"System" 
   64:   
   65:                       //Because this host only resolves assemblies from the  
   66:                       //fully qualified path and name of the assembly, 
   67:                       //this is a quick way to get the code to give us the 
   68:                       //fully qualified path and name of the System assembly. 
   69:                       //--------------------------------------------------------- 
   70:                       typeof (System.Uri).Assembly.Location
   71:                  };
   72:              }
   73:          }
   74:           //The host can provide standard imports or using statements. 
   75:           //The engine will add these statements to the generated  
   76:           //transformation class. 
   77:           //-------------------------------------------------------------- 
   78:           public  IList< string > StandardImports
   79:          {
   80:              get
   81:              {
   82:                   return   new   string []
   83:                  {
   84:                       "System" 
   85:                  };
   86:              }
   87:          }
   88:           //The engine calls this method based on the optional include directive 
   89:           //if the user has specified it in the text template. 
   90:           //This method can be called 0, 1, or more times. 
   91:           //--------------------------------------------------------------------- 
   92:           //The included text is returned in the context parameter. 
   93:           //If the host searches the registry for the location of include files, 
   94:           //or if the host searches multiple locations by default, the host can 
   95:           //return the final path of the include file in the location parameter. 
   96:           //--------------------------------------------------------------------- 
   97:           public   bool  LoadIncludeText( string  requestFileName,  out   string  content,  out   string  location)
   98:          {
   99:              content = System.String.Empty;
  100:              location = System.String.Empty;
  101:   
  102:               //If the argument is the fully qualified path of an existing file, 
  103:               //then we are done. 
  104:               //---------------------------------------------------------------- 
  105:               if  (File.Exists(requestFileName))
  106:              {
  107:                  content = File.ReadAllText(requestFileName);
  108:                   return   true ;
  109:              }
  110:               //This can be customized to search specific paths for the file. 
  111:               //This can be customized to accept paths to search as command line 
  112:               //arguments. 
  113:               //---------------------------------------------------------------- 
  114:               else 
  115:              {
  116:                   return   false ;
  117:              }
  118:          }
  119:           //Called by the Engine to enquire about  
  120:           //the processing options you require.  
  121:           //If you recognize that option, return an  
  122:           //appropriate value.  
  123:           //Otherwise, pass back NULL. 
  124:           //-------------------------------------------------------------------- 
  125:           public   object  GetHostOption( string  optionName)
  126:          {
  127:               object  returnObject;
  128:               switch  (optionName)
  129:              {
  130:                   case   "CacheAssemblies" :
  131:                      returnObject =  true ;
  132:                       break ;
  133:                   default :
  134:                      returnObject =  null ;
  135:                       break ;
  136:              }
  137:               return  returnObject;
  138:          }
  139:           //The engine calls this method to resolve assembly references used in 
  140:           //the generated transformation class project and for the optional  
  141:           //assembly directive if the user has specified it in the text template. 
  142:           //This method can be called 0, 1, or more times. 
  143:           //--------------------------------------------------------------------- 
  144:           public   string  ResolveAssemblyReference( string  assemblyReference)
  145:          {
  146:               //If the argument is the fully qualified path of an existing file, 
  147:               //then we are done. (This does not do any work.) 
  148:               //---------------------------------------------------------------- 
  149:               if  (File.Exists(assemblyReference))
  150:              {
  151:                   return  assemblyReference;
  152:              }
  153:               //Maybe the assembly is in the same folder as the text template that  
  154:               //called the directive. 
  155:               //---------------------------------------------------------------- 
  156:               string  candidate = Path.Combine(Path.GetDirectoryName( this .TemplateFile), assemblyReference);
  157:               if  (File.Exists(candidate))
  158:              {
  159:                   return  candidate;
  160:              }
  161:               //This can be customized to search specific paths for the file 
  162:               //or to search the GAC. 
  163:               //---------------------------------------------------------------- 
  164:               //This can be customized to accept paths to search as command line 
  165:               //arguments. 
  166:               //---------------------------------------------------------------- 
  167:               //If we cannot do better, return the original file name. 
  168:               return   "" ;
  169:          }
  170:           //The engine calls this method based on the directives the user has  
  171:           //specified in the text template. 
  172:           //This method can be called 0, 1, or more times. 
  173:           //--------------------------------------------------------------------- 
  174:           public  Type ResolveDirectiveProcessor( string  processorName)
  175:          {
  176:               //This host will not resolve any specific processors. 
  177:               //Check the processor name, and if it is the name of a processor the  
  178:               //host wants to support, return the type of the processor. 
  179:               //--------------------------------------------------------------------- 
  180:               if  ( string .Compare(processorName,  "XYZ" , StringComparison.OrdinalIgnoreCase) == 0)
  181:              {
  182:                   //return typeof(); 
  183:              }
  184:               //This can be customized to search specific paths for the file 
  185:               //or to search the GAC 
  186:               //If the directive processor cannot be found, throw an error. 
  187:               throw   new  Exception( "Directive Processor not found" );
  188:          }
  189:           //A directive processor can call this method if a file name does not  
  190:           //have a path. 
  191:           //The host can attempt to provide path information by searching  
  192:           //specific paths for the file and returning the file and path if found. 
  193:           //This method can be called 0, 1, or more times. 
  194:           //--------------------------------------------------------------------- 
  195:           public   string  ResolvePath( string  fileName)
  196:          {
  197:               if  (fileName ==  null )
  198:              {
  199:                   throw   new  ArgumentNullException( "the file name cannot be null" );
  200:              }
  201:               //If the argument is the fully qualified path of an existing file, 
  202:               //then we are done 
  203:               //---------------------------------------------------------------- 
  204:               if  (File.Exists(fileName))
  205:              {
  206:                   return  fileName;
  207:              }
  208:               //Maybe the file is in the same folder as the text template that  
  209:               //called the directive. 
  210:               //---------------------------------------------------------------- 
  211:               string  candidate = Path.Combine(Path.GetDirectoryName( this .TemplateFile), fileName);
  212:               if  (File.Exists(candidate))
  213:              {
  214:                   return  candidate;
  215:              }
  216:               //Look more places. 
  217:               //---------------------------------------------------------------- 
  218:               //More code can go here... 
  219:               //If we cannot do better, return the original file name. 
  220:               return  fileName;
  221:          }
  222:           //If a call to a directive in a text template does not provide a value 
  223:           //for a required parameter, the directive processor can try to get it 
  224:           //from the host by calling this method. 
  225:           //This method can be called 0, 1, or more times. 
  226:           //--------------------------------------------------------------------- 
  227:           public   string  ResolveParameterValue( string  directiveId,  string  processorName,  string  parameterName)
  228:          {
  229:               if  (directiveId ==  null )
  230:              {
  231:                   throw   new  ArgumentNullException( "the directiveId cannot be null" );
  232:              }
  233:               if  (processorName ==  null )
  234:              {
  235:                   throw   new  ArgumentNullException( "the processorName cannot be null" );
  236:              }
  237:               if  (parameterName ==  null )
  238:              {
  239:                   throw   new  ArgumentNullException( "the parameterName cannot be null" );
  240:              }
  241:               //Code to provide "hard-coded" parameter values goes here. 
  242:               //This code depends on the directive processors this host will interact with. 
  243:               //If we cannot do better, return the empty string. 
  244:               return  String.Empty;
  245:          }
  246:           //The engine calls this method to change the extension of the  
  247:           //generated text output file based on the optional output directive  
  248:           //if the user specifies it in the text template. 
  249:           //--------------------------------------------------------------------- 
  250:           public   void  SetFileExtension( string  extension)
  251:          {
  252:               //The parameter extension has a '.' in front of it already. 
  253:               //-------------------------------------------------------- 
  254:              fileExtensionValue = extension;
  255:          }
  256:           //The engine calls this method to change the encoding of the  
  257:           //generated text output file based on the optional output directive  
  258:           //if the user specifies it in the text template. 
  259:           //---------------------------------------------------------------------- 
  260:           public   void  SetOutputEncoding(System.Text.Encoding encoding,  bool  fromOutputDirective)
  261:          {
  262:              fileEncodingValue = encoding;
  263:          }
  264:           //The engine calls this method when it is done processing a text 
  265:           //template to pass any errors that occurred to the host. 
  266:           //The host can decide how to display them. 
  267:           //--------------------------------------------------------------------- 
  268:           public   void  LogErrors(CompilerErrorCollection errors)
  269:          {
  270:              errorsValue = errors;
  271:          }
  272:           //This is the application domain that is used to compile and run 
  273:           //the generated transformation class to create the generated text output. 
  274:           //---------------------------------------------------------------------- 
  275:           public  AppDomain ProvideTemplatingAppDomain( string  content)
  276:          {
  277:               //This host will provide a new application domain each time the  
  278:               //engine processes a text template. 
  279:               //------------------------------------------------------------- 
  280:               return  AppDomain.CreateDomain( "Generation App Domain" );
  281:               //This could be changed to return the current appdomain, but new  
  282:               //assemblies are loaded into this AppDomain on a regular basis. 
  283:               //If the AppDomain lasts too long, it will grow indefintely,  
  284:               //which might be regarded as a leak. 
  285:               //This could be customized to cache the application domain for  
  286:               //a certain number of text template generations (for example, 10). 
  287:               //This could be customized based on the contents of the text  
  288:               //template, which are provided as a parameter for that purpose. 
  289:          }
  290:   
  291:           public  ITextTemplatingSession CreateSession()
  292:          {
  293:               return  Session;
  294:          }
  295:   
  296:           public  ITextTemplatingSession Session
  297:          {
  298:              get;
  299:              set;
  300:          }
  301:      }
  302:  }

ITextTemplatingEngineHost  毫无疑问是模板解析引擎的主机;实现ITextTemplatingSessionHost  接口可以让我们往模板中传递变量,它采用了asp.net 中的Session概念。如果我们不需要往模板中传递Session数据话,可以不实现这个接口。

调用实例一:

    1:  Smart.TextTemplating.CustomCmdLineHost host =  new  Smart.TextTemplating.CustomCmdLineHost();
    2:              Engine engine =  new  Engine();
    3:              host.Session =  new  TextTemplatingSession();
    4:              host.Session[ "count" ] = 5;
    5:              host.TemplateFileValue =  "tmp.tt" ;
    6:               string  input = File.ReadAllText(host.TemplateFileValue);
    7:               string  output = engine.ProcessTemplate(input, host);
    8:              File.WriteAllText(host.TemplateFileValue +  ".txt" , output);

模板文件内容(tmp.tt):

    1:  <#@ template debug= "true"  #>
    2:  <#@ parameter name= "data"  type= "System.Object"  #>
    3:   
    4:  <# 
    5:      int  count = Convert.ToInt32(data);
    6:      for  ( int  i=0; i<count; i++)
    7:     {
    8:         WriteLine(i.ToString());
    9:     }   
   10:  #>

调用实例二:

此种方法我封装了一个单独的类,加入了我的代码收藏夹,实现了多文件生成,向模板文件传递数据等:

调用示例:

    1:  List< object > data =  new  List< object >();
    2:               for  ( int  i = 0; i < 5; i++)
    3:              {
    4:                  data.Add(i.ToString());
    5:              }
    6:   
    7:              Smart.TextTemplating.ParseTextTemplating parse =  new  Smart.TextTemplating.ParseTextTemplating(
    8:                   "" ,
    9:                   "view_" ,
   10:                   ".txt" ,
   11:                  data,
   12:                   "tmp.tt" );
   13:              parse.Parse();

我封装的代码(以后想用了直接调用就可以了):

    1:   using  System;
    2:   using  System.Collections.Generic;
    3:   using  System.Linq;
    4:   using  System.Text;
    5:   using  System.CodeDom.Compiler;
    6:   using  System.IO;
    7:   using  Microsoft.VisualStudio.TextTemplating;
    8:   
    9:   namespace  Smart.TextTemplating
   10:  {
   11:       public   class  ParseTextTemplating
   12:      {
   13:           #region  私有变量
   14:           private   string  _templateContent;
   15:           private   string  _saveRootPath;
   16:           private   string  _startFlag;
   17:           private   string  _extension;
   18:           private  List< object > _data =  new  List< object >();
   19:           #endregion 
   20:   
   21:           public  ParseTextTemplating( string  saveRootPath,  string  startFlag,  string  extension, List< object > data,  string  templatefilePath)
   22:          {
   23:               this ._saveRootPath = saveRootPath;
   24:               this ._startFlag = startFlag;
   25:               this ._extension = extension;
   26:               this ._data = data;
   27:   
   28:               if  ( string .IsNullOrEmpty(_saveRootPath))
   29:              {
   30:                  _saveRootPath = AppDomain.CurrentDomain.BaseDirectory;
   31:              }
   32:               if  ( string .IsNullOrEmpty(_extension))
   33:              {
   34:                  _extension =  ".html" ;
   35:              }
   36:               if  ( string .IsNullOrEmpty(templatefilePath) || !File.Exists(templatefilePath))
   37:              {
   38:                   this ._templateContent =  "" ;
   39:              }
   40:               else 
   41:              {
   42:                   this ._templateContent = File.ReadAllText(templatefilePath);
   43:              }
   44:          }
   45:   
   46:           public   void  Parse()
   47:          {
   48:               foreach  ( object  key  in  _data)
   49:              {
   50:                  SmartTextTemplatingEngineHost host =  new  SmartTextTemplatingEngineHost();
   51:                  host.Session =  new  TextTemplatingSession();
   52:                  host.Session[ "data" ] = key;
   53:                  Engine engine =  new  Engine();
   54:                   string  content = engine.ProcessTemplate(_templateContent, host);
   55:                   if  (! string .IsNullOrEmpty(content) && host.Errors.Count == 0)
   56:                  {
   57:                       string  filePath =  string .Format( "{0}{1}{2}{3}" ,
   58:                          _saveRootPath,
   59:                          _startFlag,
   60:                          DateTime.Now.ToString( "yyyyMMddHHmmss" ) + DateTime.Now.Millisecond.ToString( "d4" ),
   61:                          _extension);
   62:                      File.WriteAllText(filePath, content, Encoding.UTF8);
   63:                  }
   64:   
   65:                   foreach  (CompilerError er  in  host.Errors)
   66:                  {
   67:                      File.AppendAllText(_saveRootPath +  "error.txt" ,
   68:                          er.ToString() +  "\r\n\r\n" ,
   69:                          Encoding.UTF8);
   70:                  }
   71:              }
   72:          }
   73:      }
   74:  }

模板文件传递变量:<#@ parameter name="data" type="System.Object" #> 可以接口主程序传递过来的onject类型的变量数据。

2.3运行结果

生成的结果文件:

文件内容:

三、总结和展望

T4模板引擎是非常强大的,至于功能都强大到何处还需要我们深入的仔细了解。细心的同学可能已经发现了,T4模板引擎虽然强大,但是写模板时却没有一个像VS那样的带智能感知的代码提示工具啊?放心吧!这个问题已经不是问题了,安装一个VS扩展就行了,看截图:

下载地址我就不贴了,文件名字是“tangibleT4EditorPlusModellingToolsSetup.msi”,相信有了Google和文件的名字找到官方网站的主页和下载地址对你已经不是问题了,如果从VS扩展管理器安装的话,名字是(推荐了2个):t4 editor 和visual t4。

什么?T4模板的语法太古怪!写起来太麻烦!!写起来太累!程序员对代码总是这么苛刻,好吧,满足你的苛刻要求,不过要下次才能向你介绍了:

下次向你介绍的基于Net的模板解析引擎名字是:Razor

调用示例:

http://www.codeplex.com/ 上有个项目,名字是:RazorEngine 地址: http://razorengine.codeplex.com/

Razor的语法预览:

Razor语法详细介绍: http://weblogs.asp.net/scottgu/archive/2010/07/02/introducing-razor.aspx

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于基于T4模板引擎生成静态网站(CMS)的详细内容...

  阅读:37次