T4模板中的GetType()始终返回null

问题描述 投票:0回答:1

我正在尝试编写一个T4模板,该模板将为文件夹中的每个模型类生成一个部分类文件。我可能做错了,所以请随时提出改进建议,以改善似乎有用的功能。

我的测试项目有一个Models文件夹,其中包含几个简单的类,例如Person.cs包含...

using System;

namespace WithTT.Models {
  public partial class Person {
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string Surname { get; set; }
    // etc...
  }
}

我的T​​4模板(在同一文件夹中)使用MultipleOutputHelper.ttinclude帮助器模板。 T4文件当前看起来像这样...

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ include file="MultipleOutputHelper.ttinclude" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".log" #>

<# var manager = Manager.Create(Host, GenerationEnvironment); #>
<# manager.StartHeader(false); #>
<# manager.EndBlock(); #>

<#
  string folder = this.Host.ResolvePath("."); // get current folder
  folder = folder.Substring(0, folder.Length - 2); // knock the "\." off the end
  // Loop through each file and create an AbcExt.cs partial class
  foreach(string file in Directory.GetFiles(folder, "*.cs").Where(f => !f.EndsWith("Ext.cs")).Select(f => f.Replace(".cs", ""))) {
    manager.StartNewFile($"{file}Ext.cs"); 
    string className = Path.GetFileNameWithoutExtension(file);
    string ns = File.ReadAllLines(file + ".cs").Single(l => l.StartsWith("namespace")).Replace("namespace", "").Replace("{", "").Trim();
#>
// This code was generated from a template, do not modify!
namespace <#=ns#> {
  public partial class <#=className#> {
    // TODO AYS - Write the With() method...
  }
}
<#
manager.EndBlock();
}
#>

<# manager.Process(true); #>

到目前为止,此方法工作正常,并生成如下所示的基本代码文件

// This code was generated from a template, do not modify!
namespace WithTT.Models {
  public partial class Person {
    // TODO AYS - Write the With() method...
  }
}

现在,我需要使用一些辅助方法来生成我想要的代码,这些方法需要相关类的类型。掌握了名称空间(在ns变量中)和类名(在className变量中),我希望能够执行以下操作...

Type t = Assembly.GetExecutingAssembly().GetType(ns + "." + className);

...或...

Type t = Type.GetType(ns + "." + className);

但是,无论我使用哪个,t始终为null。从生成的文件中可以看到,名称空间和类名很好,并且根据对this question的前两个答案,两个都应该起作用。

如果我在测试项目的Program.cs中尝试相同的代码,则得到的类型没有问题。

任何人都知道如何获得所讨论课程的Type吗?谢谢

Edit我意识到了为什么类型总是为空。如果我将程序集名称空间转储到输出中,请这样说...

// <#=Assembly.GetExecutingAssembly().FullName#>

...然后我看到TemporaryT4Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null显然与保存类的程序集不同。我现在的问题是找出如何掌握项目的程序集,而不是从T4模板生成项目。

c# reflection t4
1个回答
0
投票

结果非常简单。 This SO answer显示了以下两行...

var path = this.Host.ResolveAssemblyReference("$(TargetPath)");
var asm = Assembly.LoadFrom(path);

一旦加载了程序集,获取类型就非常简单...

Type t = asm.GetType(fileNamespace + "." + className);

EDIT我发现这种方法的主要缺点是Assembly.LoadFrom锁定了程序集,这意味着您必须重新启动Visual Studio才能重建项目。

另一种方法使用Assembly.Load(File.ReadAllBytes(path)),可以避免此问题(因为它从实际程序集的副本创建Assembly,但是需要您知道程序集的路径和名称。

出于我的目的,以下是the俩...

  IServiceProvider hostServiceProvider = (IServiceProvider)Host;
  EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
  Array activeSolutionProjects = (Array)dte.ActiveSolutionProjects;
  EnvDTE.Project dteProject = (EnvDTE.Project)activeSolutionProjects.GetValue(0);
  string defaultNamespace = dteProject.Properties.Item("DefaultNamespace").Value.ToString();
  string templateDir = Path.GetDirectoryName(Host.TemplateFile);
  string fullPath = dteProject.Properties.Item("FullPath").Value.ToString();
  fullPath = fullPath.EndsWith("\\") ? fullPath.Substring(0, fullPath.Length-1) : fullPath;
  string exePath = fullPath + "\\bin\\Debug\\" + defaultNamespace + ".exe";
  Assembly asm = Assembly.Load(File.ReadAllBytes(exePath));

...这是很多代码(尽管如果您比我现在更了解它,则有可能清理它)。在正常情况下,不建议使用Assembly.LoadFrom,但在这里工作正常。

这种方法的主要问题是,我假设程序集位于bin\Debug文件夹中(这不是一个坏的假设),它的名称与项目相同(不是一个很大的假设,但一般来说可能是正确的),并且.exe(相当糟糕的假设,经常会出错。可以通过在上面的代码中查找项目类型来缓解最后一个假设,但是我没有时间尝试。)>

© www.soinside.com 2019 - 2024. All rights reserved.