zoukankan      html  css  js  c++  java
  • 基于T4模板引擎生成静态网站(CMS)

    缘由

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

    一、所需准备:

    1. 本文介绍的实现方法将以C#语言为实现。
    2. 实验环境是VS10+sp1+VSsdk
    3. 需要引入程序集:Microsoft.VisualStudio.TextTemplating.10.0.dll  和 Microsoft.VisualStudio.TextTemplating.Interfaces.10.0.dll
    4. 所需的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运行结果

    生成的结果文件:

    image_thumb

    文件内容:

    image_thumb1

    三、总结和展望

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

    image_thumb2

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

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

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

    调用示例:

    image_thumb4

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

    Razor的语法预览:

    image_thumb3

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

  • 相关阅读:
    c++语言特性深究
    springmvc和springboot的差别
    c++11新的大特性
    C/C++编程笔记:C语言进制详解,二进制、八进制和十六进制!
    程序人生丨想学编程,大学什么样的专业能成为一名真正的程序员?
    C/C++编程笔记:C语言预处理命令是什么?不要以为你直接写#就行!
    盘点:中国“颜值+才华”的几位知名女程序员!如何看待女生当程序员?
    程序人生丨三种语言实现—用户登录界面随机验证码,源代码分享!
    自学编程,为何你始终不能学出效果?切记一定要避免这 8 大误区!
    第二批《中等职业学校专业教学标准(试行)》目录
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2508110.html
Copyright © 2011-2022 走看看