在T4模板中获取引用项目的路径?

17

我有一个解决方案,其中包含几个项目。我想在我的一个测试项目中创建一些T4模板,根据另一个项目中的代码生成测试。测试项目具有对另一个项目的项目引用。我的问题是我不知道如何获取需要生成代码的edmx文件的文件路径。

示例(假装这是基于ASCII的“ 解决方案资源管理器”):

MySolution.sln
-> MyTests.csproj (C:\a\b\c\)
----> GeneratedTests.tt (C:\a\b\c\GeneratedTests.tt)
-> MyDAL.csproj (C:\x\y\z\)
----> MyModel.edmx (C:\x\y\z\MyModel.edmx)

如何使我的GeneratedTests.tt能够利用它对MyModel.edmx的项目引用来获取文件路径?

6个回答

17

这个答案仅在Visual Studio内部起作用。

设置T4模板的"hostspecific"属性。这会使您可以访问Host属性。将Host强制转换为IServiceProvider以调用GetService(typeof(DTE))。这使您可以遍历解决方案的内容。

<#@ template language="c#" hostspecific="true"  #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
These are the projects in this solution:
<#
var serviceProvider = this.Host as IServiceProvider;
var dte = serviceProvider.GetService(typeof(DTE)) as DTE;
foreach (Project p in dte.Solution.Projects)
{
#>
    <#=p.Name#> at <#=p.FullName#>
<#
}
#>

同时还可参考MSDN上ITextTemplatingEngineHost接口的示例和Oleg Synch的T4架构


如果它能像听起来的那样工作,我认为这对我会非常有用。然而,您能详细说明一下您的评论吗?我们计划自动化模板生成,因为我们开始在整个解决方案中放置它们,手动浏览和生成所有内容开始变得痛苦。我们不知道如何精确地自动化它,所以您的回答可以帮助我们规划。无论是 Nant,PS/MSBuild 还是其他任何方式,我们都可以将其完成。 - Jaxidian
当您设置hostspecific="true"时,模板将获得访问主机参数的权限。但缺点是T4主机必须提供它。在这种情况下,T4主机被假定为Visual Studio,它实现了DTE接口。命令行构建将返回null。如果您想使用此方法,我建议您在Visual Studio中转换模板,而不是在构建中进行。自动化构建可以使用生成的代码。 - Michael L Perry

12

基于James Close的评论,我能够编写以下文件路径调试模板:

<#@ template language="C#" debug="true" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#><#@
 output extension=".txt"#><#

/////////Some standard-ish settings, continue reading on
CodeGenerationTools code = new CodeGenerationTools(this);
MetadataLoader loader = new MetadataLoader(this);
CodeRegion region = new CodeRegion(this, 1);
MetadataTools ef = new MetadataTools(this);

/////////Below are the relevant sections I used for debugging

    string solutionsPath = Host.ResolveAssemblyReference("$(SolutionDir)");//Gives you the location of MySolution.sln
    string edmxFile = solutionsPath + "MyDAL/MyDAL/MyModel.edmx"; //Note - VS projects usually have a subdir with the same name as the sln, hence the repetition for MyDAL
#>
Does this file exist?

<#
//
if (File.Exists(edmxFile))
{
    //Continue.
    #>
    Yes
    <#
}
else
{
    #>
    No
    <#
}
#>

这将生成一个 .txt 文件,并且将快速帮助您调试路径是否能够被定位。

另外,如果存在无法定位的相对目录路径(例如 ../App.config),我发现在每个目录级别放置一个文件(例如 test1.txt)有所帮助,因为我想出 Host.ResolvePath 无法看到我的设置中当前程序集之外的内容。这个警告很容易让人感到困惑,因为 ../../App.config 可能会解析为 MySolution\App.config,但 ../../MyDal/README.txt 不会解析(因此文件无法找到),即使那是正确的路径。据我所知,上面的代码似乎消除了这个问题。

上述解决方案也可能是解决此问题的一种方法 - 如何使用 poco 实体生成器


5
使用以下代码行:
string path = this.Host.ResolvePath("");
Directory.SetCurrentDirectory(path);

接着使用相对路径来获取你的edmx文件,例如:string inputFile = @"..\Modal.edmx";


4

根据Mina和其他人的答案,我想出了这个解决方案。它列出了当前工作目录、解决方案路径,并使用了Mina的技巧来更改活动工作目录。

<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ import namespace="System.IO" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#
 string cwd1 = System.IO.Directory.GetCurrentDirectory();
 string solutionPath = Host.ResolveAssemblyReference("$(SolutionDir)");
 Directory.SetCurrentDirectory(solutionPath);
 string cwd2 = System.IO.Directory.GetCurrentDirectory();
#>
// Solutionpath is:<#= solutionPath #>, old cwd: <#= cwd1 #>, new cwd: <#= cwd2 #>

4

这样做是不可行的。你需要通过路径引用dll(你可以使用Host.ResolvePath找到路径),并使用工具箱中的VolatileAssembly标签,以便在不重新启动VS的情况下重新编译它,并使用反射来处理模型。


1
通过路径引用DLL会导致我试图避免的问题。一个开发环境可能将项目放在一个地方,而另一个开发环境则将其放在另一个地方。如果我可以假设位置,那么我就不必让模板来确定它 - 我可以直接硬编码或者使用构建脚本注入它。在模型上工作没有问题 - 如果我硬编码路径,我的脚本可以完美运行。我只需要找到一种动态确定该路径的方法。如果我需要模板的路径,使用Host.Resolve是可以的 - 我只需要找出MyDAL程序集源的路径即可。 - Jaxidian

2
你可以使用模板中的特殊目录宏,例如$(ProjectDir)$(SolutionDir),并且可能需要读取.sln或.csproj文件来提取其他项目的目录。请注意保留HTML标签。

我本来希望避免阅读.sln文件 - 我想主要是因为我不太理解它,也不确定在其中路径的可靠性可以做出什么样的假设。你能给我一些更好地理解这个问题的链接吗? - Jaxidian
实际上,我不认为这是合适的。这些宏似乎只能在T4指令中工作,如<#@assembly #><#@include #> - Mark H
7
如果您需要在T4指令之外解析宏路径,可以使用以下语句:strPath = Host.ResolveAssemblyReference("$(ProjectDir)")。ResolvePath无法处理宏 - 也许这是一个未来的增强功能? - James Close

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接