텍스트 템플릿에서 모델에 액세스
텍스트 템플릿을 사용하여 DSL(Domain-Specific Language) 모델을 기반으로 하는 보고서 파일, 소스 코드 파일 및 기타 텍스트 파일을 만들 수 있습니다. 텍스트 템플릿에 대한 기본 정보는 코드 생성 및 T4 텍스트 템플릿을 참조하세요. 텍스트 템플릿은 DSL을 디버그할 때 실험적 모드에서 작동하며, DSL을 배포한 컴퓨터에서도 작동합니다.
참고 항목
DSL 솔루션을 만들 때 샘플 텍스트 템플릿 *.tt 파일이 디버깅 프로젝트에 생성됩니다. 도메인 클래스의 이름을 변경하면 이러한 템플릿이 더 이상 작동하지 않습니다. 그럼에도 불구하고 필요한 기본 지시문을 포함하고 DSL과 일치하도록 업데이트할 수 있는 예제를 제공합니다.
텍스트 템플릿에서 모델에 액세스하려면 다음을 수행합니다.
템플릿 지시문의 inherit 속성을 Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation으로 설정합니다. 이렇게 하면 저장소에 액세스할 수 있습니다.
액세스하려는 DSL에 대한 지시문 프로세서를 지정합니다. 이렇게 하면 DSL에 대한 어셈블리가 로드되므로 텍스트 템플릿의 코드에서 해당 도메인 클래스, 속성 및 관계를 사용할 수 있습니다. 또한 지정한 모델 파일도 로드됩니다.
다음 예제와 유사한
.tt
파일은 DSL 최소 언어 템플릿에서 새 Visual Studio 솔루션을 만들 때 디버깅 프로젝트에 만들어집니다.
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
<#@ output extension=".txt" #>
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
This text will be output directly.
This is the name of the model: <#= this.ModelRoot.Name #>
Here is a list of elements in the model:
<#
// When you change the DSL Definition, some of the code below may not work.
foreach (ExampleElement element in this.ExampleModel.Elements)
{#>
<#= element.Name #>
<#
}
#>
이 템플릿에서는 다음 사항에 주의하세요.
템플릿은 DSL 정의에서 정의한 도메인 클래스, 속성 및 관계를 사용할 수 있습니다.
템플릿에서
requires
속성에서 지정한 모델 파일이 로드됩니다.this
의 속성에는 루트 요소가 포함되어 있습니다. 여기에서 코드는 모델의 다른 요소로 이동할 수 있습니다. 속성의 이름은 일반적으로 DSL의 루트 도메인 클래스와 동일합니다. 이 예제에서는this.ExampleModel
입니다.코드 조각이 작성되는 언어는 C#이지만 모든 종류의 텍스트를 생성할 수 있습니다. 또는
language="VB"
속성을template
지시문에 추가하여 Visual Basic에서 코드를 작성할 수 있습니다.템플릿을 디버그하려면
template
지시문에debug="true"
를 추가합니다. 예외가 발생하면 템플릿이 Visual Studio의 다른 인스턴스에서 열립니다. 코드의 특정 지점에서 디버거를 중단하려면System.Diagnostics.Debugger.Break();
문을 삽입합니다.자세한 내용은 T4 텍스트 템플릿 디버깅을 참조하세요.
DSL 지시문 프로세서 정보
템플릿은 DSL 정의에서 정의한 도메인 클래스를 사용할 수 있습니다. 이는 일반적으로 템플릿의 시작 부근에 표시되는 지시문으로 인해 발생합니다. 이전 예제에서는 다음과 같습니다.
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
지시문의 이름(이 예제에서는 MyLanguage
)은 DSL의 이름에서 파생됩니다. DSL의 일부로 생성되는 지시문 프로세서를 호출합니다. Dsl\GeneratedCode\DirectiveProcessor.cs에서 소스 코드를 찾을 수 있습니다.
DSL 지시문 프로세서는 다음 두 가지 주요 작업을 수행합니다.
DSL을 참조하는 템플릿에 어셈블리 및 가져오기 지시문을 효과적으로 삽입합니다. 이렇게 하면 템플릿 코드에서 도메인 클래스를 사용할 수 있습니다.
requires
매개 변수에 지정한 파일을 로드하고, 로드된 모델의 루트 요소를 참조하는this
에서 속성을 설정합니다.
템플릿 실행 전에 모델 유효성 검사
템플릿이 실행되기 전에 모델의 유효성을 검사할 수 있습니다.
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1';validation='open|load|save|menu'" #>
다음에 유의합니다.
filename
및validation
매개 변수는 ";" 기호로 구분되며 다른 구분 기호나 공백이 없어야 합니다.유효성 검사 범주 목록에 따라 실행할 유효성 검사 메서드가 결정됩니다. 여러 범주는 "|"로 구분되어야 하며 다른 구분 기호나 공백이 없어야 합니다.
오류가 발견되면 오류 창에 보고되고, 결과 파일에는 오류 메시지가 포함됩니다.
텍스트 템플릿에서 여러 모델 액세스
참고 항목
이 메서드를 사용하면 동일한 템플릿에서 여러 모델을 읽을 수 있지만 ModelBus 참조는 지원하지 않습니다. ModelBus 참조로 상호 연결된 모델을 읽으려면 텍스트 템플릿에서 Visual Studio ModelBus 사용을 참조하세요.
동일한 텍스트 템플릿에서 두 개 이상의 모델에 액세스하려면 각 모델에 대해 생성된 지시문 프로세서를 한 번 호출해야 합니다. requires
매개 변수에서 각 모델의 파일 이름을 지정해야 합니다. provides
매개 변수에서 루트 도메인 클래스에 사용할 이름을 지정해야 합니다. 각 지시문 호출의 provides
매개 변수에 대해 서로 다른 값을 지정해야 합니다. 예를 들어 Library.xyz, School.xyz 및 Work.xyz 라는 세 개의 모델 파일이 있다고 가정합니다. 동일한 텍스트 템플릿에서 액세스하려면 다음과 유사한 세 개의 지시문 호출을 작성해야 합니다.
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Library.xyz'" provides="ExampleModel=LibraryModel" #>
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='School.xyz'" provides="ExampleModel=SchoolModel" #>
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Work.xyz'" provides="ExampleModel=WorkModel" #>
참고 항목
이 예제 코드는 최소 언어 솔루션 템플릿을 기반으로 하는 언어용입니다.
텍스트 템플릿에 있는 모델에 액세스하기 위해 다음 예제의 코드와 유사한 코드를 작성할 수 있습니다.
<#
foreach (ExampleElement element in this.LibraryModel.Elements)
...
foreach (ExampleElement element in this.SchoolModel.Elements)
...
foreach (ExampleElement element in this.WorkModel.Elements)
...
#>
동적으로 모델 로드
런타임 시 로드할 모델을 확인하려는 경우 DSL 관련 지시문을 사용하는 대신 프로그램 코드에서 모델 파일을 동적으로 로드할 수 있습니다.
하지만 DSL 관련 지시문의 함수 중 하나는 템플릿 코드가 해당 DSL에 정의된 도메인 클래스를 사용할 수 있도록 DSL 네임스페이스를 가져오는 것입니다. 지시문을 사용하지 않으므로 로드할 수 있는 모든 모델에 대해 <assembly> 및 <import> 지시문을 추가해야 합니다. 로드할 수 있는 여러 모델이 모두 동일한 DSL의 인스턴스인 경우 이 방법이 쉽습니다.
파일을 로드하기 위해 가장 효과적인 방법은 Visual Studio ModelBus를 사용하는 것입니다. 일반적인 시나리오에서 텍스트 템플릿은 DSL 관련 지시문을 사용하여 일반적인 방식으로 첫 번째 모델을 로드합니다. 해당 모델에는 다른 모델에 대한 ModelBus 참조가 포함됩니다. ModelBus를 사용하여 참조된 모델을 열고 특정 요소에 액세스할 수 있습니다. 자세한 내용은 텍스트 템플릿에서 Visual Studio ModelBus 사용을 참조하세요.
덜 일반적인 시나리오에서는 파일 이름만 있고 현재 Visual Studio 프로젝트에 없을 수 있는 모델 파일을 여는 것이 좋을 수 있습니다. 이 경우 방법: 프로그램 코드로 파일에서 모델 열기에서 설명하는 기법을 사용하여 파일을 열 수 있습니다.
템플릿에서 여러 파일 생성
여러 파일을 생성하려는 경우(예: 모델의 각 요소에 대해 별도의 파일을 생성하려는 경우), 몇 가지 가능한 방법이 있습니다. 기본적으로 각 템플릿 파일에서 단 하나의 파일만 생성됩니다.
긴 파일 분할
이 메서드에서는 템플릿을 사용하여 구분 기호로 구분된 단일 파일을 생성합니다. 그런 다음 파일을 부분으로 분할합니다. 2개의 템플릿이 있으며, 하나는 단일 파일을 생성하고, 다른 하나는 이를 분할합니다.
LoopTemplate.t4는 긴 단일 파일을 생성합니다. 모든 템플릿 변환을 클릭할 때 직접적으로 처리되면 안 되므로 파일 확장명은 “.t4”입니다. 이 템플릿은 세그먼트를 구분하는 구분 기호 문자열을 지정하는 매개 변수를 가져옵니다.
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
<#@ parameter name="delimiter" type="System.String" #>
<#@ output extension=".txt" #>
<#@ MyDSL processor="MyDSLDirectiveProcessor" requires="fileName='SampleModel.mydsl1';validation='open|load|save|menu'" #>
<#
// Create a file segment for each element:
foreach (ExampleElement element in this.ExampleModel.Elements)
{
// First item is the delimiter:
#>
<#= string.Format(delimiter, element.Id) #>
Element: <#= element.Name #>
<#
// Here you generate more content derived from the element.
}
#>
LoopSplitter.tt
는 LoopTemplate.t4
를 호출한 다음 결과로 나오는 파일을 세그먼트로 분할합니다. 이 템플릿은 모델을 읽지 않으므로 모델링 템플릿일 필요는 없습니다.
<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="System.Runtime.Remoting.Messaging" #>
<#@ import namespace="System.IO" #>
<#
// Get the local path:
string itemTemplatePath = this.Host.ResolvePath("LoopTemplate.t4");
string dir = Path.GetDirectoryName(itemTemplatePath);
// Get the template for generating each file:
string loopTemplate = File.ReadAllText(itemTemplatePath);
Engine engine = new Engine();
// Pass parameter to new template:
string delimiterGuid = Guid.NewGuid().ToString();
string delimiter = "::::" + delimiterGuid + ":::";
CallContext.LogicalSetData("delimiter", delimiter + "{0}:::");
string joinedFiles = engine.ProcessTemplate(loopTemplate, this.Host);
string [] separateFiles = joinedFiles.Split(new string [] {delimiter}, StringSplitOptions.None);
foreach (string nameAndFile in separateFiles)
{
if (string.IsNullOrWhiteSpace(nameAndFile)) continue;
string[] parts = nameAndFile.Split(new string[]{":::"}, 2, StringSplitOptions.None);
if (parts.Length < 2) continue;
#>
Generate: [<#= dir #>] [<#= parts[0] #>]
<#
// Generate a file from this item:
File.WriteAllText(Path.Combine(dir, parts[0] + ".txt"), parts[1]);
}
#>