特定のクラスを反映し、それらに基づいてコード生成を提供する T4 スクリプトを作成しています。問題は、現在のプロジェクトのクラスにアクセスできないと言って、スクリプトがエラーになることです。
スクリプト自体は、参照しようとしているクラスと同じアセンブリにあります。名前空間、ファイルを参照し、現在のアセンブリ (プロジェクト自体) への参照を追加しようとしましたが、すべて役に立ちませんでした。
私は何が欠けていますか?
特定のクラスを反映し、それらに基づいてコード生成を提供する T4 スクリプトを作成しています。問題は、現在のプロジェクトのクラスにアクセスできないと言って、スクリプトがエラーになることです。
スクリプト自体は、参照しようとしているクラスと同じアセンブリにあります。名前空間、ファイルを参照し、現在のアセンブリ (プロジェクト自体) への参照を追加しようとしましたが、すべて役に立ちませんでした。
私は何が欠けていますか?
これこそが、Niko と uosɐſ が探しているものだと思います。「MyAssembly.CodeGeneration」を T4 テンプレートを含むプロジェクトの名前に変更するだけです。
<#@ assembly name="$(TargetPath)MyAssembly.dll" #>
<#@ import namespace="MyAssembly.CodeGeneration" #>
覚えておくべきことの 1 つは、作成している T4 スクリプトは実際には「同じアセンブリに存在する」わけではないということです。これは、実行時コードではなく設計時コードであるためです。つまり、アセンブリにまったくコンパイルされません。
代わりに、T4 テンプレート スクリプトは、コードの記述中に実行されます。または、特定のフックが有効になっている場合は、プログラムをビルド/コンパイルするたびに実行されます。ただし、実際にはプロジェクトのコードとは別のものであるため、プロジェクトのアセンブリを直接参照することはできません ( DTEなどを使用しない限り)。これにより、Visual Studio 環境自体にアクセスしたり、現在の-ロードされたプロジェクト。
例として、次のスクリプトを考えてみましょう。
<#@ template language="C#" debug="false" hostspecific="true" #>
<#@ output extension=".js" #>
<#@ assembly name="System" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data.Entity" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#@ include file="T4Toolbox.tt" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>
string targetNamespace = "MyNamespace";
var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
var project = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile).ContainingProject);
var classes = FindClasses(project, targetNamespace, "");
<# foreach (CodeClass c in classes) { #>
public class <#= c.Name #> {
<# var properties = c.Members.OfType<EnvDTE.CodeProperty>()
.Where(p => p.Access.HasFlag(vsCMAccess.vsCMAccessPublic))
.OrderBy(p => p.Name);
foreach (var prop in properties) {
#>
public <#= prop.Type.AsString #> <#= prop.Name #> { get; set; }
<# } #>
}
<# } #>
<#+ List<CodeClass> FindClasses(Project project, string ns, string className) {
List<CodeClass> result = new List<CodeClass>();
FindClasses(project.CodeModel.CodeElements, className, ns, result, false);
return result;
}
void FindClasses(CodeElements elements, string className, string searchNamespace, List<CodeClass> result, bool isNamespaceOk) {
if (elements == null) return;
foreach (CodeElement element in elements) {
if (element is CodeNamespace) {
CodeNamespace ns = element as CodeNamespace;
if (ns != null) {
if (ns.FullName == searchNamespace)
FindClasses(ns.Members, className, searchNamespace, result, true);
else
FindClasses(ns.Members, className, searchNamespace, result, false);
}
} else if (element is CodeClass && isNamespaceOk) {
CodeClass c = element as CodeClass;
if (c != null) {
if (c.FullName.Contains(className))
result.Add(c);
FindClasses(c.Members, className, searchNamespace, result, true);
}
}
}
}
このスクリプトは、本質的に、特定の名前空間 (この場合は ) を介して実行され、その中のすべてのクラスを反復処理してから、本質的に/ -"MyNamespace"
を含むパブリック プロパティのみをリストする新しいコード ファイルを出力し、オブジェクト。私のプロジェクトのいくつかでは、このコードの適合バージョンを使用して POCO に基づいて JavaScript オブジェクトを生成し、シリアライゼーションの観点から、JS モデルがサーバー側オブジェクトと常に同期できるようにしています。getter
setter
ただし、その秘訣は最初の数行にあります。
var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
var project = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile).ContainingProject);
var classes = FindClasses(project, targetNamespace, "");
本質的に、DTE サービスは Visual Studio に、現在読み込まれている の抽象モデルを提供するように要求していSolution
ますProjects
。Project
次に、currentTemplateFile
が格納されている をロードし、FindClasses()
メソッドで、検索条件に一致するそのプロジェクト内のクラスを解析します。
サンプルコードが出発点となることを願っていますが、さらに詳細が必要な場合は、次のいくつかの参考資料を参照してください。