Get referenced project's path in T4 template?
Asked Answered
S

6

17

I have a solution that has a few projects in it. I'd like to create some T4 templates in one of my test projects to generate tests based on code in another project. The test project has a Project Reference to the other project. The problem I have is that I don't know how to get a file path to the edmx file I need to generate code from.

Example (pretend this is an ASCII-based Solution Explorer):

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)

How would my GeneratedTests.tt be able to get a file path for MyModel.edmx utilizing its project reference to it?

Statfarad answered 23/8, 2010 at 13:31 Comment(0)
R
4

This doesn't work that way. You'll have to reference the dll by path (you can find that out with Host.ResolvePath and use the VolatileAssembly tag from the toolbox to be able to recompile it without restarting VS ) and use reflection to work on the Model.

Ritchey answered 23/8, 2010 at 13:39 Comment(1)
Referencing the DLL by path causes the problem I'm trying to avoid. One dev box may have the project in one place while another one has it in another place. If I could assume the location, then I wouldn't have to have the template determine it - I could just hardcode it or inject it with build scripts. Working on the model is no problem - my script runs perfectly if I hardcode the path. I just need a way to dynamically determine that path. Using Host.Resolve is fine if I need the template's path - I just need to figure out the path for the MyDAL assembly's source.Statfarad
C
17

This answer only works from within Visual Studio.

Set the "hostspecific" property of the T4 template. This gives you access to the Host property. Type cast Host to IServiceProvider to call GetService(typeof(DTE)). This lets you traverse the contents of the solution.

<#@ 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#>
<#
}
#>

Also see the example of the ITextTemplatingEngineHost interface on MSDN and T4 Architecture by Oleg Synch.

Charmaincharmaine answered 29/8, 2010 at 19:37 Comment(2)
I think this will work nicely for me if it works as it sounds like it will. Could you, however, elaborate on your comment that this only works within Visual Studio? We have plans to automate our template generations since we're starting to put them all over our solution because it's beginning to become painful to manually go through and generate everything. We don't know exactly how we'll automate it, so your answer can help us plan it. Nant, PS/MSBuild, whatever - we're fine doing it however it's necessary to do it.Statfarad
When you set hostspecific="true", the template gains access to the Host parameter. But the down side is that the T4 host must provide it. In this case, the T4 host is assumed to be Visual Studio, which implements the DTE interface. The command line build would return null. If you want to use this method, I suggest you transform your templates within Visual Studio, not within the build. The automated build can use the generated code.Charmaincharmaine
F
12

Based off of James Close's comment, I was able to write the following template for debugging my file paths:

<#@ 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
    <#
}
#>

This will generate a .txt file and will very quickly help you debug whether your path could be located or not.

As a side note, in cases where there was a relative dir path (e.g. ../App.config) that couldn't be located, I found that it helped to put a file (e.g. test1.txt) at each directory level, as I figured out that Host.ResolvePath wasn't able to see outside the current assembly with my setup. This caveat can get confusing very quickly since ../../App.config might resolve to MySolution\App.config, but ../../MyDal/README.txt won't resolve (hence the file won't be found), even if that is the correct path. The above code seems to negate this problem as far as I can see.

The above solution might also be a resolution to this issue - How to use the poco entity generator

Fiacre answered 13/1, 2013 at 13:50 Comment(0)
E
5

use these lines

string path = this.Host.ResolvePath("");
Directory.SetCurrentDirectory(path);

then use relative path to get your edmx file e.g, string inputFile = @"..\Modal.edmx";

Eerie answered 6/11, 2010 at 6:57 Comment(0)
R
4

This doesn't work that way. You'll have to reference the dll by path (you can find that out with Host.ResolvePath and use the VolatileAssembly tag from the toolbox to be able to recompile it without restarting VS ) and use reflection to work on the Model.

Ritchey answered 23/8, 2010 at 13:39 Comment(1)
Referencing the DLL by path causes the problem I'm trying to avoid. One dev box may have the project in one place while another one has it in another place. If I could assume the location, then I wouldn't have to have the template determine it - I could just hardcode it or inject it with build scripts. Working on the model is no problem - my script runs perfectly if I hardcode the path. I just need a way to dynamically determine that path. Using Host.Resolve is fine if I need the template's path - I just need to figure out the path for the MyDAL assembly's source.Statfarad
F
4

Based on the answer of Mina and others I came up with this solution. It lists the current working directory, the solution path and uses the trick of Mina to change the active working directory.

<#@ 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 #>
Fairfield answered 2/6, 2017 at 8:42 Comment(0)
M
2

You can use the macros for special directories such as $(ProjectDir), $(SolutionDir) from the template, and perhaps read the .sln or .csproj file to extract the directory for the other project.

Megasporangium answered 23/8, 2010 at 14:11 Comment(3)
I was hoping to avoid reading the .sln file - I guess mostly because I don't understand it all that well and I'm not sure what assumptions I can make about how reliable the path is in there. Can you give me any links on understanding this better, maybe?Statfarad
Actually, I don't think it's suitable. Those macros only appear to work in the T4 directives like <#@assembly #> and <#@include #>Megasporangium
If you need to resolve a macro path outside of a T4 directive you can do it by using: strPath = Host.ResolveAssemblyReference("$(ProjectDir)"), ResolvePath does not work with macros - a future enhancement perhaps?Tundra

© 2022 - 2024 — McMap. All rights reserved.