T4 Toolbox - класс ссылок в текущей сборке - PullRequest
13 голосов
/ 21 июля 2010

Я пишу скрипт T4, который отражает некоторые классы и обеспечивает генерацию кода на их основе. Проблема в том, что мой скрипт выдает ошибку, говоря, что классы в моем текущем проекте недоступны.

Сам скрипт находится в той же сборке, что и классы, на которые я пытаюсь ссылаться. Я попытался сослаться на пространство имен, файл и добавить ссылку на текущую сборку (сам проект) - все безрезультатно.

Чего мне не хватает?

Ответы [ 4 ]

10 голосов
/ 20 октября 2012

Я верю, что это то, что ищут Нико и Уосо.Просто измените «MyAssembly.CodeGeneration» на имя проекта с шаблонами T4.

<#@ assembly name="$(TargetPath)MyAssembly.dll" #>
<#@ import namespace="MyAssembly.CodeGeneration" #>
2 голосов
/ 23 апреля 2013

Следует иметь в виду, что сценарий 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"), переберите все классы в нем, затем выведите новый файл кода, в котором перечислены только их общедоступные свойства с getter / setter - по сути, создавая POCO объектов,В некоторых моих проектах я использую адаптированную версию этого кода для генерации объектов JavaScript на основе моих POCO, так что мои модели JS всегда могут быть синхронизированы с моими объектами на стороне сервера с точки зрения сериализации.

Однако хитрость заключается в первых нескольких строках:

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, в котором хранится текущий TemplateFile, и в методе FindClasses() анализируем классы в этом проекте, которые соответствуют нашим критериям поиска.

Надеюсь, пример кодадает вам отправную точку, с которой нужно спрыгнуть - но если вам нужны дополнительные подробности, вот несколько дополнительных рекомендаций для вас:

1 голос
/ 25 марта 2017

По какой-то причине я не смог заставить работать решение @brian.Я закончил тем, что делал это В моем случае T4Generators был моим отдельным проектом в том же решении.

<#@ assembly name="$(SolutionDir)\T4Generators\bin\Debug\T4Generators.dll" #>
<#@ import Namespace="T4Generators" #>
0 голосов
/ 20 сентября 2015

Ссылка это общепринятый способ. Затем проверьте, загружена ли сборка, если нет - сгенерируйте код заглушки (чтобы сделать возможной компиляцию; после компиляции снова запустите T4, чтобы сгенерировать реальный код). И проведите модульное тестирование, которое может помешать запуску заглушки.

...