动态运行T4模板 - .net core环境下 - 非TextTransform.exe模式运行
如果需要在运行时调用T4模板,一般有以下几种方式
- 通过TextTransform.exe传递参数的模式(https://www.cnblogs.com/ives/p/8760661.html)
- 通过运行时T4脚本生成的对象调用(https://www.cnblogs.com/ives/p/17294228.html)
除此之外调用脚本所需要的依赖为【Microsoft.VisualStudio.TextTemplating】、【Microsoft.VisualStudio.TextTemplating.Interfaces】
这两个依赖可以单独安装也可以通过安装【Mono.TextTemplating】或者【Microsoft.VisualStudio.SDK】来替代
需要注意Mono.TextTemplating不含.net framework依赖, Microsoft.VisualStudio.SDK包含.net framework
随后根据微软文档中对ITextTemplatingEngineHost、以及ITextTemplatingSessionHost的描述可知
- ITextTemplatingEngineHost 可以实现动态解析T4
- ITextTemplatingEngineHost 内部有两个重要对象StandardAssemblyReferences、StandardImports可以实现引用的导入(官网文档给出的为只读属性,需要简单调整下便于外部传递参数,可以通过这两个对象导入也可以通过T4模板中的宏导入,例:<#@ assembly name="System.Core.dll" #> <#@ import namespace="System.Linq" #>)
- ITextTemplatingSessionHost 可以传递参数,通过Session传递
- 如果需要在T4脚本中通过Host.ResolveParameterValue动态解析参数而不是通过绑定参数(<#@ parameter name=""ParameterName"" type=""System.String"" #>)时需要将CustomCmdLineHost中ResolveParameterValue方法调整从Session查找参数。
根据这些信息便可以实现基本的调用,另外需要注意的是解析模板所需要的引用需要注入,
如果使用Mono.TextTemplating,则需要通过StandardAssemblyReferences属性注入Mono.TextTemplating.dll。
另外如果无法解析程序集,则需要通过绝对路径将其注入,需要注意此处的路径如果包含空格需要先解码System.Web.HttpUtility.UrlDecode(resource.AbsolutePath)
var netstand = new Uri(ResourceHelper.TargetDir + "netstandard.dll");
host.StandardAssemblyReferences.Add(netstand.AbsolutePath);
调用方法为
CustomCmdLineHost host = new CustomCmdLineHost();
Engine engine = new Engine();
host.TemplateFileValue = $@"{ResourceHelper.TargetDir}{SelectTemplate.Path}";
//参数传递
var sessionHost = (ITextTemplatingSessionHost)host;
sessionHost.Session = new TextTemplatingSession();
sessionHost.Session.Add("ParameterName", base64Path);
//Read the text template.
string input = File.ReadAllText($@"{ResourceHelper.TargetDir}{SelectTemplate.Path}");
host.StandardAssemblyReferences.Add("Mono.TextTemplating.dll");
input = @"test output, paramter name value ";
//Transform the text template.
string output = engine.ProcessTemplate(input, host);
关于调用T4模板的基础类,根据文档修改后的代码如下
using System;
using System.IO;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;
namespace CustomHost
{
//The text template transformation engine is responsible for running
//the transformation process.
//The host is responsible for all input and output, locating files,
//and anything else related to the external environment.
//-------------------------------------------------------------------------
class CustomCmdLineHost : ITextTemplatingEngineHost, ITextTemplatingSessionHost
{
//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; set;
} = new
List<string>()
{ typeof(System.Uri).Assembly.Location};
public void AddStandardAssemblyReferences(string path)
{
var resource = new Uri(path);
StandardAssemblyReferences.Add(System.Web.HttpUtility.UrlDecode(resource.AbsolutePath));
}
//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;
set;
} = new List<string>() { "System" };
public ITextTemplatingSession Session { get; set; }
//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;
}
}
//Called by the Engine to enquire about
//the processing options you require.
//If you recognize that option, return an
//appropriate value.
//Otherwise, pass back NULL.
//--------------------------------------------------------------------
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 (Session == null)
{
return string.Empty;
}
if (parameterName == null)
{
throw new ArgumentNullException("the parameterName cannot be null");
}
if (Session.ContainsKey(parameterName))
{
return Session[parameterName].ToString();
}
//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.
}
public ITextTemplatingSession CreateSession()
{
return Session;
}
}
}
注意事项
导入程序集时需要保证运行目录下包含对应的dll, 框架底层的dll也需要包含,如:netstandard.dll、System.Core.dll
调试过程中可能需要返回清空调试目录下的文件,某些情况下编译无法识别改动
参数传递
传递参数需要处理以下几个步骤
- 引入dll
- 导入名称空间
- 添加参数
- 绑定参数
- t4模板中引用
//dll
host.StandardAssemblyReferences.Add("Mono.TextTemplating.dll");
host.StandardAssemblyReferences.Add("Model.dll");
//名称空间
host.StandardImports.Add("Models");
//添加参数
host.Session = new TextTemplatingSession();
host.Session.Add("parameter1", new T4ParameterInfo { Name = base64Path });
//绑定参数 - <#@ parameter name=""parameter1"" type=""Models.T4ParameterInfo"" #>
//模板中引用参数
var input = @"
<#@ parameter name=""parameter1"" type=""Models.T4ParameterInfo"" #>
test output, paramter name value <#= parameter1.Name #>"; parameter1.Name为参数中的一个字段
留待后查,同时方便他人
联系我:renhanlinbsl@163.com
联系我:renhanlinbsl@163.com